Skip to content

Commit 80ab7f5

Browse files
committed
Implement deleting right at end of line includes leading whitespace
1 parent 3b44048 commit 80ab7f5

File tree

6 files changed

+119
-132
lines changed

6 files changed

+119
-132
lines changed

src/vs/editor/common/config/editorOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,11 @@ export interface IEditorOptions {
726726
* Inserting and deleting whitespace follows tab stops.
727727
*/
728728
useTabStops?: boolean;
729+
/**
730+
* Controls whether the editor should automatically remove indentation whitespace when joining lines with Delete.
731+
* Defaults to false.
732+
*/
733+
trimWhitespaceOnDelete?: boolean;
729734
/**
730735
* The font family
731736
*/
@@ -5710,6 +5715,7 @@ export const enum EditorOption {
57105715
suggestSelection,
57115716
tabCompletion,
57125717
tabIndex,
5718+
trimWhitespaceOnDelete,
57135719
unicodeHighlighting,
57145720
unusualLineTerminators,
57155721
useShadowDOM,
@@ -6492,6 +6498,10 @@ export const EditorOptions = {
64926498
EditorOption.tabIndex, 'tabIndex',
64936499
0, -1, Constants.MAX_SAFE_SMALL_INTEGER
64946500
)),
6501+
trimWhitespaceOnDelete: register(new EditorBooleanOption(
6502+
EditorOption.trimWhitespaceOnDelete, 'trimWhitespaceOnDelete', false,
6503+
{ description: nls.localize('trimWhitespaceOnDelete', "Controls whether the editor will also delete the next line's indentation whitespace when deleting a newline.") }
6504+
)),
64956505
unicodeHighlight: register(new UnicodeHighlight()),
64966506
unusualLineTerminators: register(new EditorStringEnumOption(
64976507
EditorOption.unusualLineTerminators, 'unusualLineTerminators',

src/vs/editor/common/cursor/cursorDeleteOperations.ts

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,7 @@ export class DeleteOperations {
2323
for (let i = 0, len = selections.length; i < len; i++) {
2424
const selection = selections[i];
2525

26-
let deleteSelection: Range = selection;
27-
28-
if (deleteSelection.isEmpty()) {
29-
const position = selection.getPosition();
30-
const rightOfPosition = MoveOperations.right(config, model, position);
31-
deleteSelection = new Range(
32-
rightOfPosition.lineNumber,
33-
rightOfPosition.column,
34-
position.lineNumber,
35-
position.column
36-
);
37-
}
26+
const deleteSelection = this.getDeleteRightRange(selection, model, config);
3827

3928
if (deleteSelection.isEmpty()) {
4029
// Probably at end of file => ignore
@@ -51,6 +40,37 @@ export class DeleteOperations {
5140
return [shouldPushStackElementBefore, commands];
5241
}
5342

43+
private static getDeleteRightRange(selection: Selection, model: ICursorSimpleModel, config: CursorConfiguration): Range {
44+
if (!selection.isEmpty()) {
45+
return selection;
46+
}
47+
48+
const position = selection.getPosition();
49+
const rightOfPosition = MoveOperations.right(config, model, position);
50+
51+
if (config.trimWhitespaceOnDelete && rightOfPosition.lineNumber !== position.lineNumber) {
52+
// Smart line join (deleting leading whitespace) is on
53+
// (and) Delete is happening at the end of a line
54+
const firstNonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(rightOfPosition.lineNumber);
55+
if (firstNonWhitespaceColumn > 0) {
56+
// The next line has content
57+
return new Range(
58+
rightOfPosition.lineNumber,
59+
firstNonWhitespaceColumn,
60+
position.lineNumber,
61+
position.column
62+
);
63+
}
64+
}
65+
66+
return new Range(
67+
rightOfPosition.lineNumber,
68+
rightOfPosition.column,
69+
position.lineNumber,
70+
position.column
71+
);
72+
}
73+
5474
public static isAutoClosingPairDelete(
5575
autoClosingDelete: EditorAutoClosingEditStrategy,
5676
autoClosingBrackets: EditorAutoClosingStrategy,
@@ -150,7 +170,7 @@ export class DeleteOperations {
150170
const commands: Array<ICommand | null> = [];
151171
let shouldPushStackElementBefore = (prevEditOperationType !== EditOperationType.DeletingLeft);
152172
for (let i = 0, len = selections.length; i < len; i++) {
153-
const deleteRange = DeleteOperations.getDeleteRange(selections[i], model, config);
173+
const deleteRange = DeleteOperations.getDeleteLeftRange(selections[i], model, config);
154174

155175
// Ignore empty delete ranges, as they have no effect
156176
// They happen if the cursor is at the beginning of the file.
@@ -169,43 +189,13 @@ export class DeleteOperations {
169189

170190
}
171191

172-
private static getDeleteRange(selection: Selection, model: ICursorSimpleModel, config: CursorConfiguration,): Range {
173-
const position = selection.getPosition();
174-
const startPosition = selection.getSelectionStart();
175-
176-
const startLineNumber = Math.min(startPosition.lineNumber, position.lineNumber);
177-
const endLineNumber = Math.max(startPosition.lineNumber, position.lineNumber);
178-
let startColumn = position.column;
179-
let endColumn = startPosition.column;
180-
if (startLineNumber === startPosition.lineNumber) {
181-
//top down deletion
182-
startColumn = startPosition.column;
183-
endColumn = position.column;
184-
}
185-
let firstNonWhiteSpaceColumn = model.getLineFirstNonWhitespaceColumn(endLineNumber);
186-
let lastNonWhiteSpaceColumn = model.getLineLastNonWhitespaceColumn(startLineNumber);
187-
//deleting new line character + trimming white space
188-
if (startLineNumber !== endLineNumber && model.getLineContent(startLineNumber).length > 0) {
189-
//expand delete range to include extra white space (last non whitespace char to first non white char on following line)
190-
if (startColumn < lastNonWhiteSpaceColumn) {
191-
lastNonWhiteSpaceColumn = startColumn;
192-
}
193-
if (endColumn > firstNonWhiteSpaceColumn) {
194-
firstNonWhiteSpaceColumn = endColumn;
195-
}
196-
if (/^[ \t]*$/.test(model.getLineContent(startLineNumber))) {
197-
//if line with '\n' character has only spaces/tabs -- no need to trim left side
198-
return Range.fromPositions(new Position(startLineNumber, startColumn),
199-
new Position(endLineNumber, firstNonWhiteSpaceColumn));
200-
}
201-
return Range.fromPositions(new Position(startLineNumber, lastNonWhiteSpaceColumn),
202-
new Position(endLineNumber, firstNonWhiteSpaceColumn));
203-
}
204-
192+
private static getDeleteLeftRange(selection: Selection, model: ICursorSimpleModel, config: CursorConfiguration): Range {
205193
if (!selection.isEmpty()) {
206194
return selection;
207195
}
208196

197+
const position = selection.getPosition();
198+
209199
// Unintend when using tab stops and cursor is within indentation
210200
if (config.useTabStops && position.column > 1) {
211201
const lineContent = model.getLineContent(position.lineNumber);

src/vs/editor/common/cursorCommon.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class CursorConfiguration {
6060
public readonly lineHeight: number;
6161
public readonly typicalHalfwidthCharacterWidth: number;
6262
public readonly useTabStops: boolean;
63+
public readonly trimWhitespaceOnDelete: boolean;
6364
public readonly wordSeparators: string;
6465
public readonly emptySelectionClipboard: boolean;
6566
public readonly copyWithSyntaxHighlighting: boolean;
@@ -98,6 +99,7 @@ export class CursorConfiguration {
9899
|| e.hasChanged(EditorOption.autoClosingOvertype)
99100
|| e.hasChanged(EditorOption.autoSurround)
100101
|| e.hasChanged(EditorOption.useTabStops)
102+
|| e.hasChanged(EditorOption.trimWhitespaceOnDelete)
101103
|| e.hasChanged(EditorOption.fontInfo)
102104
|| e.hasChanged(EditorOption.readOnly)
103105
|| e.hasChanged(EditorOption.wordSegmenterLocales)
@@ -126,6 +128,7 @@ export class CursorConfiguration {
126128
this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
127129
this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
128130
this.useTabStops = options.get(EditorOption.useTabStops);
131+
this.trimWhitespaceOnDelete = options.get(EditorOption.trimWhitespaceOnDelete);
129132
this.wordSeparators = options.get(EditorOption.wordSeparators);
130133
this.emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
131134
this.copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);

src/vs/editor/common/standalone/standaloneEnums.ts

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -311,37 +311,38 @@ export enum EditorOption {
311311
suggestSelection = 134,
312312
tabCompletion = 135,
313313
tabIndex = 136,
314-
unicodeHighlighting = 137,
315-
unusualLineTerminators = 138,
316-
useShadowDOM = 139,
317-
useTabStops = 140,
318-
wordBreak = 141,
319-
wordSegmenterLocales = 142,
320-
wordSeparators = 143,
321-
wordWrap = 144,
322-
wordWrapBreakAfterCharacters = 145,
323-
wordWrapBreakBeforeCharacters = 146,
324-
wordWrapColumn = 147,
325-
wordWrapOverride1 = 148,
326-
wordWrapOverride2 = 149,
327-
wrappingIndent = 150,
328-
wrappingStrategy = 151,
329-
showDeprecated = 152,
330-
inertialScroll = 153,
331-
inlayHints = 154,
332-
wrapOnEscapedLineFeeds = 155,
333-
effectiveCursorStyle = 156,
334-
editorClassName = 157,
335-
pixelRatio = 158,
336-
tabFocusMode = 159,
337-
layoutInfo = 160,
338-
wrappingInfo = 161,
339-
defaultColorDecorators = 162,
340-
colorDecoratorsActivatedOn = 163,
341-
inlineCompletionsAccessibilityVerbose = 164,
342-
effectiveEditContext = 165,
343-
scrollOnMiddleClick = 166,
344-
effectiveAllowVariableFonts = 167
314+
trimWhitespaceOnDelete = 137,
315+
unicodeHighlighting = 138,
316+
unusualLineTerminators = 139,
317+
useShadowDOM = 140,
318+
useTabStops = 141,
319+
wordBreak = 142,
320+
wordSegmenterLocales = 143,
321+
wordSeparators = 144,
322+
wordWrap = 145,
323+
wordWrapBreakAfterCharacters = 146,
324+
wordWrapBreakBeforeCharacters = 147,
325+
wordWrapColumn = 148,
326+
wordWrapOverride1 = 149,
327+
wordWrapOverride2 = 150,
328+
wrappingIndent = 151,
329+
wrappingStrategy = 152,
330+
showDeprecated = 153,
331+
inertialScroll = 154,
332+
inlayHints = 155,
333+
wrapOnEscapedLineFeeds = 156,
334+
effectiveCursorStyle = 157,
335+
editorClassName = 158,
336+
pixelRatio = 159,
337+
tabFocusMode = 160,
338+
layoutInfo = 161,
339+
wrappingInfo = 162,
340+
defaultColorDecorators = 163,
341+
colorDecoratorsActivatedOn = 164,
342+
inlineCompletionsAccessibilityVerbose = 165,
343+
effectiveEditContext = 166,
344+
scrollOnMiddleClick = 167,
345+
effectiveAllowVariableFonts = 168
345346
}
346347

347348
/**

src/vs/editor/test/browser/controller/cursor.test.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5604,30 +5604,6 @@ suite('Editor Controller', () => {
56045604
});
56055605
});
56065606

5607-
test('feature request - #134898: Trimming whitespace when deleting newline character', () => {
5608-
const model = createTextModel(
5609-
[
5610-
'Hello world!',
5611-
' ',
5612-
' another line'
5613-
].join('\n'),
5614-
undefined,
5615-
{
5616-
insertSpaces: false
5617-
},
5618-
);
5619-
withTestCodeEditor(model, {}, (editor, viewModel) => {
5620-
viewModel.setSelections('test', [new Selection(2, 5, 3, 1)]);
5621-
//when deleting middle empty line -- no text present so left spaces/tabs preserved
5622-
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
5623-
assert.strictEqual(model.getLineContent(2), ' another line');
5624-
//removes the extra white space in front of line 2 when deleting new line character
5625-
viewModel.setSelections('test', [new Selection(1, 13, 2, 1)]);
5626-
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
5627-
assert.strictEqual(model.getLineContent(1), 'Hello world!another line');
5628-
});
5629-
});
5630-
56315607
test('issue #78527 - does not close quote on odd count', () => {
56325608
usingCursor({
56335609
text: [

src/vs/monaco.d.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3859,6 +3859,11 @@ declare namespace monaco.editor {
38593859
* Inserting and deleting whitespace follows tab stops.
38603860
*/
38613861
useTabStops?: boolean;
3862+
/**
3863+
* Controls whether the editor should automatically remove indentation whitespace when joining lines with Delete.
3864+
* Defaults to false.
3865+
*/
3866+
trimWhitespaceOnDelete?: boolean;
38623867
/**
38633868
* The font family
38643869
*/
@@ -5159,37 +5164,38 @@ declare namespace monaco.editor {
51595164
suggestSelection = 134,
51605165
tabCompletion = 135,
51615166
tabIndex = 136,
5162-
unicodeHighlighting = 137,
5163-
unusualLineTerminators = 138,
5164-
useShadowDOM = 139,
5165-
useTabStops = 140,
5166-
wordBreak = 141,
5167-
wordSegmenterLocales = 142,
5168-
wordSeparators = 143,
5169-
wordWrap = 144,
5170-
wordWrapBreakAfterCharacters = 145,
5171-
wordWrapBreakBeforeCharacters = 146,
5172-
wordWrapColumn = 147,
5173-
wordWrapOverride1 = 148,
5174-
wordWrapOverride2 = 149,
5175-
wrappingIndent = 150,
5176-
wrappingStrategy = 151,
5177-
showDeprecated = 152,
5178-
inertialScroll = 153,
5179-
inlayHints = 154,
5180-
wrapOnEscapedLineFeeds = 155,
5181-
effectiveCursorStyle = 156,
5182-
editorClassName = 157,
5183-
pixelRatio = 158,
5184-
tabFocusMode = 159,
5185-
layoutInfo = 160,
5186-
wrappingInfo = 161,
5187-
defaultColorDecorators = 162,
5188-
colorDecoratorsActivatedOn = 163,
5189-
inlineCompletionsAccessibilityVerbose = 164,
5190-
effectiveEditContext = 165,
5191-
scrollOnMiddleClick = 166,
5192-
effectiveAllowVariableFonts = 167
5167+
trimWhitespaceOnDelete = 137,
5168+
unicodeHighlighting = 138,
5169+
unusualLineTerminators = 139,
5170+
useShadowDOM = 140,
5171+
useTabStops = 141,
5172+
wordBreak = 142,
5173+
wordSegmenterLocales = 143,
5174+
wordSeparators = 144,
5175+
wordWrap = 145,
5176+
wordWrapBreakAfterCharacters = 146,
5177+
wordWrapBreakBeforeCharacters = 147,
5178+
wordWrapColumn = 148,
5179+
wordWrapOverride1 = 149,
5180+
wordWrapOverride2 = 150,
5181+
wrappingIndent = 151,
5182+
wrappingStrategy = 152,
5183+
showDeprecated = 153,
5184+
inertialScroll = 154,
5185+
inlayHints = 155,
5186+
wrapOnEscapedLineFeeds = 156,
5187+
effectiveCursorStyle = 157,
5188+
editorClassName = 158,
5189+
pixelRatio = 159,
5190+
tabFocusMode = 160,
5191+
layoutInfo = 161,
5192+
wrappingInfo = 162,
5193+
defaultColorDecorators = 163,
5194+
colorDecoratorsActivatedOn = 164,
5195+
inlineCompletionsAccessibilityVerbose = 165,
5196+
effectiveEditContext = 166,
5197+
scrollOnMiddleClick = 167,
5198+
effectiveAllowVariableFonts = 168
51935199
}
51945200

51955201
export const EditorOptions: {
@@ -5336,6 +5342,7 @@ declare namespace monaco.editor {
53365342
suggestSelection: IEditorOption<EditorOption.suggestSelection, 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'>;
53375343
tabCompletion: IEditorOption<EditorOption.tabCompletion, 'on' | 'off' | 'onlySnippets'>;
53385344
tabIndex: IEditorOption<EditorOption.tabIndex, number>;
5345+
trimWhitespaceOnDelete: IEditorOption<EditorOption.trimWhitespaceOnDelete, boolean>;
53395346
unicodeHighlight: IEditorOption<EditorOption.unicodeHighlighting, any>;
53405347
unusualLineTerminators: IEditorOption<EditorOption.unusualLineTerminators, 'off' | 'auto' | 'prompt'>;
53415348
useShadowDOM: IEditorOption<EditorOption.useShadowDOM, boolean>;

0 commit comments

Comments
 (0)