123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { DisposableStore } from '../../../base/common/lifecycle.js';
- import * as strings from '../../../base/common/strings.js';
- import { EditorAction, registerEditorAction, registerEditorContribution } from '../../browser/editorExtensions.js';
- import { ShiftCommand } from '../../common/commands/shiftCommand.js';
- import { EditOperation } from '../../common/core/editOperation.js';
- import { Range } from '../../common/core/range.js';
- import { Selection } from '../../common/core/selection.js';
- import { EditorContextKeys } from '../../common/editorContextKeys.js';
- import { TextModel } from '../../common/model/textModel.js';
- import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
- import { IModelService } from '../../common/services/modelService.js';
- import * as indentUtils from './indentUtils.js';
- import * as nls from '../../../nls.js';
- import { IQuickInputService } from '../../../platform/quickinput/common/quickInput.js';
- export function getReindentEditOperations(model, startLineNumber, endLineNumber, inheritedIndent) {
- if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
- // Model is empty
- return [];
- }
- const indentationRules = LanguageConfigurationRegistry.getIndentationRules(model.getLanguageId());
- if (!indentationRules) {
- return [];
- }
- endLineNumber = Math.min(endLineNumber, model.getLineCount());
- // Skip `unIndentedLinePattern` lines
- while (startLineNumber <= endLineNumber) {
- if (!indentationRules.unIndentedLinePattern) {
- break;
- }
- let text = model.getLineContent(startLineNumber);
- if (!indentationRules.unIndentedLinePattern.test(text)) {
- break;
- }
- startLineNumber++;
- }
- if (startLineNumber > endLineNumber - 1) {
- return [];
- }
- const { tabSize, indentSize, insertSpaces } = model.getOptions();
- const shiftIndent = (indentation, count) => {
- count = count || 1;
- return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
- };
- const unshiftIndent = (indentation, count) => {
- count = count || 1;
- return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
- };
- let indentEdits = [];
- // indentation being passed to lines below
- let globalIndent;
- // Calculate indentation for the first line
- // If there is no passed-in indentation, we use the indentation of the first line as base.
- let currentLineText = model.getLineContent(startLineNumber);
- let adjustedLineContent = currentLineText;
- if (inheritedIndent !== undefined && inheritedIndent !== null) {
- globalIndent = inheritedIndent;
- let oldIndentation = strings.getLeadingWhitespace(currentLineText);
- adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
- if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
- globalIndent = unshiftIndent(globalIndent);
- adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
- }
- if (currentLineText !== adjustedLineContent) {
- indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces)));
- }
- }
- else {
- globalIndent = strings.getLeadingWhitespace(currentLineText);
- }
- // idealIndentForNextLine doesn't equal globalIndent when there is a line matching `indentNextLinePattern`.
- let idealIndentForNextLine = globalIndent;
- if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
- idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
- globalIndent = shiftIndent(globalIndent);
- }
- else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
- idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
- }
- startLineNumber++;
- // Calculate indentation adjustment for all following lines
- for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
- let text = model.getLineContent(lineNumber);
- let oldIndentation = strings.getLeadingWhitespace(text);
- let adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length);
- if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
- idealIndentForNextLine = unshiftIndent(idealIndentForNextLine);
- globalIndent = unshiftIndent(globalIndent);
- }
- if (oldIndentation !== idealIndentForNextLine) {
- indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
- }
- // calculate idealIndentForNextLine
- if (indentationRules.unIndentedLinePattern && indentationRules.unIndentedLinePattern.test(text)) {
- // In reindent phase, if the line matches `unIndentedLinePattern` we inherit indentation from above lines
- // but don't change globalIndent and idealIndentForNextLine.
- continue;
- }
- else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
- globalIndent = shiftIndent(globalIndent);
- idealIndentForNextLine = globalIndent;
- }
- else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
- idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
- }
- else {
- idealIndentForNextLine = globalIndent;
- }
- }
- return indentEdits;
- }
- export class IndentationToSpacesAction extends EditorAction {
- constructor() {
- super({
- id: IndentationToSpacesAction.ID,
- label: nls.localize('indentationToSpaces', "Convert Indentation to Spaces"),
- alias: 'Convert Indentation to Spaces',
- precondition: EditorContextKeys.writable
- });
- }
- run(accessor, editor) {
- let model = editor.getModel();
- if (!model) {
- return;
- }
- let modelOpts = model.getOptions();
- let selection = editor.getSelection();
- if (!selection) {
- return;
- }
- const command = new IndentationToSpacesCommand(selection, modelOpts.tabSize);
- editor.pushUndoStop();
- editor.executeCommands(this.id, [command]);
- editor.pushUndoStop();
- model.updateOptions({
- insertSpaces: true
- });
- }
- }
- IndentationToSpacesAction.ID = 'editor.action.indentationToSpaces';
- export class IndentationToTabsAction extends EditorAction {
- constructor() {
- super({
- id: IndentationToTabsAction.ID,
- label: nls.localize('indentationToTabs', "Convert Indentation to Tabs"),
- alias: 'Convert Indentation to Tabs',
- precondition: EditorContextKeys.writable
- });
- }
- run(accessor, editor) {
- let model = editor.getModel();
- if (!model) {
- return;
- }
- let modelOpts = model.getOptions();
- let selection = editor.getSelection();
- if (!selection) {
- return;
- }
- const command = new IndentationToTabsCommand(selection, modelOpts.tabSize);
- editor.pushUndoStop();
- editor.executeCommands(this.id, [command]);
- editor.pushUndoStop();
- model.updateOptions({
- insertSpaces: false
- });
- }
- }
- IndentationToTabsAction.ID = 'editor.action.indentationToTabs';
- export class ChangeIndentationSizeAction extends EditorAction {
- constructor(insertSpaces, opts) {
- super(opts);
- this.insertSpaces = insertSpaces;
- }
- run(accessor, editor) {
- const quickInputService = accessor.get(IQuickInputService);
- const modelService = accessor.get(IModelService);
- let model = editor.getModel();
- if (!model) {
- return;
- }
- const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
- const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({
- id: n.toString(),
- label: n.toString(),
- // add description for tabSize value set in the configuration
- description: n === creationOpts.tabSize ? nls.localize('configuredTabSize', "Configured Tab Size") : undefined
- }));
- // auto focus the tabSize set for the current editor
- const autoFocusIndex = Math.min(model.getOptions().tabSize - 1, 7);
- setTimeout(() => {
- quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File"), activeItem: picks[autoFocusIndex] }).then(pick => {
- if (pick) {
- if (model && !model.isDisposed()) {
- model.updateOptions({
- tabSize: parseInt(pick.label, 10),
- insertSpaces: this.insertSpaces
- });
- }
- }
- });
- }, 50 /* quick input is sensitive to being opened so soon after another */);
- }
- }
- export class IndentUsingTabs extends ChangeIndentationSizeAction {
- constructor() {
- super(false, {
- id: IndentUsingTabs.ID,
- label: nls.localize('indentUsingTabs', "Indent Using Tabs"),
- alias: 'Indent Using Tabs',
- precondition: undefined
- });
- }
- }
- IndentUsingTabs.ID = 'editor.action.indentUsingTabs';
- export class IndentUsingSpaces extends ChangeIndentationSizeAction {
- constructor() {
- super(true, {
- id: IndentUsingSpaces.ID,
- label: nls.localize('indentUsingSpaces', "Indent Using Spaces"),
- alias: 'Indent Using Spaces',
- precondition: undefined
- });
- }
- }
- IndentUsingSpaces.ID = 'editor.action.indentUsingSpaces';
- export class DetectIndentation extends EditorAction {
- constructor() {
- super({
- id: DetectIndentation.ID,
- label: nls.localize('detectIndentation', "Detect Indentation from Content"),
- alias: 'Detect Indentation from Content',
- precondition: undefined
- });
- }
- run(accessor, editor) {
- const modelService = accessor.get(IModelService);
- let model = editor.getModel();
- if (!model) {
- return;
- }
- const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
- model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize);
- }
- }
- DetectIndentation.ID = 'editor.action.detectIndentation';
- export class ReindentLinesAction extends EditorAction {
- constructor() {
- super({
- id: 'editor.action.reindentlines',
- label: nls.localize('editor.reindentlines', "Reindent Lines"),
- alias: 'Reindent Lines',
- precondition: EditorContextKeys.writable
- });
- }
- run(accessor, editor) {
- let model = editor.getModel();
- if (!model) {
- return;
- }
- let edits = getReindentEditOperations(model, 1, model.getLineCount());
- if (edits.length > 0) {
- editor.pushUndoStop();
- editor.executeEdits(this.id, edits);
- editor.pushUndoStop();
- }
- }
- }
- export class ReindentSelectedLinesAction extends EditorAction {
- constructor() {
- super({
- id: 'editor.action.reindentselectedlines',
- label: nls.localize('editor.reindentselectedlines', "Reindent Selected Lines"),
- alias: 'Reindent Selected Lines',
- precondition: EditorContextKeys.writable
- });
- }
- run(accessor, editor) {
- let model = editor.getModel();
- if (!model) {
- return;
- }
- let selections = editor.getSelections();
- if (selections === null) {
- return;
- }
- let edits = [];
- for (let selection of selections) {
- let startLineNumber = selection.startLineNumber;
- let endLineNumber = selection.endLineNumber;
- if (startLineNumber !== endLineNumber && selection.endColumn === 1) {
- endLineNumber--;
- }
- if (startLineNumber === 1) {
- if (startLineNumber === endLineNumber) {
- continue;
- }
- }
- else {
- startLineNumber--;
- }
- let editOperations = getReindentEditOperations(model, startLineNumber, endLineNumber);
- edits.push(...editOperations);
- }
- if (edits.length > 0) {
- editor.pushUndoStop();
- editor.executeEdits(this.id, edits);
- editor.pushUndoStop();
- }
- }
- }
- export class AutoIndentOnPasteCommand {
- constructor(edits, initialSelection) {
- this._initialSelection = initialSelection;
- this._edits = [];
- this._selectionId = null;
- for (let edit of edits) {
- if (edit.range && typeof edit.text === 'string') {
- this._edits.push(edit);
- }
- }
- }
- getEditOperations(model, builder) {
- for (let edit of this._edits) {
- builder.addEditOperation(Range.lift(edit.range), edit.text);
- }
- let selectionIsSet = false;
- if (Array.isArray(this._edits) && this._edits.length === 1 && this._initialSelection.isEmpty()) {
- if (this._edits[0].range.startColumn === this._initialSelection.endColumn &&
- this._edits[0].range.startLineNumber === this._initialSelection.endLineNumber) {
- selectionIsSet = true;
- this._selectionId = builder.trackSelection(this._initialSelection, true);
- }
- else if (this._edits[0].range.endColumn === this._initialSelection.startColumn &&
- this._edits[0].range.endLineNumber === this._initialSelection.startLineNumber) {
- selectionIsSet = true;
- this._selectionId = builder.trackSelection(this._initialSelection, false);
- }
- }
- if (!selectionIsSet) {
- this._selectionId = builder.trackSelection(this._initialSelection);
- }
- }
- computeCursorState(model, helper) {
- return helper.getTrackedSelection(this._selectionId);
- }
- }
- export class AutoIndentOnPaste {
- constructor(editor) {
- this.callOnDispose = new DisposableStore();
- this.callOnModel = new DisposableStore();
- this.editor = editor;
- this.callOnDispose.add(editor.onDidChangeConfiguration(() => this.update()));
- this.callOnDispose.add(editor.onDidChangeModel(() => this.update()));
- this.callOnDispose.add(editor.onDidChangeModelLanguage(() => this.update()));
- }
- update() {
- // clean up
- this.callOnModel.clear();
- // we are disabled
- if (this.editor.getOption(9 /* autoIndent */) < 4 /* Full */ || this.editor.getOption(47 /* formatOnPaste */)) {
- return;
- }
- // no model
- if (!this.editor.hasModel()) {
- return;
- }
- this.callOnModel.add(this.editor.onDidPaste(({ range }) => {
- this.trigger(range);
- }));
- }
- trigger(range) {
- let selections = this.editor.getSelections();
- if (selections === null || selections.length > 1) {
- return;
- }
- const model = this.editor.getModel();
- if (!model) {
- return;
- }
- if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
- return;
- }
- const autoIndent = this.editor.getOption(9 /* autoIndent */);
- const { tabSize, indentSize, insertSpaces } = model.getOptions();
- let textEdits = [];
- let indentConverter = {
- shiftIndent: (indentation) => {
- return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
- },
- unshiftIndent: (indentation) => {
- return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
- }
- };
- let startLineNumber = range.startLineNumber;
- while (startLineNumber <= range.endLineNumber) {
- if (this.shouldIgnoreLine(model, startLineNumber)) {
- startLineNumber++;
- continue;
- }
- break;
- }
- if (startLineNumber > range.endLineNumber) {
- return;
- }
- let firstLineText = model.getLineContent(startLineNumber);
- if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) {
- const indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, model, model.getLanguageId(), startLineNumber, indentConverter);
- if (indentOfFirstLine !== null) {
- let oldIndentation = strings.getLeadingWhitespace(firstLineText);
- let newSpaceCnt = indentUtils.getSpaceCnt(indentOfFirstLine, tabSize);
- let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
- if (newSpaceCnt !== oldSpaceCnt) {
- let newIndent = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
- textEdits.push({
- range: new Range(startLineNumber, 1, startLineNumber, oldIndentation.length + 1),
- text: newIndent
- });
- firstLineText = newIndent + firstLineText.substr(oldIndentation.length);
- }
- else {
- let indentMetadata = LanguageConfigurationRegistry.getIndentMetadata(model, startLineNumber);
- if (indentMetadata === 0 || indentMetadata === 8 /* UNINDENT_MASK */) {
- // we paste content into a line where only contains whitespaces
- // after pasting, the indentation of the first line is already correct
- // the first line doesn't match any indentation rule
- // then no-op.
- return;
- }
- }
- }
- }
- const firstLineNumber = startLineNumber;
- // ignore empty or ignored lines
- while (startLineNumber < range.endLineNumber) {
- if (!/\S/.test(model.getLineContent(startLineNumber + 1))) {
- startLineNumber++;
- continue;
- }
- break;
- }
- if (startLineNumber !== range.endLineNumber) {
- let virtualModel = {
- getLineTokens: (lineNumber) => {
- return model.getLineTokens(lineNumber);
- },
- getLanguageId: () => {
- return model.getLanguageId();
- },
- getLanguageIdAtPosition: (lineNumber, column) => {
- return model.getLanguageIdAtPosition(lineNumber, column);
- },
- getLineContent: (lineNumber) => {
- if (lineNumber === firstLineNumber) {
- return firstLineText;
- }
- else {
- return model.getLineContent(lineNumber);
- }
- }
- };
- let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageId(), startLineNumber + 1, indentConverter);
- if (indentOfSecondLine !== null) {
- let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize);
- let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize);
- if (newSpaceCntOfSecondLine !== oldSpaceCntOfSecondLine) {
- let spaceCntOffset = newSpaceCntOfSecondLine - oldSpaceCntOfSecondLine;
- for (let i = startLineNumber + 1; i <= range.endLineNumber; i++) {
- let lineContent = model.getLineContent(i);
- let originalIndent = strings.getLeadingWhitespace(lineContent);
- let originalSpacesCnt = indentUtils.getSpaceCnt(originalIndent, tabSize);
- let newSpacesCnt = originalSpacesCnt + spaceCntOffset;
- let newIndent = indentUtils.generateIndent(newSpacesCnt, tabSize, insertSpaces);
- if (newIndent !== originalIndent) {
- textEdits.push({
- range: new Range(i, 1, i, originalIndent.length + 1),
- text: newIndent
- });
- }
- }
- }
- }
- }
- if (textEdits.length > 0) {
- this.editor.pushUndoStop();
- let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection());
- this.editor.executeCommand('autoIndentOnPaste', cmd);
- this.editor.pushUndoStop();
- }
- }
- shouldIgnoreLine(model, lineNumber) {
- model.forceTokenization(lineNumber);
- let nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
- if (nonWhitespaceColumn === 0) {
- return true;
- }
- let tokens = model.getLineTokens(lineNumber);
- if (tokens.getCount() > 0) {
- let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhitespaceColumn);
- if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === 1 /* Comment */) {
- return true;
- }
- }
- return false;
- }
- dispose() {
- this.callOnDispose.dispose();
- this.callOnModel.dispose();
- }
- }
- AutoIndentOnPaste.ID = 'editor.contrib.autoIndentOnPaste';
- function getIndentationEditOperations(model, builder, tabSize, tabsToSpaces) {
- if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
- // Model is empty
- return;
- }
- let spaces = '';
- for (let i = 0; i < tabSize; i++) {
- spaces += ' ';
- }
- let spacesRegExp = new RegExp(spaces, 'gi');
- for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
- let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
- if (lastIndentationColumn === 0) {
- lastIndentationColumn = model.getLineMaxColumn(lineNumber);
- }
- if (lastIndentationColumn === 1) {
- continue;
- }
- const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn);
- const originalIndentation = model.getValueInRange(originalIndentationRange);
- const newIndentation = (tabsToSpaces
- ? originalIndentation.replace(/\t/ig, spaces)
- : originalIndentation.replace(spacesRegExp, '\t'));
- builder.addEditOperation(originalIndentationRange, newIndentation);
- }
- }
- export class IndentationToSpacesCommand {
- constructor(selection, tabSize) {
- this.selection = selection;
- this.tabSize = tabSize;
- this.selectionId = null;
- }
- getEditOperations(model, builder) {
- this.selectionId = builder.trackSelection(this.selection);
- getIndentationEditOperations(model, builder, this.tabSize, true);
- }
- computeCursorState(model, helper) {
- return helper.getTrackedSelection(this.selectionId);
- }
- }
- export class IndentationToTabsCommand {
- constructor(selection, tabSize) {
- this.selection = selection;
- this.tabSize = tabSize;
- this.selectionId = null;
- }
- getEditOperations(model, builder) {
- this.selectionId = builder.trackSelection(this.selection);
- getIndentationEditOperations(model, builder, this.tabSize, false);
- }
- computeCursorState(model, helper) {
- return helper.getTrackedSelection(this.selectionId);
- }
- }
- registerEditorContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
- registerEditorAction(IndentationToSpacesAction);
- registerEditorAction(IndentationToTabsAction);
- registerEditorAction(IndentUsingTabs);
- registerEditorAction(IndentUsingSpaces);
- registerEditorAction(DetectIndentation);
- registerEditorAction(ReindentLinesAction);
- registerEditorAction(ReindentSelectedLinesAction);
|