From e09be9facd5fdbbf43341a4680b7663779f8ce43 Mon Sep 17 00:00:00 2001 From: Chris Stead Date: Wed, 18 Aug 2021 15:12:16 -0700 Subject: [PATCH] F Added indent and outdent --- extension.js | 12 +++++- modules/action-setup.js | 22 +++++++--- modules/commands/indent/indent-action.js | 38 ++++++++++++++-- modules/commands/outdent/outdent-action.js | 48 +++++++++++++++++++++ modules/edit-utils/SourceEdit.js | 33 ++++++++++++++ modules/edit-utils/code-range-transforms.js | 30 +++++++++++++ modules/edit-utils/textEditTransforms.js | 40 +++++++++++++++++ modules/source-utilities.js | 36 ++++++++++++++++ modules/transforms/selection-to-position.js | 4 +- modules/validatorService.js | 15 +++++++ modules/vscode-service.js | 19 ++++++-- package.json | 22 +++++++++- 12 files changed, 300 insertions(+), 19 deletions(-) create mode 100644 modules/commands/outdent/outdent-action.js create mode 100644 modules/edit-utils/SourceEdit.js create mode 100644 modules/edit-utils/code-range-transforms.js create mode 100644 modules/edit-utils/textEditTransforms.js create mode 100644 modules/source-utilities.js create mode 100644 modules/validatorService.js diff --git a/extension.js b/extension.js index df5867b..ba0c969 100644 --- a/extension.js +++ b/extension.js @@ -4,13 +4,21 @@ function activate(context) { const formatDocument = () => vscode.commands.executeCommand("editor.action.formatDocument"); - let disposable = vscode.commands.registerCommand('cmstead.snipkit.indent', function () { + let disposable1 = vscode.commands.registerCommand('cmstead.snipkit.indent', function () { require('./modules/commands/indent/indent-action') .indent() .then(formatDocument); }); - context.subscriptions.push(disposable); + context.subscriptions.push(disposable1); + + let disposable2 = vscode.commands.registerCommand('cmstead.snipkit.outdent', function () { + require('./modules/commands/outdent/outdent-action') + .outdent() + .then(formatDocument); + }); + + context.subscriptions.push(disposable2); } function deactivate() { } diff --git a/modules/action-setup.js b/modules/action-setup.js index 77362be..45bda6f 100644 --- a/modules/action-setup.js +++ b/modules/action-setup.js @@ -2,20 +2,28 @@ const { buildSelectionPath } = require('./selection-path'); const { parseSource } = require('./parser/parser'); const { transformSelectionToPosition } = require('./transforms/selection-to-position'); -const vscode = require('./vscode-service').getVsCode(); +const vscode = require('./vscode-service').getVscode(); function actionSetup() { const activeTextEditor = vscode.window.activeTextEditor; const location = transformSelectionToPosition(activeTextEditor.selection); const source = activeTextEditor.document.getText(); - const ast = parseSource(source); - const selectionPath = buildSelectionPath(ast.child, location); - return { - source, - ast, - selectionPath + try { + const ast = parseSource(source); + const selectionPath = buildSelectionPath(ast.child, location); + + return { + activeTextEditor, + source, + + location, + ast, + selectionPath + } + } catch (_) { + throw new Error('Unable to interpret JSON source; SnipKit cannot start') } } diff --git a/modules/commands/indent/indent-action.js b/modules/commands/indent/indent-action.js index 7705b56..7e2cd3d 100644 --- a/modules/commands/indent/indent-action.js +++ b/modules/commands/indent/indent-action.js @@ -1,12 +1,44 @@ const { asyncActionSetup } = require("../../action-setup"); +const { getNewSourceEdit } = require("../../edit-utils/SourceEdit"); +const { transformLocationToRange } = require("../../edit-utils/textEditTransforms"); +const { getSourceSelection } = require("../../source-utilities"); +const { validateUserInput } = require("../../validatorService"); function indent() { + let actionSetup = null; + let sourceSelection = null; + let indentedSelection = null; return asyncActionSetup() - .then(({selectionPath}) => { - console.log(selectionPath); + .then((newActionSetup) => { + actionSetup = newActionSetup; }) - .catch(function(error){ + .then(() => getSourceSelection(actionSetup.source, actionSetup.location)) + .then((sourceSelection) => validateUserInput({ + value: sourceSelection, + validator: sourceSelection => + sourceSelection + .split(/\r?\n/g) + .reduce((result, line) => + result && /^\s*"?/.test(line)), + message: "Invalid body selection: not all lines begin with an open quote" + })) + .then((newSourceSelection) => sourceSelection = newSourceSelection) + + .then(() => indentedSelection = sourceSelection + .split(/\r?\n/g) + .map((line) => line.replace(/^(\s*)"/, '$1\"\\t')) + .join('\n')) + + .then(() => { + const replacementRange = transformLocationToRange(actionSetup.location); + + return getNewSourceEdit() + .addReplacementEdit(replacementRange, indentedSelection) + .applyEdit() + }) + + .catch(function (error) { console.log(error); }); } diff --git a/modules/commands/outdent/outdent-action.js b/modules/commands/outdent/outdent-action.js new file mode 100644 index 0000000..7f1c7f3 --- /dev/null +++ b/modules/commands/outdent/outdent-action.js @@ -0,0 +1,48 @@ +const { asyncActionSetup } = require("../../action-setup"); +const { getNewSourceEdit } = require("../../edit-utils/SourceEdit"); +const { transformLocationToRange } = require("../../edit-utils/textEditTransforms"); +const { getSourceSelection } = require("../../source-utilities"); +const { validateUserInput } = require("../../validatorService"); + +function outdent() { + let actionSetup = null; + let sourceSelection = null; + let indentedSelection = null; + return asyncActionSetup() + .then((newActionSetup) => { + actionSetup = newActionSetup; + }) + + .then(() => getSourceSelection(actionSetup.source, actionSetup.location)) + .then((sourceSelection) => validateUserInput({ + value: sourceSelection, + validator: sourceSelection => + sourceSelection + .split(/\r?\n/g) + .reduce((result, line) => + result && /^\s*(")?/.test(line)), + message: "Invalid body selection: not all lines begin with an open quote" + })) + .then((newSourceSelection) => sourceSelection = newSourceSelection) + + .then(() => indentedSelection = sourceSelection + .split(/\r?\n/g) + .map((line) => line.replace(/^(\s*)("\\t)/, '$1\"')) + .join('\n')) + + .then(() => { + const replacementRange = transformLocationToRange(actionSetup.location); + + return getNewSourceEdit() + .addReplacementEdit(replacementRange, indentedSelection) + .applyEdit() + }) + + .catch(function (error) { + console.log(error); + }); +} + +module.exports = { + outdent +}; \ No newline at end of file diff --git a/modules/edit-utils/SourceEdit.js b/modules/edit-utils/SourceEdit.js new file mode 100644 index 0000000..ea24318 --- /dev/null +++ b/modules/edit-utils/SourceEdit.js @@ -0,0 +1,33 @@ +const { WorkspaceEdit, window, workspace } = require("../vscode-service").getVscode(); + +class SourceEdit{ + constructor(){ + this.workspaceEdit = new WorkspaceEdit(); + this.uri = window.activeTextEditor.document.uri; + } + + addInsertEdit(insertPosition, textToInsert) { + this.workspaceEdit.insert(this.uri, insertPosition, textToInsert); + + return this; + } + + addReplacementEdit(replacementRange, replacementText) { + this.workspaceEdit.replace(this.uri, replacementRange, replacementText); + + return this; + } + + applyEdit(){ + return workspace.applyEdit(this.workspaceEdit); + } +} + +function getNewSourceEdit() { + return new SourceEdit(); +} + +module.exports = { + getNewSourceEdit, + SourceEdit +}; \ No newline at end of file diff --git a/modules/edit-utils/code-range-transforms.js b/modules/edit-utils/code-range-transforms.js new file mode 100644 index 0000000..db67fc1 --- /dev/null +++ b/modules/edit-utils/code-range-transforms.js @@ -0,0 +1,30 @@ +function transformSelectionToLocation(selection) { + return { + start: { + line: selection.start.line + 1, + column: selection.start.character + }, + end: { + line: selection.end.line + 1, + column: selection.end.character + } + }; +} + +function transformLocationToSelection(location) { + return { + start: { + line: location.start.line - 1, + character: location.start.column + }, + end: { + line: location.end.line - 1, + character: location.end.column + } + }; +} + +module.exports = { + transformLocationToSelection, + transformSelectionToLocation +}; \ No newline at end of file diff --git a/modules/edit-utils/textEditTransforms.js b/modules/edit-utils/textEditTransforms.js new file mode 100644 index 0000000..11fc999 --- /dev/null +++ b/modules/edit-utils/textEditTransforms.js @@ -0,0 +1,40 @@ +const vscodeService = require('../vscode-service'); + +const { + Position, + Range, +} = vscodeService.getVscode(); + + +function transformLocationPartToPosition({ line, column }) { + return new Position(line - 1, column); +} + + +function transformLocationToRange({ start, end }) { + return new Range( + transformLocationPartToPosition(start), + transformLocationPartToPosition(end) + ); +} + + +function buildEditLocations({ + actionSetup: { location: selectionLocation }, + extractionLocation +}) { + const extractionLocationStart = extractionLocation.start; + + return { + extractionPosition: transformLocationPartToPosition(extractionLocationStart), + replacementRange: transformLocationToRange(selectionLocation) + } + +} + + +module.exports = { + buildEditLocations, + transformLocationPartToPosition, + transformLocationToRange +}; \ No newline at end of file diff --git a/modules/source-utilities.js b/modules/source-utilities.js new file mode 100644 index 0000000..ed482cc --- /dev/null +++ b/modules/source-utilities.js @@ -0,0 +1,36 @@ +function locationToSourceSelection({ start, end }) { + return { + startLine: start.line - 1, + endLine: end.line - 1, + startColumn: start.column, + endColumn: end.column + }; +} + +function getSourceSelection(sourceCode, location) { + const { + startLine, + endLine, + startColumn, + endColumn + } = locationToSourceSelection(location); + + const selectedLines = sourceCode.split('\n').slice(startLine, endLine + 1); + + if (selectedLines.length === 1) { + return selectedLines[0].slice(startColumn, endColumn) + } else { + const lastIndex = selectedLines.length - 1; + + selectedLines[0] = selectedLines[0].slice(startColumn); + selectedLines[lastIndex] = selectedLines[lastIndex].slice(0, endColumn); + + return selectedLines.join('\n'); + } + +} + +module.exports = { + getSourceSelection, + locationToSourceSelection +}; \ No newline at end of file diff --git a/modules/transforms/selection-to-position.js b/modules/transforms/selection-to-position.js index 267da8c..e4b6727 100644 --- a/modules/transforms/selection-to-position.js +++ b/modules/transforms/selection-to-position.js @@ -1,11 +1,11 @@ function transformSelectionToPosition(selection) { return { start: { - line: selection.start.line, + line: selection.start.line + 1, column: selection.start.character + 1 }, end: { - line: selection.end.line, + line: selection.end.line + 1, column: selection.end.character + 1 } }; diff --git a/modules/validatorService.js b/modules/validatorService.js new file mode 100644 index 0000000..8f76875 --- /dev/null +++ b/modules/validatorService.js @@ -0,0 +1,15 @@ +function validateUserInput({ + value, + validator = () => false, + message = 'Unable to validate user input' +}) { + if (typeof value === 'undefined' || !validator(value)) { + throw new Error(message); + } + + return value; +} + +module.exports = { + validateUserInput +}; \ No newline at end of file diff --git a/modules/vscode-service.js b/modules/vscode-service.js index c2f901a..b9919c9 100644 --- a/modules/vscode-service.js +++ b/modules/vscode-service.js @@ -1,7 +1,18 @@ -function getVsCode() { - return require('vscode'); +let vscode = null; + +function setVscodeInstance(vscodeInstance) { + vscode = vscodeInstance; +} + +function getVscode() { + if(vscode === null) { + vscode = require('vscode'); + } + + return vscode; } module.exports = { - getVsCode -} \ No newline at end of file + setVscodeInstance, + getVscode +}; \ No newline at end of file diff --git a/package.json b/package.json index 97495eb..6c77a9c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "public": true, "activationEvents": [ - "onCommand:cmstead.snipkit.indent" + "onCommand:cmstead.snipkit.indent", + "onCommand:cmstead.snipkit.outdent" ], "contributes": { "commands": [ @@ -41,6 +42,25 @@ "command": "cmstead.snipkit.indent", "title": "SnipKit: Indent Lines", "description": "Indent selected body lines" + }, + { + "command": "cmstead.snipkit.outdent", + "title": "SnipKit: Outdent Lines", + "description": "Outdent selected body lines" + } + ], + "keybindings": [ + { + "command": "cmstead.snipkit.indent", + "key": "ctrl+alt+]", + "mac": "cmd+alt+]", + "when": "editorHasSelection" + }, + { + "command": "cmstead.snipkit.outdent", + "key": "ctrl+alt+[", + "mac": "cmd+alt+[", + "when": "editorHasSelection" } ], "snippets": [