[css/html/json] provide replace & insert proposals

This commit is contained in:
Martin Aeschlimann 2019-12-02 21:43:59 +01:00
parent f4fa05e0ae
commit 5f1b81b497
7 changed files with 227 additions and 122 deletions

View File

@ -5,8 +5,8 @@
import * as fs from 'fs';
import * as path from 'path';
import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, workspace } from 'vscode';
import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, workspace, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList } from 'vscode';
import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, ProvideCompletionItemsSignature } from 'vscode-languageclient';
import * as nls from 'vscode-nls';
import { getCustomDataPathsFromAllExtensions, getCustomDataPathsInAllWorkspaces } from './customData';
@ -43,6 +43,31 @@ export function activate(context: ExtensionContext) {
},
initializationOptions: {
dataPaths
},
middleware: {
// testing the replace / insert mode
provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
function updateRanges(item: CompletionItem) {
const range = item.range;
if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) {
item.range2 = { inserting: new Range(range.start, position), replacing: range };
item.range = undefined;
}
}
function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined {
if (r) {
(Array.isArray(r) ? r : r.items).forEach(updateRanges);
}
return r;
}
const isThenable = <T>(obj: ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
const r = next(document, position, context, token);
if (isThenable<CompletionItem[] | CompletionList | null | undefined>(r)) {
return r.then(updateProposals);
}
return updateProposals(r);
}
}
};

View File

@ -783,6 +783,17 @@
}
}
],
"configurationDefaults": {
"[css]": {
"editor.suggest.insertMode": "replace"
},
"[scss]": {
"editor.suggest.insertMode": "replace"
},
"[less]": {
"editor.suggest.insertMode": "replace"
}
},
"jsonValidation": [
{
"fileMatch": "*.css-data.json",

View File

@ -8,8 +8,8 @@ import * as fs from 'fs';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient';
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature } from 'vscode-languageclient';
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateTagClosing } from './tagClosing';
import TelemetryReporter from 'vscode-extension-telemetry';
@ -72,6 +72,31 @@ export function activate(context: ExtensionContext) {
dataPaths,
provideFormatter: false, // tell the server to not provide formatting capability and ignore the `html.format.enable` setting.
},
middleware: {
// testing the replace / insert mode
provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
function updateRanges(item: CompletionItem) {
const range = item.range;
if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) {
item.range2 = { inserting: new Range(range.start, position), replacing: range };
item.range = undefined;
}
}
function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined {
if (r) {
(Array.isArray(r) ? r : r.items).forEach(updateRanges);
}
return r;
}
const isThenable = <T>(obj: ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
const r = next(document, position, context, token);
if (isThenable<CompletionItem[] | CompletionList | null | undefined>(r)) {
return r.then(updateProposals);
}
return updateProposals(r);
}
}
};
// Create the language client and start the client.

View File

@ -180,6 +180,14 @@
}
}
},
"configurationDefaults": {
"[html]": {
"editor.suggest.insertMode": "replace"
},
"[handlebars]": {
"editor.suggest.insertMode": "replace"
}
},
"jsonValidation": [
{
"fileMatch": "*.html-data.json",

View File

@ -10,8 +10,16 @@ import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light';
const localize = nls.loadMessageBundle();
import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, ProviderResult, TextEdit, Range, Disposable } from 'vscode';
import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient';
import {
workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration,
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken,
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext
} from 'vscode';
import {
LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType,
DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams,
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature
} from 'vscode-languageclient';
import TelemetryReporter from 'vscode-extension-telemetry';
import { hash } from './utils/hash';
@ -132,6 +140,29 @@ export function activate(context: ExtensionContext) {
}
next(uri, diagnostics);
},
// testing the replace / insert mode
provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
function updateRanges(item: CompletionItem) {
const range = item.range;
if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) {
item.range2 = { inserting: new Range(range.start, position), replacing: range };
item.range = undefined;
}
}
function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined {
if (r) {
(Array.isArray(r) ? r : r.items).forEach(updateRanges);
}
return r;
}
const isThenable = <T>(obj: ProviderResult<T>): obj is Thenable<T> => obj && (<any>obj)['then'];
const r = next(document, position, context, token);
if (isThenable<CompletionItem[] | CompletionList | null | undefined>(r)) {
return r.then(updateProposals);
}
return updateProposals(r);
}
}
};
@ -422,5 +453,4 @@ function readJSONFile(location: string) {
console.log(`Problems reading ${location}: ${e}`);
return {};
}
}

View File

@ -97,7 +97,8 @@
"[json]": {
"editor.quickSuggestions": {
"strings": true
}
},
"editor.suggest.insertMode": "replace"
}
}
},

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Location, getLocation, createScanner, SyntaxKind, ScanError } from 'jsonc-parser';
import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser';
import { basename } from 'path';
import { BowerJSONContribution } from './bowerJSONContribution';
import { PackageJSONContribution } from './packageJSONContribution';
@ -111,7 +111,7 @@ export class JSONCompletionItemProvider implements CompletionItemProvider {
add: (suggestion: CompletionItem) => {
if (!proposed[suggestion.label]) {
proposed[suggestion.label] = true;
suggestion.range = overwriteRange;
suggestion.range2 = { replacing: overwriteRange, inserting: new Range(overwriteRange.start, overwriteRange.start) };
items.push(suggestion);
}
},
@ -123,8 +123,9 @@ export class JSONCompletionItemProvider implements CompletionItemProvider {
let collectPromise: Thenable<any> | null = null;
if (location.isAtPropertyKey) {
const addValue = !location.previousNode || !location.previousNode.colonOffset;
const isLast = this.isLast(document, position);
const scanner = createScanner(document.getText(), true);
const addValue = !location.previousNode || !this.hasColonAfter(scanner, location.previousNode.offset + location.previousNode.length);
const isLast = this.isLast(scanner, document.offsetAt(position));
collectPromise = this.jsonContribution.collectPropertySuggestions(fileName, location, currentWord, addValue, isLast, collector);
} else {
if (location.path.length === 0) {
@ -153,15 +154,19 @@ export class JSONCompletionItemProvider implements CompletionItemProvider {
return text.substring(i + 1, position.character);
}
private isLast(document: TextDocument, position: Position): boolean {
const scanner = createScanner(document.getText(), true);
scanner.setPosition(document.offsetAt(position));
private isLast(scanner: JSONScanner, offset: number): boolean {
scanner.setPosition(offset);
let nextToken = scanner.scan();
if (nextToken === SyntaxKind.StringLiteral && scanner.getTokenError() === ScanError.UnexpectedEndOfString) {
nextToken = scanner.scan();
}
return nextToken === SyntaxKind.CloseBraceToken || nextToken === SyntaxKind.EOF;
}
private hasColonAfter(scanner: JSONScanner, offset: number): boolean {
scanner.setPosition(offset);
return scanner.scan() === SyntaxKind.ColonToken;
}
}
export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' });