diff --git a/package-lock.json b/package-lock.json index 61d745d..02c1447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "clang-format", - "version": "1.9.0", + "version": "99.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -374,7 +374,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -1384,6 +1384,11 @@ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, + "shlex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/shlex/-/shlex-2.0.1.tgz", + "integrity": "sha512-XrweL4ORG4Y9E9VvqsAjQ0Dhr4oW3BP6d4IceL7EnRA5Y7KU+4wCFg9HMhFm7SxhVswKu9emOvblBr7J17Vqtg==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 00fc7e0..4017791 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "clang-format", "displayName": "Clang-Format", "description": "Use Clang-Format in Visual Studio Code", - "version": "1.9.0", + "version": "99.9.0", "publisher": "xaver", "engines": { "vscode": "^1.1.0" @@ -12,7 +12,8 @@ "theme": "dark" }, "dependencies": { - "sax": "^1.2.1" + "sax": "^1.2.1", + "shlex": "^2.0.1" }, "categories": [ "Formatters" @@ -28,6 +29,7 @@ ], "main": "./out/src/extension", "activationEvents": [ + "onCommand:clang-format.formatSelection", "onLanguage:cpp", "onLanguage:c", "onLanguage:objective-c", @@ -42,6 +44,18 @@ "onLanguage:cuda" ], "contributes": { + "commands": [ + { + "command": "clang-format.formatSelection", + "title": "Format selection or around cursor", + "category": "Clang-Format" + }, + { + "command": "clang-format.formatSelectionAlternate", + "title": "Format selection or around cursor (alternate)", + "category": "Clang-Format" + } + ], "configuration": { "type": "object", "title": "Clang-Format configuration", @@ -71,10 +85,15 @@ "default": "", "description": "clang-format fallback style for C++, left empty to use clang-format.style" }, + "clang-format.language.cpp.alternate.style": { + "type": "string", + "default": "", + "description": "clang-format fallback style for C++, left empty to use clang-format.style" + }, "clang-format.language.cpp.fallbackStyle": { "type": "string", "default": "", - "description": "clang-format fallback style for C++, left empty to use clang-format.fallbackStyle" + "description": "clang-format alternate style for C++" }, "clang-format.language.c.enable": { "type": "boolean", @@ -86,6 +105,11 @@ "default": "", "description": "clang-format fallback style for C, left empty to use clang-format.style" }, + "clang-format.language.c.alternate.style": { + "type": "string", + "default": "", + "description": "clang-format alternate style for C" + }, "clang-format.language.c.fallbackStyle": { "type": "string", "default": "", diff --git a/src/clangMode.ts b/src/clangMode.ts index 9ae16a7..e93d631 100644 --- a/src/clangMode.ts +++ b/src/clangMode.ts @@ -14,4 +14,6 @@ for (let l of ['cpp', 'c', 'objective-c', 'objective-cpp', 'java', 'javascript', } } +export const LANGUAGES: string[] = languages; + export const MODES: vscode.DocumentFilter[] = languages.map((language) => ({language, scheme: 'file'})); \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index a506993..bc57567 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,12 +2,24 @@ import * as vscode from 'vscode'; import cp = require('child_process'); import path = require('path'); import {MODES, - ALIAS} from './clangMode'; + ALIAS, + LANGUAGES} from './clangMode'; import {getBinPath} from './clangPath'; import sax = require('sax'); +import * as shlex from 'shlex'; export let outputChannel = vscode.window.createOutputChannel('Clang-Format'); +interface FormatResult { + edits: vscode.TextEdit[]; + newCursorPos: vscode.Position; +} + +interface EditInfo { + offset: number; + length: number; +} + export class ClangDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider { private defaultConfigure = { executable: 'clang-format', @@ -17,15 +29,46 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma }; public provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): Thenable { - return this.doFormatDocument(document, null, options, token); + return this.doFormatDocument(document, null, options, token).then((result) => { + return result.edits; + }); } public provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Thenable { - return this.doFormatDocument(document, range, options, token); + return this.doFormatDocument(document, range, options, token).then((result) => { + return result.edits; + }); + } + + public formatSelection(alternate = false): void { + let editor = vscode.window.activeTextEditor; + let document = editor.document; + + if (LANGUAGES.indexOf(document.languageId) === -1) { + vscode.commands.executeCommand('editor.action.formatSelection'); + return; + } + + this.doFormatDocument(editor.document, editor.selection, undefined, undefined, alternate).then( + (result) => { + editor.edit((editBuilder) => { + for (let edit of result.edits) + editBuilder.replace(edit.range, edit.newText); + }).then((didEdits) => { + if (!didEdits) + vscode.window.showErrorMessage('Could not apply formatting edits'); + else + editor.selection = new vscode.Selection(result.newCursorPos, result.newCursorPos); + }); + } + ); } - private getEdits(document: vscode.TextDocument, xml: string, codeContent: string): Thenable { + private getEdits(document: vscode.TextDocument, xml: string, + codeContent: string): Thenable { return new Promise((resolve, reject) => { + let newCursorPosInfo = {offset: 0, length: 0}; + let options = { trim: false, normalize: false, @@ -61,6 +104,8 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma return editInfo; }; + let onNewCursorPos = false; + parser.onerror = (err) => { reject(err.message); }; @@ -74,6 +119,10 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma case 'replacements': return; + case 'cursor': + onNewCursorPos = true; + break; + case 'replacement': currentEdit = { length: parseInt(tag.attributes['length'].toString()), @@ -90,12 +139,18 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma }; parser.ontext = (text) => { - if (!currentEdit) { return; } - - currentEdit.text = text; + if (onNewCursorPos) { + newCursorPosInfo.offset = parseInt(text); + byteToOffset(newCursorPosInfo); + } else if (currentEdit) + currentEdit.text = text; }; parser.onclosetag = (tagName) => { + if (onNewCursorPos) { + onNewCursorPos = false; + return; + } if (!currentEdit) { return; } let start = document.positionAt(currentEdit.offset); @@ -108,7 +163,7 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma }; parser.onend = () => { - resolve(edits); + resolve({edits, newCursorPos: document.positionAt(newCursorPosInfo.offset)}); }; parser.write(xml); @@ -138,8 +193,9 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma return ALIAS[document.languageId] || document.languageId; } - private getStyle(document: vscode.TextDocument) { - let ret = vscode.workspace.getConfiguration('clang-format').get(`language.${this.getLanguage(document)}.style`); + private getStyle(document: vscode.TextDocument, alternate = false) { + const styleOpt = alternate ? 'alternate.style' : 'style'; + let ret = vscode.workspace.getConfiguration('clang-format').get(`language.${this.getLanguage(document)}.${styleOpt}`); if (ret.trim()) { return ret.trim(); } @@ -174,7 +230,7 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma return assumedFilename; } - private doFormatDocument(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Thenable { + private doFormatDocument(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken, alternate = false): Thenable { return new Promise((resolve, reject) => { let filename = document.fileName; @@ -183,7 +239,7 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma let formatArgs = [ '-output-replacements-xml', - `-style=${this.getStyle(document)}`, + `-style=${this.getStyle(document, alternate)}`, `-fallback-style=${this.getFallbackStyle(document)}`, `-assume-filename=${this.getAssumedFilename(document)}` ]; @@ -198,6 +254,8 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma offset = Buffer.byteLength(codeContent.substr(0, offset), 'utf8'); formatArgs.push(`-offset=${offset}`, `-length=${length}`); + if (length === 0) + formatArgs.push(`-cursor=${offset}`); } let workingPath = vscode.workspace.rootPath; @@ -207,6 +265,9 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma let stdout = ''; let stderr = ''; + const argsString = formatArgs.map(shlex.quote).join(' '); + outputChannel.clear(); + outputChannel.appendLine(`Calling with arguments: ${argsString}`) let child = cp.spawn(formatCommandBinPath, formatArgs, { cwd: workingPath }); child.stdin.end(codeContent); child.stdout.on('data', chunk => stdout += chunk); @@ -221,13 +282,12 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma child.on('close', code => { try { if (stderr.length != 0) { - outputChannel.show(); - outputChannel.clear(); outputChannel.appendLine(stderr); - return reject('Cannot format due to syntax errors.'); } if (code != 0) { + outputChannel.show(); + outputChannel.appendLine(`Process exited with code ${code}`); return reject(); } @@ -245,10 +305,6 @@ export class ClangDocumentFormattingEditProvider implements vscode.DocumentForma } }); } - - public formatDocument(document: vscode.TextDocument): Thenable { - return this.doFormatDocument(document, null, null, null); - } } let diagnosticCollection: vscode.DiagnosticCollection; @@ -263,4 +319,15 @@ export function activate(ctx: vscode.ExtensionContext): void { ctx.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(mode, formatter)); availableLanguages[mode.language] = true; }); + + ctx.subscriptions.push( + vscode.commands.registerCommand('clang-format.formatSelection', + () => { + formatter.formatSelection(); + })); + ctx.subscriptions.push( + vscode.commands.registerCommand('clang-format.formatSelectionAlternate', + () => { + formatter.formatSelection(true /* alternate */); + })); }