src/vs/editor/common/commands/shiftCommand.ts

278 LOC · 85 covered · 193 uncovered · 12 ranges · 12 concepts · 3 introducers · 7 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts · 126 LOCcommands/trimTrailingWhi…src/vs/editor/common/languages/enterAction.ts · 65 LOClanguages/enterAction.tssrc/vs/editor/common/languages/supports/indentRules.ts · 83 LOCsupports/indentRules.tssrc/vs/editor/common/languages/supports/indentationLineProcessor.ts · 236 LOCsupports/indentationLine…src/vs/editor/common/model/tokens/tokenizerSyntaxTokenBackend.ts · 323 LOCtokens/tokenizerSyntaxTo…src/vs/editor/contrib/indentation/common/indentation.ts · 115 LOCcommon/indentation.tssrc/vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint.ts · 888 LOCcommon/languageConfigura…JavaScript Issue #25437|occurrence=1, JavaScript Issue #141816|occurrence=1 · 0 introduced LOCJavaScript Issue #25437|…indentation.ts ×1 · 2 introduced LOCindentation.ts ×1textModelTokens.ts ×4 · 12 introduced LOCtextModelTokens.ts ×4JavaScript Enriching the hover|occurrence=1 · 0 introduced LOCJavaScript Enriching the…JavaScript Issue #86176|occurrence=1 · 0 introduced LOCJavaScript Issue #86176|…indentation.ts ×4 · 41 introduced LOCindentation.ts ×4indentation.ts ×1 · 2 introduced LOCindentation.ts ×1indentation.ts ×6 · 27 introduced LOCindentation.ts ×6languageConfigurationExtensionPoint.ts ×38 · 309 introduced LOClanguageConfigurationExt…languageConfigurationExtensionPoint.ts ×6 · 12 introduced LOClanguageConfigurationExt…shiftCommand.ts ×1 · 5 introduced LOCshiftCommand.ts ×1languageConfigurationExtensionPoint.ts ×19 · 775 introduced LOClanguageConfigurationExt…JavaScript Enriching the hover|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Enriching the hover|occurrence=1JavaScript Enriching the…JavaScript Issue #141816|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Issue #141816|occurrence=1JavaScript Issue #141816…JavaScript Issue #209859: do not do reindentation for tokens inside of a string|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Issue #209859: do not do reindentation for tokens inside of a string|occurrence=1JavaScript Issue #209859…JavaScript Issue #25437|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Issue #25437|occurrence=1JavaScript Issue #25437|…JavaScript Issue #29886|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Issue #29886|occurrence=1JavaScript Issue #29886|…JavaScript Issue #86176|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Auto-Reindentation - TypeScript/JavaScript Issue #86176|occurrence=1JavaScript Issue #86176|…autoindent.test|title=Language Configuration Parsing Folding markers support object regex syntax with flags|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/codeEditor/test/node/autoindent.test|title=Language Configuration Parsing Folding markers support object regex syntax with flags|occurrence=1autoindent.test|title=La…Focused file · src/vs/editor/common/commands/shiftCommand.ts · 278 LOCcommands/shiftCommand.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- languageConfigurationExtensionPoint.ts ×19
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from '../../../base/common/charCode.js';
7 > import * as strings from '../../../base/common/strings.js';
8 > import { CursorColumns } from '../core/cursorColumns.js';
9 > import { Range } from '../core/range.js';
10 > import { Selection, SelectionDirection } from '../core/selection.js';
11 > import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from '../editorCommon.js';
12 > import { ITextModel } from '../model.js';
13 > import { EditorAutoIndentStrategy } from '../config/editorOptions.js';
14 > import { getEnterAction } from '../languages/enterAction.js';
15 > import { ILanguageConfigurationService } from '../languages/languageConfigurationRegistry.js';
16 >
17 > export interface IShiftCommandOpts {
18 > isUnshift: boolean;
19 > tabSize: number;
20 > indentSize: number;
21 > insertSpaces: boolean;
22 > useTabStops: boolean;
23 > autoIndent: EditorAutoIndentStrategy;
24 > }
25 >
26 > const repeatCache: { [str: string]: string[] } = Object.create(null);
27 > function cachedStringRepeat(str: string, count: number): string { indentation.ts ×4
28 > if (count <= 0) {
29 > return '';
30 > }
31 > if (!repeatCache[str]) {
32 > repeatCache[str] = ['', str];
33 > }
34 > const cache = repeatCache[str];
35 > for (let i = cache.length; i <= count; i++) {
36 > cache[i] = cache[i - 1] + str;
37 > }
38 > return cache[count];
39 > }
41 > export class ShiftCommand implements ICommand {
42 >
43 > public static unshiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string {
44 > // Determine the visible column where the content starts
45 > const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize);
46 >
47 > if (insertSpaces) {
48 > const indent = cachedStringRepeat(' ', indentSize);
49 > const desiredTabStop = CursorColumns.prevIndentTabStop(contentStartVisibleColumn, indentSize);
50 > const indentCount = desiredTabStop / indentSize; // will be an integer
51 > return cachedStringRepeat(indent, indentCount);
52 > } else {
53 > const indent = '\t'; shiftCommand.ts ×1
54 > const desiredTabStop = CursorColumns.prevRenderTabStop(contentStartVisibleColumn, tabSize);
55 > const indentCount = desiredTabStop / tabSize; // will be an integer
56 > return cachedStringRepeat(indent, indentCount);
57 > }
59 >
60 > public static shiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string {
61 > // Determine the visible column where the content starts indentation.ts ×4
62 > const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize);
63 >
64 > if (insertSpaces) {
65 > const indent = cachedStringRepeat(' ', indentSize);
66 > const desiredTabStop = CursorColumns.nextIndentTabStop(contentStartVisibleColumn, indentSize);
67 > const indentCount = desiredTabStop / indentSize; // will be an integer
68 > return cachedStringRepeat(indent, indentCount);
69 > } else {
70 const indent = '\t';
71 const desiredTabStop = CursorColumns.nextRenderTabStop(contentStartVisibleColumn, tabSize);
72 const indentCount = desiredTabStop / tabSize; // will be an integer
73 return cachedStringRepeat(indent, indentCount);
74 }
77 > private readonly _opts: IShiftCommandOpts;
78 > private readonly _selection: Selection;
79 > private _selectionId: string | null;
80 > private _useLastEditRangeForCursorEndPosition: boolean;
81 > private _selectionStartColumnStaysPut: boolean;
82 >
83 > constructor(
84 range: Selection,
85 opts: IShiftCommandOpts,
86 @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService
87 ) {
88 this._opts = opts;
89 this._selection = range;
90 this._selectionId = null;
91 this._useLastEditRangeForCursorEndPosition = false;
92 this._selectionStartColumnStaysPut = false;
93 }
95 > private _addEditOperation(builder: IEditOperationBuilder, range: Range, text: string) {
96 if (this._useLastEditRangeForCursorEndPosition) {
97 builder.addTrackedEditOperation(range, text);
98 } else {
99 builder.addEditOperation(range, text);
100 }
101 }
103 > public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
104 const startLine = this._selection.startLineNumber;
105
106 let endLine = this._selection.endLineNumber;
107 if (this._selection.endColumn === 1 && startLine !== endLine) {
108 endLine = endLine - 1;
109 }
110
111 const { tabSize, indentSize, insertSpaces } = this._opts;
112 const shouldIndentEmptyLines = (startLine === endLine);
113
114 if (this._opts.useTabStops) {
115 // if indenting or outdenting on a whitespace only line
116 if (this._selection.isEmpty()) {
117 if (/^\s*$/.test(model.getLineContent(startLine))) {
118 this._useLastEditRangeForCursorEndPosition = true;
119 }
120 }
121
122 // keep track of previous line's "miss-alignment"
123 let previousLineExtraSpaces = 0, extraSpaces = 0;
124 for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++, previousLineExtraSpaces = extraSpaces) {
125 extraSpaces = 0;
126 const lineText = model.getLineContent(lineNumber);
127 let indentationEndIndex = strings.firstNonWhitespaceIndex(lineText);
128
129 if (this._opts.isUnshift && (lineText.length === 0 || indentationEndIndex === 0)) {
130 // empty line or line with no leading whitespace => nothing to do
131 continue;
132 }
133
134 if (!shouldIndentEmptyLines && !this._opts.isUnshift && lineText.length === 0) {
135 // do not indent empty lines => nothing to do
136 continue;
137 }
138
139 if (indentationEndIndex === -1) {
140 // the entire line is whitespace
141 indentationEndIndex = lineText.length;
142 }
143
144 if (lineNumber > 1) {
145 const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(lineText, indentationEndIndex + 1, tabSize);
146 if (contentStartVisibleColumn % indentSize !== 0) {
147 // The current line is "miss-aligned", so let's see if this is expected...
148 // This can only happen when it has trailing commas in the indent
149 if (model.tokenization.isCheapToTokenize(lineNumber - 1)) {
150 const enterAction = getEnterAction(this._opts.autoIndent, model, new Range(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1), lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)), this._languageConfigurationService);
151 if (enterAction) {
152 extraSpaces = previousLineExtraSpaces;
153 if (enterAction.appendText) {
154 for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < indentSize; j++) {
155 if (enterAction.appendText.charCodeAt(j) === CharCode.Space) {
156 extraSpaces++;
157 } else {
158 break;
159 }
160 }
161 }
162 if (enterAction.removeText) {
163 extraSpaces = Math.max(0, extraSpaces - enterAction.removeText);
164 }
165
166 // Act as if `prefixSpaces` is not part of the indentation
167 for (let j = 0; j < extraSpaces; j++) {
168 if (indentationEndIndex === 0 || lineText.charCodeAt(indentationEndIndex - 1) !== CharCode.Space) {
169 break;
170 }
171 indentationEndIndex--;
172 }
173 }
174 }
175 }
176 }
177
178
179 if (this._opts.isUnshift && indentationEndIndex === 0) {
180 // line with no leading whitespace => nothing to do
181 continue;
182 }
183
184 let desiredIndent: string;
185 if (this._opts.isUnshift) {
186 desiredIndent = ShiftCommand.unshiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces);
187 } else {
188 desiredIndent = ShiftCommand.shiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces);
189 }
190
191 this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), desiredIndent);
192 if (lineNumber === startLine && !this._selection.isEmpty()) {
193 // Force the startColumn to stay put because we're inserting after it
194 this._selectionStartColumnStaysPut = (this._selection.startColumn <= indentationEndIndex + 1);
195 }
196 }
197 } else {
198
199 // if indenting or outdenting on a whitespace only line
200 if (!this._opts.isUnshift && this._selection.isEmpty() && model.getLineLength(startLine) === 0) {
201 this._useLastEditRangeForCursorEndPosition = true;
202 }
203
204 const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t');
205
206 for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
207 const lineText = model.getLineContent(lineNumber);
208 let indentationEndIndex = strings.firstNonWhitespaceIndex(lineText);
209
210 if (this._opts.isUnshift && (lineText.length === 0 || indentationEndIndex === 0)) {
211 // empty line or line with no leading whitespace => nothing to do
212 continue;
213 }
214
215 if (!shouldIndentEmptyLines && !this._opts.isUnshift && lineText.length === 0) {
216 // do not indent empty lines => nothing to do
217 continue;
218 }
219
220 if (indentationEndIndex === -1) {
221 // the entire line is whitespace
222 indentationEndIndex = lineText.length;
223 }
224
225 if (this._opts.isUnshift && indentationEndIndex === 0) {
226 // line with no leading whitespace => nothing to do
227 continue;
228 }
229
230 if (this._opts.isUnshift) {
231
232 indentationEndIndex = Math.min(indentationEndIndex, indentSize);
233 for (let i = 0; i < indentationEndIndex; i++) {
234 const chr = lineText.charCodeAt(i);
235 if (chr === CharCode.Tab) {
236 indentationEndIndex = i + 1;
237 break;
238 }
239 }
240
241 this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), '');
242 } else {
243 this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, 1), oneIndent);
244 if (lineNumber === startLine && !this._selection.isEmpty()) {
245 // Force the startColumn to stay put because we're inserting after it
246 this._selectionStartColumnStaysPut = (this._selection.startColumn === 1);
247 }
248 }
249 }
250 }
251
252 this._selectionId = builder.trackSelection(this._selection);
253 }
255 > public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
256 if (this._useLastEditRangeForCursorEndPosition) {
257 const lastOp = helper.getInverseEditOperations()[0];
258 return new Selection(lastOp.range.endLineNumber, lastOp.range.endColumn, lastOp.range.endLineNumber, lastOp.range.endColumn);
259 }
260 const result = helper.getTrackedSelection(this._selectionId!);
261
262 if (this._selectionStartColumnStaysPut) {
263 // The selection start should not move
264 const initialStartColumn = this._selection.startColumn;
265 const resultStartColumn = result.startColumn;
266 if (resultStartColumn <= initialStartColumn) {
267 return result;
268 }
269
270 if (result.getDirection() === SelectionDirection.LTR) {
271 return new Selection(result.startLineNumber, initialStartColumn, result.endLineNumber, result.endColumn);
272 }
273 return new Selection(result.endLineNumber, result.endColumn, result.startLineNumber, initialStartColumn);
274 }
275
276 return result;
277 }