monaco-editor
Version:
A browser based code editor
568 lines (565 loc) • 25.1 kB
JavaScript
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { getLeadingWhitespace } from '../../../../base/common/strings.js';
import { localize2, localize } from '../../../../nls.js';
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
import { EditorAction, registerEditorContribution, registerEditorAction } from '../../../browser/editorExtensions.js';
import { ShiftCommand } from '../../../common/commands/shiftCommand.js';
import { Range } from '../../../common/core/range.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { getGoodIndentForLine, getIndentMetadata } from '../../../common/languages/autoIndent.js';
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
import { IModelService } from '../../../common/services/model.js';
import { getStandardTokenTypeAtPosition } from '../../../common/tokens/lineTokens.js';
import { getReindentEditOperations } from '../common/indentation.js';
import { getSpaceCnt, generateIndent } from '../common/indentUtils.js';
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (undefined && undefined.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
class IndentationToSpacesAction extends EditorAction {
static { this.ID = 'editor.action.indentationToSpaces'; }
constructor() {
super({
id: IndentationToSpacesAction.ID,
label: localize2(1189, "Convert Indentation to Spaces"),
precondition: EditorContextKeys.writable,
metadata: {
description: localize2(1190, "Convert the tab indentation to spaces."),
}
});
}
run(accessor, editor) {
const model = editor.getModel();
if (!model) {
return;
}
const modelOpts = model.getOptions();
const 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
});
}
}
class IndentationToTabsAction extends EditorAction {
static { this.ID = 'editor.action.indentationToTabs'; }
constructor() {
super({
id: IndentationToTabsAction.ID,
label: localize2(1191, "Convert Indentation to Tabs"),
precondition: EditorContextKeys.writable,
metadata: {
description: localize2(1192, "Convert the spaces indentation to tabs."),
}
});
}
run(accessor, editor) {
const model = editor.getModel();
if (!model) {
return;
}
const modelOpts = model.getOptions();
const 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
});
}
}
class ChangeIndentationSizeAction extends EditorAction {
constructor(insertSpaces, displaySizeOnly, opts) {
super(opts);
this.insertSpaces = insertSpaces;
this.displaySizeOnly = displaySizeOnly;
}
run(accessor, editor) {
const quickInputService = accessor.get(IQuickInputService);
const modelService = accessor.get(IModelService);
const model = editor.getModel();
if (!model) {
return;
}
const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
const modelOpts = model.getOptions();
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 && n === modelOpts.tabSize
? localize(1185, "Configured Tab Size")
: n === creationOpts.tabSize
? localize(1186, "Default Tab Size")
: n === modelOpts.tabSize
? localize(1187, "Current 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: localize(1188, "Select Tab Size for Current File"), activeItem: picks[autoFocusIndex] }).then(pick => {
if (pick) {
if (model && !model.isDisposed()) {
const pickedVal = parseInt(pick.label, 10);
if (this.displaySizeOnly) {
model.updateOptions({
tabSize: pickedVal
});
}
else {
model.updateOptions({
tabSize: pickedVal,
indentSize: pickedVal,
insertSpaces: this.insertSpaces
});
}
}
}
});
}, 50 /* quick input is sensitive to being opened so soon after another */);
}
}
class IndentUsingTabs extends ChangeIndentationSizeAction {
static { this.ID = 'editor.action.indentUsingTabs'; }
constructor() {
super(false, false, {
id: IndentUsingTabs.ID,
label: localize2(1193, "Indent Using Tabs"),
precondition: undefined,
metadata: {
description: localize2(1194, "Use indentation with tabs."),
}
});
}
}
class IndentUsingSpaces extends ChangeIndentationSizeAction {
static { this.ID = 'editor.action.indentUsingSpaces'; }
constructor() {
super(true, false, {
id: IndentUsingSpaces.ID,
label: localize2(1195, "Indent Using Spaces"),
precondition: undefined,
metadata: {
description: localize2(1196, "Use indentation with spaces."),
}
});
}
}
class ChangeTabDisplaySize extends ChangeIndentationSizeAction {
static { this.ID = 'editor.action.changeTabDisplaySize'; }
constructor() {
super(true, true, {
id: ChangeTabDisplaySize.ID,
label: localize2(1197, "Change Tab Display Size"),
precondition: undefined,
metadata: {
description: localize2(1198, "Change the space size equivalent of the tab."),
}
});
}
}
class DetectIndentation extends EditorAction {
static { this.ID = 'editor.action.detectIndentation'; }
constructor() {
super({
id: DetectIndentation.ID,
label: localize2(1199, "Detect Indentation from Content"),
precondition: undefined,
metadata: {
description: localize2(1200, "Detect the indentation from content."),
}
});
}
run(accessor, editor) {
const modelService = accessor.get(IModelService);
const model = editor.getModel();
if (!model) {
return;
}
const creationOpts = modelService.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize);
}
}
class ReindentLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.reindentlines',
label: localize2(1201, "Reindent Lines"),
precondition: EditorContextKeys.writable,
metadata: {
description: localize2(1202, "Reindent the lines of the editor."),
},
canTriggerInlineEdits: true,
});
}
run(accessor, editor) {
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
const model = editor.getModel();
if (!model) {
return;
}
const edits = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
if (edits.length > 0) {
editor.pushUndoStop();
editor.executeEdits(this.id, edits);
editor.pushUndoStop();
}
}
}
class ReindentSelectedLinesAction extends EditorAction {
constructor() {
super({
id: 'editor.action.reindentselectedlines',
label: localize2(1203, "Reindent Selected Lines"),
precondition: EditorContextKeys.writable,
metadata: {
description: localize2(1204, "Reindent the selected lines of the editor."),
},
canTriggerInlineEdits: true,
});
}
run(accessor, editor) {
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
const model = editor.getModel();
if (!model) {
return;
}
const selections = editor.getSelections();
if (selections === null) {
return;
}
const edits = [];
for (const 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--;
}
const editOperations = getReindentEditOperations(model, languageConfigurationService, startLineNumber, endLineNumber);
edits.push(...editOperations);
}
if (edits.length > 0) {
editor.pushUndoStop();
editor.executeEdits(this.id, edits);
editor.pushUndoStop();
}
}
}
class AutoIndentOnPasteCommand {
constructor(edits, initialSelection) {
this._initialSelection = initialSelection;
this._edits = [];
this._selectionId = null;
for (const edit of edits) {
if (edit.range && typeof edit.text === 'string') {
this._edits.push(edit);
}
}
}
getEditOperations(model, builder) {
for (const 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);
}
}
let AutoIndentOnPaste = class AutoIndentOnPaste {
static { this.ID = 'editor.contrib.autoIndentOnPaste'; }
constructor(editor, _languageConfigurationService) {
this.editor = editor;
this._languageConfigurationService = _languageConfigurationService;
this.callOnDispose = new DisposableStore();
this.callOnModel = new DisposableStore();
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(17 /* EditorOption.autoIndentOnPaste */) || this.editor.getOption(16 /* EditorOption.autoIndent */) < 4 /* EditorAutoIndentStrategy.Full */) {
return;
}
// no model
if (!this.editor.hasModel()) {
return;
}
this.callOnModel.add(this.editor.onDidPaste(({ range }) => {
this.trigger(range);
}));
}
trigger(range) {
const selections = this.editor.getSelections();
if (selections === null || selections.length > 1) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
}
const containsOnlyWhitespace = this.rangeContainsOnlyWhitespaceCharacters(model, range);
if (containsOnlyWhitespace) {
return;
}
if (!this.editor.getOption(18 /* EditorOption.autoIndentOnPasteWithinString */) && isStartOrEndInString(model, range)) {
return;
}
if (!model.tokenization.isCheapToTokenize(range.getStartPosition().lineNumber)) {
return;
}
const autoIndent = this.editor.getOption(16 /* EditorOption.autoIndent */);
const { tabSize, indentSize, insertSpaces } = model.getOptions();
const textEdits = [];
const 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;
let firstLineText = model.getLineContent(startLineNumber);
if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) {
const indentOfFirstLine = getGoodIndentForLine(autoIndent, model, model.getLanguageId(), startLineNumber, indentConverter, this._languageConfigurationService);
if (indentOfFirstLine !== null) {
const oldIndentation = getLeadingWhitespace(firstLineText);
const newSpaceCnt = getSpaceCnt(indentOfFirstLine, tabSize);
const oldSpaceCnt = getSpaceCnt(oldIndentation, tabSize);
if (newSpaceCnt !== oldSpaceCnt) {
const newIndent = generateIndent(newSpaceCnt, tabSize, insertSpaces);
textEdits.push({
range: new Range(startLineNumber, 1, startLineNumber, oldIndentation.length + 1),
text: newIndent
});
firstLineText = newIndent + firstLineText.substring(oldIndentation.length);
}
else {
const indentMetadata = getIndentMetadata(model, startLineNumber, this._languageConfigurationService);
if (indentMetadata === 0 || indentMetadata === 8 /* IndentConsts.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) {
const virtualModel = {
tokenization: {
getLineTokens: (lineNumber) => {
return model.tokenization.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);
}
}
};
const indentOfSecondLine = getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageId(), startLineNumber + 1, indentConverter, this._languageConfigurationService);
if (indentOfSecondLine !== null) {
const newSpaceCntOfSecondLine = getSpaceCnt(indentOfSecondLine, tabSize);
const oldSpaceCntOfSecondLine = getSpaceCnt(getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize);
if (newSpaceCntOfSecondLine !== oldSpaceCntOfSecondLine) {
const spaceCntOffset = newSpaceCntOfSecondLine - oldSpaceCntOfSecondLine;
for (let i = startLineNumber + 1; i <= range.endLineNumber; i++) {
const lineContent = model.getLineContent(i);
const originalIndent = getLeadingWhitespace(lineContent);
const originalSpacesCnt = getSpaceCnt(originalIndent, tabSize);
const newSpacesCnt = originalSpacesCnt + spaceCntOffset;
const newIndent = 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();
const cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection());
this.editor.executeCommand('autoIndentOnPaste', cmd);
this.editor.pushUndoStop();
}
}
rangeContainsOnlyWhitespaceCharacters(model, range) {
const lineContainsOnlyWhitespace = (content) => {
return content.trim().length === 0;
};
let containsOnlyWhitespace = true;
if (range.startLineNumber === range.endLineNumber) {
const lineContent = model.getLineContent(range.startLineNumber);
const linePart = lineContent.substring(range.startColumn - 1, range.endColumn - 1);
containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart);
}
else {
for (let i = range.startLineNumber; i <= range.endLineNumber; i++) {
const lineContent = model.getLineContent(i);
if (i === range.startLineNumber) {
const linePart = lineContent.substring(range.startColumn - 1);
containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart);
}
else if (i === range.endLineNumber) {
const linePart = lineContent.substring(0, range.endColumn - 1);
containsOnlyWhitespace = lineContainsOnlyWhitespace(linePart);
}
else {
containsOnlyWhitespace = model.getLineFirstNonWhitespaceColumn(i) === 0;
}
if (!containsOnlyWhitespace) {
break;
}
}
}
return containsOnlyWhitespace;
}
dispose() {
this.callOnDispose.dispose();
this.callOnModel.dispose();
}
};
AutoIndentOnPaste = __decorate([
__param(1, ILanguageConfigurationService)
], AutoIndentOnPaste);
function isStartOrEndInString(model, range) {
const isPositionInString = (position) => {
const tokenType = getStandardTokenTypeAtPosition(model, position);
return tokenType === 2 /* StandardTokenType.String */;
};
return isPositionInString(range.getStartPosition()) || isPositionInString(range.getEndPosition());
}
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 += ' ';
}
const 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);
}
}
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);
}
}
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, 2 /* EditorContributionInstantiation.BeforeFirstInteraction */);
registerEditorAction(IndentationToSpacesAction);
registerEditorAction(IndentationToTabsAction);
registerEditorAction(IndentUsingTabs);
registerEditorAction(IndentUsingSpaces);
registerEditorAction(ChangeTabDisplaySize);
registerEditorAction(DetectIndentation);
registerEditorAction(ReindentLinesAction);
registerEditorAction(ReindentSelectedLinesAction);
export { AutoIndentOnPaste, AutoIndentOnPasteCommand, ChangeIndentationSizeAction, ChangeTabDisplaySize, DetectIndentation, IndentUsingSpaces, IndentUsingTabs, IndentationToSpacesAction, IndentationToSpacesCommand, IndentationToTabsAction, IndentationToTabsCommand, ReindentLinesAction, ReindentSelectedLinesAction };