Allow for a pylance middleware that does exclusions based on isDocumentAllowed but doesn't change anything

This commit is contained in:
Rich Chiodo 2021-11-02 12:06:52 -07:00
Родитель 883ee82068
Коммит 0d6523198b
11 изменённых файлов: 826 добавлений и 45 удалений

59
.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,59 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": true, // set this to true to hide the "out" folder with the compiled JS files
"**/*.pyc": true,
".nyc_output": true,
"obj": true,
"bin": true,
"**/__pycache__": true,
"**/node_modules": false,
".vscode-test": true,
".vscode test": true,
".venv": true,
"**/.pytest_cache/**": true,
"languageServer.*/**": true,
"**/.mypy_cache/**": true,
"**/.ropeproject/**": true
},
"search.exclude": {
"out": true, // set this to false to include "out" folder in search results
"**/node_modules": true,
"coverage": true,
"languageServer*/**": true,
".vscode-test": true,
".vscode test": true
},
"[python]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
},
"[javascript]": {
"editor.formatOnSave": true
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"python.linting.enabled": false,
"python.testing.promptToConfigure": false,
"python.workspaceSymbols.enabled": false,
"python.formatting.provider": "black",
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
"typescriptHero.imports.stringQuoteStyle": "'",
"prettier.printWidth": 120,
"prettier.singleQuote": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.tslint": true
},
"python.languageServer": "Pylance",
"python.analysis.logLevel": "Trace",
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": [
// Match what black does.
"--max-line-length=88"
],
"typescript.preferences.importModuleSpecifier": "relative"
}

2
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{ {
"name": "@vscode/jupyter-lsp-middleware", "name": "@vscode/jupyter-lsp-middleware",
"version": "0.2.17", "version": "0.2.18",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"name": "@vscode/jupyter-lsp-middleware", "name": "@vscode/jupyter-lsp-middleware",
"version": "0.2.17", "version": "0.2.18",
"description": "VS Code Python Language Server Middleware for Jupyter Notebook", "description": "VS Code Python Language Server Middleware for Jupyter Notebook",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

Просмотреть файл

@ -6,6 +6,7 @@ import { LanguageClient, Middleware } from 'vscode-languageclient/node';
import { IVSCodeNotebook } from './common/types'; import { IVSCodeNotebook } from './common/types';
import { HidingMiddlewareAddon } from './hidingMiddlewareAddon'; import { HidingMiddlewareAddon } from './hidingMiddlewareAddon';
import { NotebookMiddlewareAddon } from './notebookMiddlewareAddon'; import { NotebookMiddlewareAddon } from './notebookMiddlewareAddon';
import { PylanceMiddlewareAddon } from './pylanceMiddlewareAddon';
export type NotebookMiddleware = Middleware & Disposable & { export type NotebookMiddleware = Middleware & Disposable & {
stopWatching(notebook: NotebookDocument): void; stopWatching(notebook: NotebookDocument): void;
@ -40,3 +41,17 @@ export function createNotebookMiddleware(
isDocumentAllowed isDocumentAllowed
); );
} }
export function createPylanceMiddleware(
getClient: () => LanguageClient | undefined,
pythonPath: string,
isDocumentAllowed: (uri: Uri) => boolean
): NotebookMiddleware {
// LanguageClients are created per interpreter (as they start) with a selector for all notebooks
// Middleware swallows all requests for notebooks that don't match itself (isDocumentAllowed returns false)
return new PylanceMiddlewareAddon(
getClient,
pythonPath,
isDocumentAllowed
);
}

Просмотреть файл

@ -0,0 +1,577 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {
CallHierarchyIncomingCall,
CallHierarchyItem,
CallHierarchyOutgoingCall,
CancellationToken,
CodeAction,
CodeActionContext,
CodeLens,
Color,
ColorInformation,
ColorPresentation,
Command,
CompletionContext,
CompletionItem,
Declaration,
Definition,
DefinitionLink,
Diagnostic,
Disposable,
DocumentHighlight,
DocumentLink,
DocumentSymbol,
FoldingContext,
FoldingRange,
FormattingOptions,
LinkedEditingRanges,
Location,
NotebookDocument,
Position,
Position as VPosition,
ProviderResult,
Range,
SelectionRange,
SemanticTokens,
SemanticTokensEdits,
SignatureHelp,
SignatureHelpContext,
SymbolInformation,
TextDocument,
TextEdit,
Uri,
WorkspaceEdit
} from 'vscode';
import {
ConfigurationParams,
ConfigurationRequest,
DidCloseTextDocumentNotification,
DidOpenTextDocumentNotification,
HandleDiagnosticsSignature,
LanguageClient,
Middleware,
PrepareRenameSignature,
ProvideCodeActionsSignature,
ProvideCodeLensesSignature,
ProvideCompletionItemsSignature,
ProvideDefinitionSignature,
ProvideDocumentFormattingEditsSignature,
ProvideDocumentHighlightsSignature,
ProvideDocumentLinksSignature,
ProvideDocumentRangeFormattingEditsSignature,
ProvideDocumentSymbolsSignature,
ProvideHoverSignature,
ProvideOnTypeFormattingEditsSignature,
ProvideReferencesSignature,
ProvideRenameEditsSignature,
ProvideSignatureHelpSignature,
ProvideWorkspaceSymbolsSignature,
ResolveCodeLensSignature,
ResolveCompletionItemSignature,
ResolveDocumentLinkSignature,
ResponseError
} from 'vscode-languageclient/node';
import { ProvideDeclarationSignature } from 'vscode-languageclient/lib/common/declaration';
import { isThenable } from './common/utils';
import { ProvideTypeDefinitionSignature } from 'vscode-languageclient/lib/common/typeDefinition';
import { ProvideImplementationSignature } from 'vscode-languageclient/lib/common/implementation';
import {
ProvideDocumentColorsSignature,
ProvideColorPresentationSignature
} from 'vscode-languageclient/lib/common/colorProvider';
import { ProvideFoldingRangeSignature } from 'vscode-languageclient/lib/common/foldingRange';
import { ProvideSelectionRangeSignature } from 'vscode-languageclient/lib/common/selectionRange';
import {
PrepareCallHierarchySignature,
CallHierarchyIncomingCallsSignature,
CallHierarchyOutgoingCallsSignature
} from 'vscode-languageclient/lib/common/callHierarchy';
import {
DocumentRangeSemanticTokensSignature,
DocumentSemanticsTokensEditsSignature,
DocumentSemanticsTokensSignature
} from 'vscode-languageclient/lib/common/semanticTokens';
import { ProvideLinkedEditingRangeSignature } from 'vscode-languageclient/lib/common/linkedEditingRange';
/**
* This class is a temporary solution to handling intellisense and diagnostics in python based notebooks.
*
* It is responsible for sending requests to pylance if they are allowed.
*/
export class PylanceMiddlewareAddon implements Middleware, Disposable {
constructor(
private readonly getClient: () => LanguageClient | undefined,
private readonly pythonPath: string,
private readonly isDocumentAllowed: (uri: Uri) => boolean
) {
// Make sure a bunch of functions are bound to this. VS code can call them without a this context
this.handleDiagnostics = this.handleDiagnostics.bind(this);
this.didOpen = this.didOpen.bind(this);
}
public workspace = {
configuration: async (
params: ConfigurationParams,
token: CancellationToken,
next: ConfigurationRequest.HandlerSignature
) => {
// Handle workspace/configuration requests.
let settings = next(params, token);
if (isThenable(settings)) {
settings = await settings;
}
if (settings instanceof ResponseError) {
return settings;
}
for (const [i, item] of params.items.entries()) {
if (item.section === 'python') {
settings[i].pythonPath = this.pythonPath;
}
}
return settings;
}
};
public dispose(): void {
// Nothing to dispose at the moment
}
public stopWatching(notebook: NotebookDocument): void {
// Close all of the cells. This should cause diags and other things to be cleared
const client = this.getClient();
if (client && notebook.cellCount > 0) {
notebook.getCells().forEach((c) => {
const params = client.code2ProtocolConverter.asCloseTextDocumentParams(c.document);
client.sendNotification(DidCloseTextDocumentNotification.type, params);
});
// Set the diagnostics to nothing for all the cells
if (client.diagnostics) {
notebook.getCells().forEach((c) => {
client.diagnostics?.set(c.document.uri, []);
});
}
}
}
public startWatching(notebook: NotebookDocument): void {
// We need to talk directly to the language client here.
const client = this.getClient();
// Mimic a document open for all cells
if (client && notebook.cellCount > 0) {
notebook.getCells().forEach((c) => {
this.didOpen(c.document, (ev) => {
const params = client.code2ProtocolConverter.asOpenTextDocumentParams(ev);
client.sendNotification(DidOpenTextDocumentNotification.type, params);
});
});
}
}
public didOpen(document: TextDocument, next: (ev: TextDocument) => void) {
next(document);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public provideCompletionItem(
document: TextDocument,
position: Position,
context: CompletionContext,
token: CancellationToken,
next: ProvideCompletionItemsSignature
) {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, context, token);
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
public provideHover(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideHoverSignature
) {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
// eslint-disable-next-line class-methods-use-this
public resolveCompletionItem(
item: CompletionItem,
token: CancellationToken,
next: ResolveCompletionItemSignature
): ProviderResult<CompletionItem> {
// Range should have already been remapped.
// TODO: What if the LS needs to read the range? It won't make sense. This might mean
// doing this at the extension level is not possible.
return next(item, token);
}
public provideSignatureHelp(
document: TextDocument,
position: Position,
context: SignatureHelpContext,
token: CancellationToken,
next: ProvideSignatureHelpSignature
): ProviderResult<SignatureHelp> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, context, token);
}
}
public provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideDefinitionSignature
): ProviderResult<Definition | DefinitionLink[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
public provideReferences(
document: TextDocument,
position: Position,
options: {
includeDeclaration: boolean;
},
token: CancellationToken,
next: ProvideReferencesSignature
): ProviderResult<Location[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, options, token);
}
}
public provideDocumentHighlights(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideDocumentHighlightsSignature
): ProviderResult<DocumentHighlight[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
public provideDocumentSymbols(
document: TextDocument,
token: CancellationToken,
next: ProvideDocumentSymbolsSignature
): ProviderResult<SymbolInformation[] | DocumentSymbol[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, token);
}
}
public provideWorkspaceSymbols(
query: string,
token: CancellationToken,
next: ProvideWorkspaceSymbolsSignature
): ProviderResult<SymbolInformation[]> {
// Is this one possible to check?
return next(query, token);
}
// eslint-disable-next-line class-methods-use-this
public provideCodeActions(
document: TextDocument,
range: Range,
context: CodeActionContext,
token: CancellationToken,
next: ProvideCodeActionsSignature
): ProviderResult<(Command | CodeAction)[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, range, context, token);
}
}
// eslint-disable-next-line class-methods-use-this
public provideCodeLenses(
document: TextDocument,
token: CancellationToken,
next: ProvideCodeLensesSignature
): ProviderResult<CodeLens[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, token);
}
}
// eslint-disable-next-line class-methods-use-this
public resolveCodeLens(
codeLens: CodeLens,
token: CancellationToken,
next: ResolveCodeLensSignature
): ProviderResult<CodeLens> {
// Range should have already been remapped.
// TODO: What if the LS needs to read the range? It won't make sense. This might mean
// doing this at the extension level is not possible.
return next(codeLens, token);
}
// eslint-disable-next-line class-methods-use-this
public provideDocumentFormattingEdits(
document: TextDocument,
options: FormattingOptions,
token: CancellationToken,
next: ProvideDocumentFormattingEditsSignature
): ProviderResult<TextEdit[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, options, token);
}
}
// eslint-disable-next-line class-methods-use-this
public provideDocumentRangeFormattingEdits(
document: TextDocument,
range: Range,
options: FormattingOptions,
token: CancellationToken,
next: ProvideDocumentRangeFormattingEditsSignature
): ProviderResult<TextEdit[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, range, options, token);
}
}
// eslint-disable-next-line class-methods-use-this
public provideOnTypeFormattingEdits(
document: TextDocument,
position: Position,
ch: string,
options: FormattingOptions,
token: CancellationToken,
next: ProvideOnTypeFormattingEditsSignature
): ProviderResult<TextEdit[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, ch, options, token);
}
}
// eslint-disable-next-line class-methods-use-this
public provideRenameEdits(
document: TextDocument,
position: Position,
newName: string,
token: CancellationToken,
next: ProvideRenameEditsSignature
): ProviderResult<WorkspaceEdit> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, newName, token);
}
}
// eslint-disable-next-line class-methods-use-this
public prepareRename(
document: TextDocument,
position: Position,
token: CancellationToken,
next: PrepareRenameSignature
): ProviderResult<
| Range
| {
range: Range;
placeholder: string;
}
> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
// eslint-disable-next-line class-methods-use-this
public provideDocumentLinks(
document: TextDocument,
token: CancellationToken,
next: ProvideDocumentLinksSignature
): ProviderResult<DocumentLink[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, token);
}
}
// eslint-disable-next-line class-methods-use-this
public resolveDocumentLink(
link: DocumentLink,
token: CancellationToken,
next: ResolveDocumentLinkSignature
): ProviderResult<DocumentLink> {
// Range should have already been remapped.
// TODO: What if the LS needs to read the range? It won't make sense. This might mean
// doing this at the extension level is not possible.
return next(link, token);
}
public handleDiagnostics(uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature): void {
if (this.shouldProvideIntellisense(uri)) {
return next(uri, diagnostics);
} else {
// Swallow all other diagnostics
next(uri, []);
}
}
public provideTypeDefinition(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideTypeDefinitionSignature
) {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
public provideImplementation(
document: TextDocument,
position: VPosition,
token: CancellationToken,
next: ProvideImplementationSignature
): ProviderResult<Definition | DefinitionLink[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
public provideDocumentColors(
document: TextDocument,
token: CancellationToken,
next: ProvideDocumentColorsSignature
): ProviderResult<ColorInformation[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, token);
}
}
public provideColorPresentations(
color: Color,
context: {
document: TextDocument;
range: Range;
},
token: CancellationToken,
next: ProvideColorPresentationSignature
): ProviderResult<ColorPresentation[]> {
if (this.shouldProvideIntellisense(context.document.uri)) {
return next(color, context, token);
}
}
public provideFoldingRanges(
document: TextDocument,
context: FoldingContext,
token: CancellationToken,
next: ProvideFoldingRangeSignature
): ProviderResult<FoldingRange[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, context, token);
}
}
public provideDeclaration(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideDeclarationSignature
): ProviderResult<Declaration> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
public provideSelectionRanges(
document: TextDocument,
positions: Position[],
token: CancellationToken,
next: ProvideSelectionRangeSignature
): ProviderResult<SelectionRange[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, positions, token);
}
}
public prepareCallHierarchy(
document: TextDocument,
positions: Position,
token: CancellationToken,
next: PrepareCallHierarchySignature
): ProviderResult<CallHierarchyItem | CallHierarchyItem[]> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, positions, token);
}
}
public provideCallHierarchyIncomingCalls(
item: CallHierarchyItem,
token: CancellationToken,
next: CallHierarchyIncomingCallsSignature
): ProviderResult<CallHierarchyIncomingCall[]> {
if (this.shouldProvideIntellisense(item.uri)) {
return next(item, token);
}
}
public provideCallHierarchyOutgoingCalls(
item: CallHierarchyItem,
token: CancellationToken,
next: CallHierarchyOutgoingCallsSignature
): ProviderResult<CallHierarchyOutgoingCall[]> {
if (this.shouldProvideIntellisense(item.uri)) {
return next(item, token);
}
}
public provideDocumentSemanticTokens(
document: TextDocument,
token: CancellationToken,
next: DocumentSemanticsTokensSignature
): ProviderResult<SemanticTokens> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, token);
}
}
public provideDocumentSemanticTokensEdits(
document: TextDocument,
previousResultId: string,
token: CancellationToken,
next: DocumentSemanticsTokensEditsSignature
): ProviderResult<SemanticTokensEdits | SemanticTokens> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, previousResultId, token);
}
}
public provideDocumentRangeSemanticTokens(
document: TextDocument,
range: Range,
token: CancellationToken,
next: DocumentRangeSemanticTokensSignature
): ProviderResult<SemanticTokens> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, range, token);
}
}
public provideLinkedEditingRange(
document: TextDocument,
position: Position,
token: CancellationToken,
next: ProvideLinkedEditingRangeSignature
): ProviderResult<LinkedEditingRanges> {
if (this.shouldProvideIntellisense(document.uri)) {
return next(document, position, token);
}
}
private shouldProvideIntellisense(uri: Uri): boolean {
// Make sure document is allowed
return this.isDocumentAllowed(uri);
}
}

Просмотреть файл

@ -23,7 +23,7 @@ import {
ServerCapabilities, ServerCapabilities,
StaticFeature StaticFeature
} from 'vscode-languageclient/node'; } from 'vscode-languageclient/node';
import { createNotebookMiddleware, createHidingMiddleware } from '../..'; import { createNotebookMiddleware, createHidingMiddleware, createPylanceMiddleware } from '../..';
import { FileBasedCancellationStrategy } from '../../fileBasedCancellationStrategy'; import { FileBasedCancellationStrategy } from '../../fileBasedCancellationStrategy';
import * as uuid from 'uuid/v4'; import * as uuid from 'uuid/v4';
@ -870,10 +870,40 @@ export class LanguageServer implements vscode.Disposable {
} }
} }
export type MiddlewareType = 'pylance' | 'hiding' | 'notebook';
function createMiddleware(
middlewareType: MiddlewareType,
notebookApi: IVSCodeNotebook,
getClient: () => LanguageClient | undefined,
traceInfo: (...args: any[]) => void,
cellSelector: DocumentSelector,
notebookFileRegex: RegExp,
pythonPath: string,
isDocumentAllowed: (uri: vscode.Uri) => boolean
) {
switch (middlewareType) {
case 'hiding':
return createHidingMiddleware();
case 'pylance':
return createPylanceMiddleware(getClient, pythonPath, isDocumentAllowed);
case 'notebook':
return createNotebookMiddleware(
notebookApi,
getClient,
traceInfo,
cellSelector,
notebookFileRegex,
pythonPath,
isDocumentAllowed
);
}
}
async function startLanguageServer( async function startLanguageServer(
outputChannel: string, outputChannel: string,
languageServerFolder: string, languageServerFolder: string,
hidingMiddleware: boolean, middlewareType: MiddlewareType,
pythonPath: string, pythonPath: string,
selector: DocumentSelector, selector: DocumentSelector,
shouldProvideIntellisense: (uri: vscode.Uri) => boolean shouldProvideIntellisense: (uri: vscode.Uri) => boolean
@ -902,7 +932,8 @@ async function startLanguageServer(
} }
}; };
const middleware = hidingMiddleware ? createHidingMiddleware() : createNotebookMiddleware( const middleware = createMiddleware(
middlewareType,
notebookApi, notebookApi,
() => languageClient, () => languageClient,
traceInfo, traceInfo,
@ -952,7 +983,7 @@ async function startLanguageServer(
export async function createLanguageServer( export async function createLanguageServer(
outputChannel: string, outputChannel: string,
selector: DocumentSelector, selector: DocumentSelector,
hidingMiddleware: boolean, middlewareType: MiddlewareType,
shouldProvideIntellisense: (uri: vscode.Uri) => boolean shouldProvideIntellisense: (uri: vscode.Uri) => boolean
): Promise<LanguageServer | undefined> { ): Promise<LanguageServer | undefined> {
// Python should be installed too. // Python should be installed too.
@ -972,7 +1003,7 @@ export async function createLanguageServer(
return startLanguageServer( return startLanguageServer(
outputChannel, outputChannel,
path.join(pylance.extensionPath, 'dist'), path.join(pylance.extensionPath, 'dist'),
hidingMiddleware, middlewareType,
pythonPath, pythonPath,
selector, selector,
shouldProvideIntellisense shouldProvideIntellisense

Просмотреть файл

@ -3,15 +3,7 @@
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
import { assert } from 'chai'; import { assert } from 'chai';
import { import { Position, Disposable, languages, Range, WorkspaceEdit, workspace, Uri } from 'vscode';
Position,
Disposable,
languages,
Range,
WorkspaceEdit,
workspace,
Uri
} from 'vscode';
import { DocumentFilter } from 'vscode-languageserver-protocol'; import { DocumentFilter } from 'vscode-languageserver-protocol';
import { import {
canRunNotebookTests, canRunNotebookTests,
@ -56,7 +48,7 @@ suite('Hiding tests', function () {
languageServer = await createLanguageServer( languageServer = await createLanguageServer(
'lsp-middleware-test', 'lsp-middleware-test',
NOTEBOOK_SELECTOR, NOTEBOOK_SELECTOR,
true, 'hiding',
shouldProvideIntellisense shouldProvideIntellisense
); );
}); });

Просмотреть файл

@ -63,7 +63,7 @@ suite('Notebook tests', function () {
languageServer = await createLanguageServer( languageServer = await createLanguageServer(
'lsp-middleware-test', 'lsp-middleware-test',
NOTEBOOK_SELECTOR, NOTEBOOK_SELECTOR,
false, 'notebook',
shouldProvideIntellisense shouldProvideIntellisense
); );
}); });
@ -230,7 +230,7 @@ suite('Notebook tests', function () {
); );
}); });
test('Make sure diags are skipped when not allowing', async function () { test('Make sure diags are skipped when not allowing', async function () {
this.skip(); // Skip for now. Requires jupyter to not be providing intellisense too this.skip(); // Skip for now. Requires jupyter extension to not be providing intellisense too
allowIntellisense = false; allowIntellisense = false;
await insertCodeCell('import sys\nprint(sys.executable)'); await insertCodeCell('import sys\nprint(sys.executable)');
await insertCodeCell('import sys\nprint(sys.executable)'); await insertCodeCell('import sys\nprint(sys.executable)');

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
import { assert } from 'chai';
import { Position, Disposable, languages, Range, WorkspaceEdit, workspace, Uri } from 'vscode';
import { DocumentFilter } from 'vscode-languageserver-protocol';
import {
canRunNotebookTests,
closeNotebooksAndCleanUpAfterTests,
insertCodeCell,
createEmptyPythonNotebook,
traceInfo,
createLanguageServer,
focusCell,
captureScreenShot,
captureOutputMessages,
LanguageServer,
waitForDiagnostics
} from './helper';
export const PYTHON_LANGUAGE = 'python';
export const NotebookCellScheme = 'vscode-notebook-cell';
export const InteractiveInputScheme = 'vscode-interactive-input';
export const NOTEBOOK_SELECTOR: DocumentFilter[] = [
{ scheme: NotebookCellScheme, language: PYTHON_LANGUAGE },
{ scheme: InteractiveInputScheme, language: PYTHON_LANGUAGE }
];
/* eslint-disable @typescript-eslint/no-explicit-any, no-invalid-this */
suite('Pylance tests', function () {
const disposables: Disposable[] = [];
let languageServer: LanguageServer | undefined = undefined;
let allowIntellisense = true;
let emptyNotebookUri: Uri | undefined;
const shouldProvideIntellisense = (uri: Uri) => {
if (emptyNotebookUri?.fsPath === uri.fsPath) {
return allowIntellisense;
}
return false;
};
this.timeout(120_000);
suiteSetup(async function () {
this.timeout(120_000);
if (!canRunNotebookTests()) {
return this.skip();
}
languageServer = await createLanguageServer(
'lsp-middleware-test',
NOTEBOOK_SELECTOR,
'pylance',
shouldProvideIntellisense
);
});
// Use same notebook without starting kernel in every single test (use one for whole suite).
setup(async function () {
traceInfo(`Start Test ${this.currentTest?.title}`);
allowIntellisense = true;
emptyNotebookUri = await createEmptyPythonNotebook(disposables);
traceInfo(`Start Test (completed) ${this.currentTest?.title}`);
});
teardown(async function () {
traceInfo(`Ended Test ${this.currentTest?.title}`);
if (this.currentTest && this.currentTest.state === 'failed') {
await captureScreenShot(this.currentTest.title);
await captureOutputMessages();
}
await closeNotebooksAndCleanUpAfterTests(disposables);
traceInfo(`Ended Test (completed) ${this.currentTest?.title}`);
});
suiteTeardown(async () => {
closeNotebooksAndCleanUpAfterTests(disposables);
await languageServer?.dispose();
});
test('Edit a cell and make sure diagnostics do show up', async () => {
// Pylance should definitely be able to handle a single cell
const cell = await insertCodeCell('import sys\nprint(sys.executable)\na = 1');
// Should be no diagnostics yet
let diagnostics = languages.getDiagnostics(cell.document.uri);
assert.isEmpty(diagnostics, 'No diagnostics should be found in the first cell');
// Edit the cell
await focusCell(cell);
const edit = new WorkspaceEdit();
edit.replace(cell.document.uri, new Range(new Position(0, 7), new Position(0, 10)), 'system');
await workspace.applyEdit(edit);
// There should be diagnostics now
await waitForDiagnostics(cell.document.uri);
});
});

63
vscode.d.ts поставляемый
Просмотреть файл

@ -766,8 +766,9 @@ declare module 'vscode' {
preserveFocus?: boolean; preserveFocus?: boolean;
/** /**
* An optional flag that controls if an {@link TextEditor editor}-tab will be replaced * An optional flag that controls if an {@link TextEditor editor}-tab shows as preview. Preview tabs will
* with the next editor or if it will be kept. * be replaced and reused until set to stay - either explicitly or through editing. The default behaviour depends
* on the `workbench.editor.enablePreview`-setting.
*/ */
preview?: boolean; preview?: boolean;
@ -2566,11 +2567,12 @@ declare module 'vscode' {
} }
/** /**
* The MarkdownString represents human-readable text that supports formatting via the * Human-readable text that supports formatting via the [markdown syntax](https://commonmark.org).
* markdown syntax. Standard markdown is supported, also tables, but no embedded html.
* *
* Rendering of {@link ThemeIcon theme icons} via the `$(<name>)`-syntax is supported * Rendering of {@link ThemeIcon theme icons} via the `$(<name>)`-syntax is supported
* when the {@linkcode MarkdownString.supportThemeIcons supportThemeIcons} is set to `true`. * when the {@linkcode supportThemeIcons} is set to `true`.
*
* Rendering of embedded html is supported when {@linkcode supportHtml} is set to `true`.
*/ */
export class MarkdownString { export class MarkdownString {
@ -2591,7 +2593,7 @@ declare module 'vscode' {
supportThemeIcons?: boolean; supportThemeIcons?: boolean;
/** /**
* Indicates that this markdown string can contain raw html tags. Defaults to false. * Indicates that this markdown string can contain raw html tags. Defaults to `false`.
* *
* When `supportHtml` is false, the markdown renderer will strip out any raw html tags * When `supportHtml` is false, the markdown renderer will strip out any raw html tags
* that appear in the markdown text. This means you can only use markdown syntax for rendering. * that appear in the markdown text. This means you can only use markdown syntax for rendering.
@ -3550,7 +3552,7 @@ declare module 'vscode' {
* *
* This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented).
*/ */
readonly resultId?: string; readonly resultId: string | undefined;
/** /**
* The actual tokens data. * The actual tokens data.
* @see {@link DocumentSemanticTokensProvider.provideDocumentSemanticTokens provideDocumentSemanticTokens} for an explanation of the format. * @see {@link DocumentSemanticTokensProvider.provideDocumentSemanticTokens provideDocumentSemanticTokens} for an explanation of the format.
@ -3570,7 +3572,7 @@ declare module 'vscode' {
* *
* This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented).
*/ */
readonly resultId?: string; readonly resultId: string | undefined;
/** /**
* The edits to the tokens data. * The edits to the tokens data.
* All edits refer to the initial data state. * All edits refer to the initial data state.
@ -3596,7 +3598,7 @@ declare module 'vscode' {
/** /**
* The elements to insert. * The elements to insert.
*/ */
readonly data?: Uint32Array; readonly data: Uint32Array | undefined;
constructor(start: number, deleteCount: number, data?: Uint32Array); constructor(start: number, deleteCount: number, data?: Uint32Array);
} }
@ -5745,7 +5747,7 @@ declare module 'vscode' {
* The priority of this item. Higher value means the item should * The priority of this item. Higher value means the item should
* be shown more to the left. * be shown more to the left.
*/ */
readonly priority?: number; readonly priority: number | undefined;
/** /**
* The name of the entry, like 'Python Language Indicator', 'Git Status' etc. * The name of the entry, like 'Python Language Indicator', 'Git Status' etc.
@ -5801,7 +5803,7 @@ declare module 'vscode' {
/** /**
* Accessibility information used when a screen reader interacts with this StatusBar item * Accessibility information used when a screen reader interacts with this StatusBar item
*/ */
accessibilityInformation?: AccessibilityInformation; accessibilityInformation: AccessibilityInformation | undefined;
/** /**
* Shows the entry in the status bar. * Shows the entry in the status bar.
@ -8421,7 +8423,7 @@ declare module 'vscode' {
/** /**
* The detected default shell for the extension host, this is overridden by the * The detected default shell for the extension host, this is overridden by the
* `terminal.integrated.shell` setting for the extension host's platform. Note that in * `terminal.integrated.defaultProfile` setting for the extension host's platform. Note that in
* environments that do not support a shell the value is the empty string. * environments that do not support a shell the value is the empty string.
*/ */
export const shell: string; export const shell: string;
@ -9723,6 +9725,8 @@ declare module 'vscode' {
* Note writing `\n` will just move the cursor down 1 row, you need to write `\r` as well * Note writing `\n` will just move the cursor down 1 row, you need to write `\r` as well
* to move the cursor to the left-most cell. * to move the cursor to the left-most cell.
* *
* Events fired before {@link Pseudoterminal.open} is called will be be ignored.
*
* **Example:** Write red text to the terminal * **Example:** Write red text to the terminal
* ```typescript * ```typescript
* const writeEmitter = new vscode.EventEmitter<string>(); * const writeEmitter = new vscode.EventEmitter<string>();
@ -9748,6 +9752,8 @@ declare module 'vscode' {
* bar). Set to `undefined` for the terminal to go back to the regular dimensions (fit to * bar). Set to `undefined` for the terminal to go back to the regular dimensions (fit to
* the size of the panel). * the size of the panel).
* *
* Events fired before {@link Pseudoterminal.open} is called will be be ignored.
*
* **Example:** Override the dimensions of a terminal to 20 columns and 10 rows * **Example:** Override the dimensions of a terminal to 20 columns and 10 rows
* ```typescript * ```typescript
* const dimensionsEmitter = new vscode.EventEmitter<vscode.TerminalDimensions>(); * const dimensionsEmitter = new vscode.EventEmitter<vscode.TerminalDimensions>();
@ -9770,6 +9776,8 @@ declare module 'vscode' {
/** /**
* An event that when fired will signal that the pty is closed and dispose of the terminal. * An event that when fired will signal that the pty is closed and dispose of the terminal.
* *
* Events fired before {@link Pseudoterminal.open} is called will be be ignored.
*
* A number can be used to provide an exit code for the terminal. Exit codes must be * A number can be used to provide an exit code for the terminal. Exit codes must be
* positive and a non-zero exit codes signals failure which shows a notification for a * positive and a non-zero exit codes signals failure which shows a notification for a
* regular terminal and allows dependent tasks to proceed when used with the * regular terminal and allows dependent tasks to proceed when used with the
@ -9799,6 +9807,8 @@ declare module 'vscode' {
/** /**
* An event that when fired allows changing the name of the terminal. * An event that when fired allows changing the name of the terminal.
* *
* Events fired before {@link Pseudoterminal.open} is called will be be ignored.
*
* **Example:** Change the terminal name to "My new terminal". * **Example:** Change the terminal name to "My new terminal".
* ```typescript * ```typescript
* const writeEmitter = new vscode.EventEmitter<string>(); * const writeEmitter = new vscode.EventEmitter<string>();
@ -10835,8 +10845,8 @@ declare module 'vscode' {
* will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern}
* to restrict the search results to a {@link WorkspaceFolder workspace folder}. * to restrict the search results to a {@link WorkspaceFolder workspace folder}.
* @param exclude A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern * @param exclude A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes and the user's * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default file-excludes (e.g. the `files.exclude`-setting
* configured excludes will apply. When `null`, no excludes will apply. * but not `search.exclude`) will apply. When `null`, no excludes will apply.
* @param maxResults An upper-bound for the result. * @param maxResults An upper-bound for the result.
* @param token A token that can be used to signal cancellation to the underlying search engine. * @param token A token that can be used to signal cancellation to the underlying search engine.
* @return A thenable that resolves to an array of resource identifiers. Will return no results if no * @return A thenable that resolves to an array of resource identifiers. Will return no results if no
@ -12733,8 +12743,9 @@ declare module 'vscode' {
* The UI-visible count of {@link SourceControlResourceState resource states} of * The UI-visible count of {@link SourceControlResourceState resource states} of
* this source control. * this source control.
* *
* Equals to the total number of {@link SourceControlResourceState resource state} * If undefined, this source control will
* of this source control, if undefined. * - display its UI-visible count as zero, and
* - contribute the count of its {@link SourceControlResourceState resource states} to the UI-visible aggregated count for all source controls
*/ */
count?: number; count?: number;
@ -14101,7 +14112,7 @@ declare module 'vscode' {
* Associated tag for the profile. If this is set, only {@link TestItem} * Associated tag for the profile. If this is set, only {@link TestItem}
* instances with the same tag will be eligible to execute in this profile. * instances with the same tag will be eligible to execute in this profile.
*/ */
tag?: TestTag; tag: TestTag | undefined;
/** /**
* If this method is present, a configuration gear will be present in the * If this method is present, a configuration gear will be present in the
@ -14109,7 +14120,7 @@ declare module 'vscode' {
* you can take other editor actions, such as showing a quick pick or * you can take other editor actions, such as showing a quick pick or
* opening a configuration file. * opening a configuration file.
*/ */
configureHandler?: () => void; configureHandler: (() => void) | undefined;
/** /**
* Handler called to start a test run. When invoked, the function should call * Handler called to start a test run. When invoked, the function should call
@ -14258,7 +14269,7 @@ declare module 'vscode' {
* The process of running tests should resolve the children of any test * The process of running tests should resolve the children of any test
* items who have not yet been resolved. * items who have not yet been resolved.
*/ */
readonly include?: TestItem[]; readonly include: TestItem[] | undefined;
/** /**
* An array of tests the user has marked as excluded from the test included * An array of tests the user has marked as excluded from the test included
@ -14267,14 +14278,14 @@ declare module 'vscode' {
* May be omitted if no exclusions were requested. Test controllers should * May be omitted if no exclusions were requested. Test controllers should
* not run excluded tests or any children of excluded tests. * not run excluded tests or any children of excluded tests.
*/ */
readonly exclude?: TestItem[]; readonly exclude: TestItem[] | undefined;
/** /**
* The profile used for this request. This will always be defined * The profile used for this request. This will always be defined
* for requests issued from the editor UI, though extensions may * for requests issued from the editor UI, though extensions may
* programmatically create requests not associated with any profile. * programmatically create requests not associated with any profile.
*/ */
readonly profile?: TestRunProfile; readonly profile: TestRunProfile | undefined;
/** /**
* @param tests Array of specific tests to run, or undefined to run all tests * @param tests Array of specific tests to run, or undefined to run all tests
@ -14293,7 +14304,7 @@ declare module 'vscode' {
* disambiguate multiple sets of results in a test run. It is useful if * disambiguate multiple sets of results in a test run. It is useful if
* tests are run across multiple platforms, for example. * tests are run across multiple platforms, for example.
*/ */
readonly name?: string; readonly name: string | undefined;
/** /**
* A cancellation token which will be triggered when the test run is * A cancellation token which will be triggered when the test run is
@ -14433,7 +14444,7 @@ declare module 'vscode' {
/** /**
* URI this `TestItem` is associated with. May be a file or directory. * URI this `TestItem` is associated with. May be a file or directory.
*/ */
readonly uri?: Uri; readonly uri: Uri | undefined;
/** /**
* The children of this test item. For a test suite, this may contain the * The children of this test item. For a test suite, this may contain the
@ -14446,7 +14457,7 @@ declare module 'vscode' {
* top-level items in the {@link TestController.items} and for items that * top-level items in the {@link TestController.items} and for items that
* aren't yet included in another item's {@link children}. * aren't yet included in another item's {@link children}.
*/ */
readonly parent?: TestItem; readonly parent: TestItem | undefined;
/** /**
* Tags associated with this test item. May be used in combination with * Tags associated with this test item. May be used in combination with
@ -14488,7 +14499,7 @@ declare module 'vscode' {
* *
* This is only meaningful if the `uri` points to a file. * This is only meaningful if the `uri` points to a file.
*/ */
range?: Range; range: Range | undefined;
/** /**
* Optional error encountered while loading the test. * Optional error encountered while loading the test.
@ -14496,7 +14507,7 @@ declare module 'vscode' {
* Note that this is not a test result and should only be used to represent errors in * Note that this is not a test result and should only be used to represent errors in
* test discovery, such as syntax errors. * test discovery, such as syntax errors.
*/ */
error?: string | MarkdownString; error: string | MarkdownString | undefined;
} }
/** /**

5
vscode.proposed.d.ts поставляемый
Просмотреть файл

@ -1575,6 +1575,10 @@ declare module 'vscode' {
} }
export interface NotebookController { export interface NotebookController {
/**
* The human-readable label used to categorise controllers.
*/
kind?: string;
// todo@API allow add, not remove // todo@API allow add, not remove
readonly rendererScripts: NotebookRendererScript[]; readonly rendererScripts: NotebookRendererScript[];
@ -1822,6 +1826,7 @@ declare module 'vscode' {
/** /**
* The text of the hint. * The text of the hint.
*/ */
// todo@API label?
text: string; text: string;
/** /**
* The position of this hint. * The position of this hint.