Skip to content

Commit

Permalink
Wrap selection in XML element
Browse files Browse the repository at this point in the history
Fixes #794

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr authored and datho7561 committed Dec 8, 2022
1 parent 26e6c90 commit a833f7d
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 177 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This VS Code extension provides support for creating and editing XML documents,
| enabled by default | requires additional configuration to enable |

* [RelaxNG (experimental) support](https://github.com/redhat-developer/vscode-xml/blob/main/docs/Features/RelaxNGFeatures.md#relaxng-features) (since v0.22.0)
* [Surround with Tags, Comments, CDATA](https://github.com/redhat-developer/vscode-xml/blob/main/docs/Refactor.md#refactor) (since v0.23.0)
* Syntax error reporting
* General code completion
* [Auto-close tags](https://github.com/redhat-developer/vscode-xml/blob/main/docs/Features/XMLFeatures.md#xml-tag-auto-close)
Expand Down
25 changes: 25 additions & 0 deletions docs/Refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Refactor

## Surround with Tags (Wrap)

This refactor command gives the capability to select an XML content and surround it with a given tag. To execute this command you can:

* use command palette (`Ctrl+P`) and type `Surround`

![Surround with Tags](images/Refactor/SurroundWithTags.gif)

* use contextual menu

If you prefer using keyboard to process `Surround with Tags (Wrap)`,you need to associate this command with a keybinding. See [Keyboard Shortcuts editor](https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-editor) for more informations.

## Surround with Comments

Similar to `Surround with Tags (Wrap)`, you can comment out the selected XML content:

![Surround with Tags](images/Refactor/SurroundWithComments.gif)

## Surround with CDATA

Similar to `Surround with Tags (Wrap)`, you can surround the selected XML content with CDATA:

![Surround with Tags](images/Refactor/SurroundWithCDATA.gif)
Binary file added docs/images/Refactor/SurroundWithCDATA.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/Refactor/SurroundWithComments.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/Refactor/SurroundWithTags.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
310 changes: 155 additions & 155 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 55 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -684,17 +684,69 @@
"command": "xml.restart.language.server",
"title": "Restart XML Language Server",
"category": "XML"
},
{
"command": "xml.refactor.surround.with.tags",
"title": "Surround with Tags (Wrap)",
"category": "XML"
},
{
"command": "xml.refactor.surround.with.comments",
"title": "Surround with Comments",
"category": "XML"
},
{
"command": "xml.refactor.surround.with.cdata",
"title": "Surround with CDATA",
"category": "XML"
}
],
"menus": {
"commandPalette": [
{
"command": "xml.validation.current.file",
"when": "editorLangId == xml"
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady"
},
{
"command": "xml.validation.all.files",
"when": "XMLLSReady"
},
{
"command": "xml.command.bind.grammar",
"when": "resourceFilename =~ /xml/ && editorIsOpen"
"when": "resourceFilename =~ /xml/ && editorIsOpen && XMLLSReady"
},
{
"command": "xml.restart.language.server",
"when": "XMLLSReady"
},
{
"command": "xml.refactor.surround.with.tags",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady"
},
{
"command": "xml.refactor.surround.with.comments",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady"
},
{
"command": "xml.refactor.surround.with.cdata",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady"
}
],
"editor/context": [
{
"command": "xml.refactor.surround.with.tags",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
"group": "1_modification"
},
{
"command": "xml.refactor.surround.with.comments",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
"group": "1_modification"
},
{
"command": "xml.refactor.surround.with.cdata",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
"group": "1_modification"
}
]
},
Expand All @@ -705,4 +757,4 @@
}
]
}
}
}
29 changes: 18 additions & 11 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, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, TextDocumentPositionParams } from "vscode-languageclient";
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, DocumentSelector, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, State, TextDocumentPositionParams } from "vscode-languageclient";
import { Executable, LanguageClient } from 'vscode-languageclient/node';
import { XMLFileAssociation } from '../api/xmlExtensionApi';
import { registerClientServerCommands } from '../commands/registerCommands';
Expand All @@ -14,6 +14,17 @@ import * as Telemetry from '../telemetry';
import { ClientErrorHandler } from './clientErrorHandler';
import { activateTagClosing, AutoCloseResult } from './tagClosing';

export const XML_SUPPORTED_LANGUAGE_IDS = ['xml', 'xsl', 'dtd', 'svg'];

const XML_DOCUMENT_SELECTOR: DocumentSelector =
XML_SUPPORTED_LANGUAGE_IDS
.map(langId => ({ scheme: 'file', language: langId }))
.concat(
XML_SUPPORTED_LANGUAGE_IDS
.map(langId => ({ scheme: 'untitled', language: langId }))
);


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

const TagCloseRequest: RequestType<TextDocumentPositionParams, AutoCloseResult, any> = new RequestType('xml/closeTag');
Expand All @@ -34,6 +45,11 @@ export async function startLanguageClient(context: ExtensionContext, executable:
const languageClientOptions: LanguageClientOptions = getLanguageClientOptions(logfile, externalXmlSettings, requirementsData, context);
languageClient = new LanguageClient('xml', 'XML Support', executable, languageClientOptions);

languageClient.onDidChangeState(e => {
// Notify that XML language client is started / stoped
commands.executeCommand('setContext', 'XMLLSReady', e.newState == State.Running);
});

languageClient.onTelemetry(async (e: TelemetryEvent) => {
if (e.name === Telemetry.SERVER_INITIALIZED_EVT) {
e.properties[Telemetry.SETTINGS_EVT] = {
Expand Down Expand Up @@ -120,16 +136,7 @@ function getLanguageClientOptions(
context: ExtensionContext): LanguageClientOptions {
return {
// Register the server for xml, xsl, dtd, svg
documentSelector: [
{ scheme: 'file', language: 'xml' },
{ scheme: 'file', language: 'xsl' },
{ scheme: 'file', language: 'dtd' },
{ scheme: 'file', language: 'svg' },
{ scheme: 'untitled', language: 'xml' },
{ scheme: 'untitled', language: 'xsl' },
{ scheme: 'untitled', language: 'dtd' },
{ scheme: 'untitled', language: 'svg' }
],
documentSelector: XML_DOCUMENT_SELECTOR,
revealOutputChannelOn: RevealOutputChannelOn.Never,
//wrap with key 'settings' so it can be handled same a DidChangeConfiguration
initializationOptions: {
Expand Down
11 changes: 10 additions & 1 deletion src/commands/clientCommandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,13 @@ export const EXECUTE_WORKSPACE_COMMAND = 'xml.workspace.executeCommand';
/**
* Command to restart connection to language server.
*/
export const RESTART_LANGUAGE_SERVER = 'xml.restart.language.server';
export const RESTART_LANGUAGE_SERVER = 'xml.restart.language.server';

/**
* Command to wrap element.
*/
export const REFACTOR_SURROUND_WITH_TAGS = 'xml.refactor.surround.with.tags';

export const REFACTOR_SURROUND_WITH_COMMENTS = 'xml.refactor.surround.with.comments';

export const REFACTOR_SURROUND_WITH_CDATA = 'xml.refactor.surround.with.cdata';
102 changes: 99 additions & 3 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from 'path';
import { commands, ConfigurationTarget, env, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, TextDocument, Uri, window, workspace, WorkspaceEdit } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentEdit, TextDocumentIdentifier } from "vscode-languageclient";
import * as vscode from 'vscode';
import { commands, ConfigurationTarget, env, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, SnippetString, TextDocument, Uri, window, workspace, WorkspaceEdit, Selection } from "vscode";
import { CancellationToken, ExecuteCommandParams, ExecuteCommandRequest, ReferencesRequest, TextDocumentEdit, TextDocumentIdentifier, TextEdit } from "vscode-languageclient";
import { LanguageClient } from 'vscode-languageclient/node';
import { markdownPreviewProvider } from "../markdownPreviewProvider";
import { DEBUG } from '../server/java/javaServerStarter';
Expand Down Expand Up @@ -29,6 +30,7 @@ export async function registerClientServerCommands(context: ExtensionContext, la

registerCodeLensReferencesCommands(context, languageClient);
registerValidationCommands(context);
registerRefactorCommands(context, languageClient);
registerAssociationCommands(context, languageClient);
registerRestartLanguageServerCommand(context, languageClient);

Expand Down Expand Up @@ -182,7 +184,7 @@ async function grammarAssociationCommand(documentURI: Uri, languageClient: Langu
if (!predefinedUrl || !predefinedUrl.startsWith('http')) {
predefinedUrl = '';
}
grammarURI = await window.showInputBox({title:'Fill with schema / grammar URL' , value:predefinedUrl});
grammarURI = await window.showInputBox({ title: 'Fill with schema / grammar URL', value: predefinedUrl });
} else {
// step 2.1: Open a dialog to select the XSD, DTD, RelaxNG file to bind.
const options: OpenDialogOptions = {
Expand Down Expand Up @@ -366,3 +368,97 @@ function registerRestartLanguageServerCommand(context: ExtensionContext, languag

}));
}

interface SurroundWithResponse {
start: TextEdit;
end: TextEdit;
}

class SurroundWithKind {

static readonly tags = 'tags';
static readonly comments = 'comments';
static readonly cdata = 'cdata';

}

/**
* Register commands used for refactoring XML files
*
* @param context the extension context
*/
function registerRefactorCommands(context: ExtensionContext, languageClient: LanguageClient) {

// Surround with Tags (Wrap)
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.REFACTOR_SURROUND_WITH_TAGS, async () => {
await surroundWith(SurroundWithKind.tags, languageClient);
}));

// Surround with Comments
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.REFACTOR_SURROUND_WITH_COMMENTS, async () => {
await surroundWith(SurroundWithKind.comments, languageClient);
}));


// Surround with CDATA
context.subscriptions.push(commands.registerCommand(ClientCommandConstants.REFACTOR_SURROUND_WITH_CDATA, async () => {
await surroundWith(SurroundWithKind.cdata, languageClient);
}));
}

async function surroundWith(surroundWithType: SurroundWithKind, languageClient: LanguageClient) {
const activeEditor = window.activeTextEditor;
if (!activeEditor) {
return;
}
const selection = activeEditor.selections[0];
if (!selection) {
return;
}

const uri = window.activeTextEditor.document.uri;
const identifier = TextDocumentIdentifier.create(uri.toString());
const range = languageClient.code2ProtocolConverter.asRange(selection);
const supportedSnippet: boolean = vscode.SnippetTextEdit ? true : false;

let result: SurroundWithResponse;
try {
result = await commands.executeCommand(ServerCommandConstants.REFACTOR_SURROUND_WITH, identifier, range, surroundWithType, supportedSnippet);
} catch (error) {
console.log(`Error while surround with : ${error}`);
}

if (!result) {
return;
}

const startTag = result.start.newText;
const endTag = result.end.newText;

if (supportedSnippet) {
// SnippetTextEdit is supported, uses snippet (with choice) to manage cursor.
const startRange = languageClient.protocol2CodeConverter.asRange(result.start.range);
const endRange = languageClient.protocol2CodeConverter.asRange(result.end.range);
const snippetEdits = [new vscode.SnippetTextEdit(startRange, new SnippetString(startTag)), new vscode.SnippetTextEdit(endRange, new SnippetString(endTag))];
const edit = new WorkspaceEdit();
edit.set(activeEditor.document.uri, snippetEdits);
await workspace.applyEdit(edit);
} else {
// SnippetTextEdit is not supported, update start / end tag
const startPos = languageClient.protocol2CodeConverter.asPosition(result.start.range.start);
const endPos = languageClient.protocol2CodeConverter.asPosition(result.end.range.start);
activeEditor.edit((selectedText) => {
selectedText.insert(startPos, startTag);
selectedText.insert(endPos, endTag);
})

if (surroundWithType === SurroundWithKind.tags) {
// Force the show of completion
const pos = languageClient.protocol2CodeConverter.asPosition(result.start.range.start);
const posAfterStartBracket = new Position(pos.line, pos.character + 1);
activeEditor.selections = [new Selection(posAfterStartBracket, posAfterStartBracket)];
commands.executeCommand("editor.action.triggerSuggest");
}
}

}
9 changes: 7 additions & 2 deletions src/commands/serverCommandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ export const ASSOCIATE_GRAMMAR_INSERT = "xml.associate.grammar.insert";
/**
* Command to check if the current XML document is bound to a grammar
*/
export const CHECK_BOUND_GRAMMAR = "xml.check.bound.grammar"
export const CHECK_BOUND_GRAMMAR = "xml.check.bound.grammar";

/**
* Command to check if a given file pattern matches any file on the workspace
*/
export const CHECK_FILE_PATTERN = "xml.check.file.pattern"
export const CHECK_FILE_PATTERN = "xml.check.file.pattern";

/**
* Command to surround with tags, comments, cdata
*/
export const REFACTOR_SURROUND_WITH = "xml.refactor.surround.with";
6 changes: 4 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { ExtensionContext, Uri, extensions, languages } from "vscode";
import { ExtensionContext, Uri, extensions, languages, commands } from "vscode";
import { Executable, LanguageClient } from 'vscode-languageclient/node';
import { XMLExtensionApi } from './api/xmlExtensionApi';
import { getXmlExtensionApiImplementation } from './api/xmlExtensionApiImplementation';
import { cleanUpHeapDumps } from './client/clientErrorHandler';
import { getIndentationRules } from './client/indentation';
import { startLanguageClient } from './client/xmlClient';
import { startLanguageClient, XML_SUPPORTED_LANGUAGE_IDS } from './client/xmlClient';
import { registerClientOnlyCommands } from './commands/registerCommands';
import { collectXmlJavaExtensions } from './plugin';
import * as requirements from './server/requirements';
Expand All @@ -38,6 +38,8 @@ export async function activate(context: ExtensionContext): Promise<XMLExtensionA

languages.setLanguageConfiguration('xml', getIndentationRules());
languages.setLanguageConfiguration('xsl', getIndentationRules());
// Register in the context 'xml.supportedLanguageIds' to use it in command when condition in package.json
commands.executeCommand('setContext', 'xml.supportedLanguageIds', XML_SUPPORTED_LANGUAGE_IDS);

let requirementsData: requirements.RequirementsData;
try {
Expand Down

0 comments on commit a833f7d

Please sign in to comment.