Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@
"markdownDescription": "Enable/disable autoclosing of XML tags. Default is `true`. \n\nIMPORTANT: Turn off `#editor.autoClosingTags#` for this to work.",
"scope": "window"
},
"xml.completion.autoCreateQuotes": {
"type": "boolean",
"default": true,
"markdownDescription": "Enable/disable automatic creation of quotes for XML attribute values when typing `=`. Default is `true`.",
"scope": "window"
},
"xml.completion.autoCloseRemovesContent": {
"type": "boolean",
"default": true,
Expand Down
143 changes: 143 additions & 0 deletions src/client/autoInsertion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*
* Adapted from: https://github.com/microsoft/vscode/blob/main/extensions/html-language-features/client/src/autoInsertion.ts
*--------------------------------------------------------------------------------------------*/
'use strict';

import { window, workspace, Disposable, TextDocumentContentChangeEvent, TextDocument, Position, SnippetString, Range, TextDocumentChangeEvent, TextDocumentChangeReason } from 'vscode';

export interface AutoInsertResult {
snippet: string,
range?: Range
}

export function activateAutoInsertion(
provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable<AutoInsertResult>,
supportedLanguages: { [id: string]: boolean },
autoCloseConfigName: string,
autoQuoteConfigName: string
): Disposable {

const disposables: Disposable[] = [];
workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables);

let anyIsEnabled = false;
const isEnabled = {
'autoQuote': false,
'autoClose': false
};
updateEnabledState();
window.onDidChangeActiveTextEditor(updateEnabledState, null, disposables);

let timeout: NodeJS.Timeout | undefined = undefined;

disposables.push({
dispose: () => {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
}
});

function updateEnabledState() {
anyIsEnabled = false;
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
if (!supportedLanguages[document.languageId]) {
return;
}
const configurations = workspace.getConfiguration(undefined, document.uri);
isEnabled['autoClose'] = configurations.get<boolean>(autoCloseConfigName) ?? false;
isEnabled['autoQuote'] = configurations.get<boolean>(autoQuoteConfigName) ?? false;
anyIsEnabled = isEnabled['autoClose'] || isEnabled['autoQuote'];
}

function onDidChangeTextDocument({ document, contentChanges, reason }: TextDocumentChangeEvent) {
if (!anyIsEnabled || contentChanges.length === 0 || reason === TextDocumentChangeReason.Undo || reason === TextDocumentChangeReason.Redo) {
return;
}
const activeDocument = window.activeTextEditor && window.activeTextEditor.document;
if (document !== activeDocument) {
return;
}
if (typeof timeout !== 'undefined') {
clearTimeout(timeout);
}
const lastChange = contentChanges[contentChanges.length - 1];

if (lastChange.rangeLength === 0 && isSingleLine(lastChange.text)) {
// Insertion case
const lastCharacter = lastChange.text[lastChange.text.length - 1];
if (isEnabled['autoQuote'] && lastCharacter === '=') {
doAutoInsert('autoQuote', document, lastChange);
} else if (isEnabled['autoClose'] && (lastCharacter === '>' || lastCharacter === '/')) {
doAutoInsert('autoClose', document, lastChange);
}
} else if (isEnabled['autoClose'] && lastChange.rangeLength > 0 && lastChange.text === '') {
// Deletion case: check if the character at the deletion position is now '>'
// This handles the case where '/' is removed from <tag/> leaving <tag>
const position = lastChange.range.start;
const lineText = document.lineAt(position.line).text;
if (position.character < lineText.length) {
const charAtCursor = lineText.charAt(position.character);
if (charAtCursor === '>') {
doAutoInsertAtPosition('autoClose', document, new Position(position.line, position.character + 1));
}
}
}
}

function isSingleLine(text: string): boolean {
return !/\n/.test(text);
}

function doAutoInsert(kind: 'autoQuote' | 'autoClose', document: TextDocument, lastChange: TextDocumentContentChangeEvent) {
const rangeStart = lastChange.range.start;
const position = new Position(rangeStart.line, rangeStart.character + lastChange.text.length);
doAutoInsertAtPosition(kind, document, position);
}

function doAutoInsertAtPosition(kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) {
const version = document.version;
timeout = setTimeout(() => {
provider(kind, document, position).then(result => {
const text = result?.snippet;
if (text && isEnabled[kind]) {
const activeEditor = window.activeTextEditor;
if (activeEditor) {
const activeDocument = activeEditor.document;
if (document === activeDocument && activeDocument.version === version) {
const selections = activeEditor.selections;
if (selections.length > 1 && selections.some(s => s.active.isEqual(position))) {
activeEditor.insertSnippet(new SnippetString(text), selections.map(s => s.active));
} else {
activeEditor.insertSnippet(new SnippetString(text), getReplaceLocation(result.range, position));
}
}
}
}
}, (_reason: any) => {

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 125 in src/client/autoInsertion.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type
console.log('xml/autoInsert request has been cancelled');
});
timeout = undefined;
}, 100);
}

return Disposable.from(...disposables);
}

function getReplaceLocation(range: Range | undefined, position: Position): Range | Position {
if (range != null) {
return new Range(
new Position(range.start.line, range.start.character),
new Position(range.end.line, range.end.character)
);
}
return position;
}
27 changes: 18 additions & 9 deletions src/client/xmlClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TelemetryEvent } from '@redhat-developer/vscode-redhat-telemetry/lib';
import { commands, ExtensionContext, extensions, Position, TextDocument, TextEditor, Uri, window, workspace } from 'vscode';
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, DocumentFilter, DocumentSelector, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, State, TextDocumentPositionParams } from "vscode-languageclient";
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, DocumentFilter, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, State } from "vscode-languageclient";
import { Executable, LanguageClient } from 'vscode-languageclient/node';
import { XMLFileAssociation } from '../api/xmlExtensionApi';
import { registerClientServerCommands } from '../commands/registerCommands';
Expand All @@ -13,7 +13,7 @@
import * as Telemetry from '../telemetry';
import { ClientErrorHandler } from './clientErrorHandler';
import { getLanguageParticipants } from './languageParticipants';
import { activateTagClosing, AutoCloseResult } from './tagClosing';
import { activateAutoInsertion, AutoInsertResult } from './autoInsertion';

const languageParticipants = getLanguageParticipants();

Expand All @@ -26,14 +26,20 @@
return l as string;
});

const ExecuteClientCommandRequest: RequestType<ExecuteCommandParams, any, void> = new RequestType('xml/executeClientCommand');

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 29 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type

const TagCloseRequest: RequestType<TextDocumentPositionParams, AutoCloseResult, any> = new RequestType('xml/closeTag');
interface AutoInsertParams {
kind: 'autoQuote' | 'autoClose';
textDocument: { uri: string };
position: { line: number; character: number };
}

const AutoInsertRequest: RequestType<AutoInsertParams, AutoInsertResult, any> = new RequestType('xml/autoInsert');

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 37 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type

interface ActionableMessage {
severity: MessageType;
message: string;
data?: any;

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 42 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type
commands?: Command[];
}

Expand Down Expand Up @@ -80,13 +86,16 @@

registerClientServerCommands(context, languageClient);

// Setup autoCloseTags
const tagProvider = (document: TextDocument, position: Position) => {
const param = languageClient.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
const text = languageClient.sendRequest(TagCloseRequest, param);
return text;
// Setup auto-insertion (autoClose tags + autoQuote attributes)
const autoInsertProvider = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => {
const param: AutoInsertParams = {
kind,
textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document),
position: languageClient.code2ProtocolConverter.asPosition(position)
};
return languageClient.sendRequest(AutoInsertRequest, param);
};
context.subscriptions.push(activateTagClosing(tagProvider, { xml: true, xsl: true }, ServerCommandConstants.AUTO_CLOSE_TAGS));
context.subscriptions.push(activateAutoInsertion(autoInsertProvider, { xml: true, xsl: true }, ServerCommandConstants.AUTO_CLOSE_TAGS, ServerCommandConstants.AUTO_CREATE_QUOTES));

if (extensions.onDidChange) {// Theia doesn't support this API yet
context.subscriptions.push(extensions.onDidChange(() => {
Expand All @@ -97,7 +106,7 @@
// Copied from:
// https://github.com/redhat-developer/vscode-java/pull/1081/files
languageClient.onRequest(ConfigurationRequest.type, (params: ConfigurationParams) => {
const result: any[] = [];

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 109 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type
const activeEditor: TextEditor | undefined = window.activeTextEditor;
for (const item of params.items) {
if (activeEditor && activeEditor.document.uri.toString() === Uri.parse(item.scopeUri).toString()) {
Expand All @@ -122,7 +131,7 @@
}
}));

const onDidGrantWorkspaceTrust = (workspace as any).onDidGrantWorkspaceTrust;

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 134 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type
if (onDidGrantWorkspaceTrust !== undefined) {
context.subscriptions.push(onDidGrantWorkspaceTrust(() => {
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
Expand Down Expand Up @@ -198,7 +207,7 @@
show(notification.message, ...titles).then((selection) => {
for (const action of notification.commands) {
if (action.title === selection) {
const args: any[] = (action.arguments) ? action.arguments : [];

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-x86_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test-windows (win32) / smoke-test

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (linux-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-aarch_64) / smoke-test

Unexpected any. Specify a different type

Check warning on line 210 in src/client/xmlClient.ts

View workflow job for this annotation

GitHub Actions / matrix-smoke-test (osx-x86_64) / smoke-test

Unexpected any. Specify a different type
commands.executeCommand(action.command, ...args);
break;
}
Expand Down
5 changes: 5 additions & 0 deletions src/commands/serverCommandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import * as ClientCommandConstants from "./clientCommandConstants";
*/
export const AUTO_CLOSE_TAGS = 'xml.completion.autoCloseTags';

/**
* Auto create quotes after attribute =
*/
export const AUTO_CREATE_QUOTES = 'xml.completion.autoCreateQuotes';

/**
* Commands to revalidate files with an LSP command on the XML Language Server
*/
Expand Down
Loading