forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add language server support for native interactive window #16560
Merged
Merged
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
fb35f87
Extract ConcatTextDocument
rebornix a1b44c2
Improve text len cache
rebornix 913f111
:lipstick:
rebornix e8362f3
find input document in iw.
rebornix 2d63363
hook up with jedi middleware
rebornix 44a7c01
:lipstick:
rebornix a91a642
postion is zero based
rebornix c7fdfcd
isClosed should include input document
joyceerhl c4a1f13
Dispose NotebookConcatDocument
joyceerhl 14e0eea
Properly handle onDidClose
joyceerhl b780a7a
hide diagnostics for interactive cells
rebornix e14f0b1
unnecessary diagnostics filtering.
rebornix a700a3e
class per file.
rebornix dd804a5
update styles.
rebornix 29c5456
Update unit tests
joyceerhl bc6fb25
Try to fix code quality
joyceerhl 430eefd
fix lint/format
rebornix 4688098
remove unary operator
rebornix 64194c1
Fix lint
joyceerhl 1e5706e
Fix lint
joyceerhl 3c8fcab
Run prettier
joyceerhl 9f93c45
Merge branch 'main' of https://github.com/microsoft/vscode-python int…
joyceerhl b635b5e
fix input document init.
rebornix 3213a8c
:lipstick:
rebornix 6e040ad
filter out non-python code.
rebornix e82f326
Format
joyceerhl 01cadeb
Format
joyceerhl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { Position, Range, Uri, Event, Location, TextLine } from 'vscode'; | ||
|
||
export interface IConcatTextDocument { | ||
onDidChange: Event<void>; | ||
isClosed: boolean; | ||
getText(range?: Range): string; | ||
contains(uri: Uri): boolean; | ||
offsetAt(position: Position): number; | ||
positionAt(locationOrOffset: Location | number): Position; | ||
validateRange(range: Range): Range; | ||
validatePosition(position: Position): Position; | ||
locationAt(positionOrRange: Position | Range): Location; | ||
lineAt(posOrNumber: Position | number): TextLine; | ||
getWordRangeAtPosition(position: Position, regexp?: RegExp | undefined): Range | undefined; | ||
} |
244 changes: 244 additions & 0 deletions
244
src/client/jupyter/languageserver/interactiveConcatTextDocument.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
/* eslint-disable class-methods-use-this */ | ||
|
||
import { | ||
NotebookDocument, | ||
Position, | ||
Range, | ||
TextDocument, | ||
Uri, | ||
workspace, | ||
DocumentSelector, | ||
Event, | ||
EventEmitter, | ||
Location, | ||
TextLine, | ||
} from 'vscode'; | ||
import { NotebookConcatTextDocument } from 'vscode-proposed'; | ||
|
||
import { IVSCodeNotebook } from '../../common/application/types'; | ||
import { InteractiveInputScheme, PYTHON_LANGUAGE } from '../../common/constants'; | ||
import { IConcatTextDocument } from './concatTextDocument'; | ||
|
||
export class InteractiveConcatTextDocument implements IConcatTextDocument { | ||
private _input: TextDocument | undefined = undefined; | ||
|
||
private _concatTextDocument: NotebookConcatTextDocument; | ||
|
||
private _lineCounts: [number, number] = [0, 0]; | ||
|
||
private _textLen: [number, number] = [0, 0]; | ||
|
||
private _onDidChange = new EventEmitter<void>(); | ||
|
||
onDidChange: Event<void> = this._onDidChange.event; | ||
|
||
get isClosed(): boolean { | ||
return this._concatTextDocument.isClosed || !!this._input?.isClosed; | ||
} | ||
|
||
constructor( | ||
private _notebook: NotebookDocument, | ||
private _selector: DocumentSelector, | ||
notebookApi: IVSCodeNotebook, | ||
) { | ||
this._concatTextDocument = notebookApi.createConcatTextDocument(_notebook, this._selector); | ||
|
||
this._concatTextDocument.onDidChange(() => { | ||
// not performant, NotebookConcatTextDocument should provide lineCount | ||
this._updateConcat(); | ||
this._onDidChange.fire(); | ||
}); | ||
|
||
workspace.onDidChangeTextDocument((e) => { | ||
if (e.document === this._input) { | ||
this._updateInput(); | ||
this._onDidChange.fire(); | ||
} | ||
}); | ||
|
||
this._updateConcat(); | ||
this._updateInput(); | ||
|
||
const once = workspace.onDidOpenTextDocument((e) => { | ||
if (e.uri.scheme === InteractiveInputScheme) { | ||
const counter = /Interactive-(\d+)\.interactive/.exec(this._notebook.uri.path); | ||
if (!counter || !counter[1]) { | ||
return; | ||
} | ||
|
||
if (e.uri.path.indexOf(`InteractiveInput-${counter[1]}`) >= 0) { | ||
this._input = e; | ||
this._updateInput(); | ||
once.dispose(); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
private _updateConcat() { | ||
let concatLineCnt = 0; | ||
let concatTextLen = 0; | ||
for (let i = 0; i < this._notebook.cellCount; i += 1) { | ||
const cell = this._notebook.cellAt(i); | ||
if (cell.document.languageId === PYTHON_LANGUAGE) { | ||
concatLineCnt += cell.document.lineCount + 1; | ||
concatTextLen += this._getDocumentTextLen(cell.document) + 1; | ||
} | ||
} | ||
|
||
this._lineCounts = [ | ||
concatLineCnt > 0 ? concatLineCnt - 1 : 0, // NotebookConcatTextDocument.lineCount | ||
this._lineCounts[1], | ||
]; | ||
|
||
this._textLen = [concatTextLen > 0 ? concatTextLen - 1 : 0, this._textLen[1]]; | ||
} | ||
|
||
private _updateInput() { | ||
this._lineCounts = [this._lineCounts[0], this._input?.lineCount ?? 0]; | ||
|
||
this._textLen = [this._textLen[0], this._getDocumentTextLen(this._input)]; | ||
} | ||
|
||
private _getDocumentTextLen(textDocument?: TextDocument): number { | ||
if (!textDocument) { | ||
return 0; | ||
} | ||
return textDocument.offsetAt(textDocument.lineAt(textDocument.lineCount - 1).range.end) + 1; | ||
} | ||
|
||
getText(range?: Range): string { | ||
if (!range) { | ||
let result = ''; | ||
result += `${this._concatTextDocument.getText()}\n${this._input?.getText() ?? ''}`; | ||
return result; | ||
} | ||
|
||
if (range.isEmpty) { | ||
return ''; | ||
} | ||
|
||
const start = this.locationAt(range.start); | ||
const end = this.locationAt(range.end); | ||
|
||
const startDocument = workspace.textDocuments.find( | ||
(document) => document.uri.toString() === start.uri.toString(), | ||
); | ||
const endDocument = workspace.textDocuments.find((document) => document.uri.toString() === end.uri.toString()); | ||
|
||
if (!startDocument || !endDocument) { | ||
return ''; | ||
} | ||
if (startDocument === endDocument) { | ||
return startDocument.getText(start.range); | ||
} | ||
|
||
const a = startDocument.getText(new Range(start.range.start, new Position(startDocument.lineCount, 0))); | ||
const b = endDocument.getText(new Range(new Position(0, 0), end.range.end)); | ||
return `${a}\n${b}`; | ||
} | ||
|
||
offsetAt(position: Position): number { | ||
const { line } = position; | ||
if (line >= this._lineCounts[0]) { | ||
// input box | ||
const lineOffset = Math.max(0, line - this._lineCounts[0] - 1); | ||
return this._input?.offsetAt(new Position(lineOffset, position.character)) ?? 0; | ||
} | ||
// concat | ||
return this._concatTextDocument.offsetAt(position); | ||
} | ||
|
||
// turning an offset on the final concatenatd document to position | ||
positionAt(locationOrOffset: Location | number): Position { | ||
if (typeof locationOrOffset === 'number') { | ||
const concatTextLen = this._textLen[0]; | ||
|
||
if (locationOrOffset >= concatTextLen) { | ||
// in the input box | ||
const offset = Math.max(0, locationOrOffset - concatTextLen - 1); | ||
return this._input?.positionAt(offset) ?? new Position(0, 0); | ||
} | ||
const position = this._concatTextDocument.positionAt(locationOrOffset); | ||
return new Position(this._lineCounts[0] + position.line, position.character); | ||
} | ||
|
||
if (locationOrOffset.uri.toString() === this._input?.uri.toString()) { | ||
// range in the input box | ||
return new Position( | ||
this._lineCounts[0] + locationOrOffset.range.start.line, | ||
locationOrOffset.range.start.character, | ||
); | ||
} | ||
return this._concatTextDocument.positionAt(locationOrOffset); | ||
} | ||
|
||
locationAt(positionOrRange: Range | Position): Location { | ||
if (positionOrRange instanceof Position) { | ||
positionOrRange = new Range(positionOrRange, positionOrRange); | ||
} | ||
|
||
const start = positionOrRange.start.line; | ||
if (start >= this._lineCounts[0]) { | ||
// this is the inputbox | ||
const offset = Math.max(0, start - this._lineCounts[0] - 1); | ||
const startPosition = new Position(offset, positionOrRange.start.character); | ||
const endOffset = Math.max(0, positionOrRange.end.line - this._lineCounts[0] - 1); | ||
const endPosition = new Position(endOffset, positionOrRange.end.character); | ||
|
||
// TODO@rebornix ! | ||
return new Location(this._input!.uri, new Range(startPosition, endPosition)); | ||
} | ||
|
||
// this is the NotebookConcatTextDocument | ||
return this._concatTextDocument.locationAt(positionOrRange); | ||
} | ||
|
||
contains(uri: Uri): boolean { | ||
if (this._input?.uri.toString() === uri.toString()) { | ||
return true; | ||
} | ||
|
||
return this._concatTextDocument.contains(uri); | ||
} | ||
|
||
validateRange(range: Range): Range { | ||
return range; | ||
} | ||
|
||
validatePosition(position: Position): Position { | ||
return position; | ||
} | ||
|
||
lineAt(posOrNumber: Position | number): TextLine { | ||
const position = typeof posOrNumber === 'number' ? new Position(posOrNumber, 0) : posOrNumber; | ||
|
||
// convert this position into a cell location | ||
// (we need the translated location, that's why we can't use getCellAtPosition) | ||
const location = this._concatTextDocument.locationAt(position); | ||
|
||
// Get the cell at this location | ||
if (location.uri.toString() === this._input?.uri.toString()) { | ||
return this._input.lineAt(location.range.start); | ||
} | ||
|
||
const cell = this._notebook.getCells().find((c) => c.document.uri.toString() === location.uri.toString()); | ||
return cell!.document.lineAt(location.range.start); | ||
} | ||
|
||
getWordRangeAtPosition(position: Position, regexp?: RegExp | undefined): Range | undefined { | ||
// convert this position into a cell location | ||
// (we need the translated location, that's why we can't use getCellAtPosition) | ||
const location = this._concatTextDocument.locationAt(position); | ||
|
||
if (location.uri.toString() === this._input?.uri.toString()) { | ||
return this._input.getWordRangeAtPosition(location.range.start, regexp); | ||
} | ||
|
||
// Get the cell at this location | ||
const cell = this._notebook.getCells().find((c) => c.document.uri.toString() === location.uri.toString()); | ||
return cell!.document.getWordRangeAtPosition(location.range.start, regexp); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, why are scopes not explicitly declared for these methods? Is it because it means they are
public
methods by default?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add a unit test this week, and there are still some other bugs with diagnostics not being cleared on document close that we will fix up in a separate PR. And yes all class members are public by default, and these methods are also supposed to be public.