Skip to content

Commit 9264622

Browse files
authored
Merge pull request microsoft#119480 from codeclown/delete-duplicates
Feature: New command - Delete Duplicate Lines
2 parents e040003 + fee6bbc commit 9264622

File tree

2 files changed

+131
-1
lines changed

2 files changed

+131
-1
lines changed

src/vs/editor/contrib/linesOperations/linesOperations.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,74 @@ export class SortLinesDescendingAction extends AbstractSortLinesAction {
282282
}
283283
}
284284

285+
export class DeleteDuplicateLinesAction extends EditorAction {
286+
constructor() {
287+
super({
288+
id: 'editor.action.removeDuplicateLines',
289+
label: nls.localize('lines.deleteDuplicates', "Delete Duplicate Lines"),
290+
alias: 'Delete Duplicate Lines',
291+
precondition: EditorContextKeys.writable
292+
});
293+
}
294+
295+
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
296+
if (!editor.hasModel()) {
297+
return;
298+
}
299+
300+
let model: ITextModel = editor.getModel();
301+
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
302+
return;
303+
}
304+
305+
let edits: IIdentifiedSingleEditOperation[] = [];
306+
let endCursorState: Selection[] = [];
307+
308+
let linesDeleted = 0;
309+
310+
for (let selection of editor.getSelections()) {
311+
let uniqueLines = new Set();
312+
let lines = [];
313+
314+
for (let i = selection.startLineNumber; i <= selection.endLineNumber; i++) {
315+
let line = model.getLineContent(i);
316+
317+
if (uniqueLines.has(line)) {
318+
continue;
319+
}
320+
321+
lines.push(line);
322+
uniqueLines.add(line);
323+
}
324+
325+
326+
let selectionToReplace = new Selection(
327+
selection.startLineNumber,
328+
1,
329+
selection.endLineNumber,
330+
model.getLineMaxColumn(selection.endLineNumber)
331+
);
332+
333+
let adjustedSelectionStart = selection.startLineNumber - linesDeleted;
334+
let finalSelection = new Selection(
335+
adjustedSelectionStart,
336+
1,
337+
adjustedSelectionStart + lines.length - 1,
338+
lines[lines.length - 1].length
339+
);
340+
341+
edits.push(EditOperation.replace(selectionToReplace, lines.join('\n')));
342+
endCursorState.push(finalSelection);
343+
344+
linesDeleted += (selection.endLineNumber - selection.startLineNumber + 1) - lines.length;
345+
}
346+
347+
editor.pushUndoStop();
348+
editor.executeEdits(this.id, edits, endCursorState);
349+
editor.pushUndoStop();
350+
}
351+
}
352+
285353
export class TrimTrailingWhitespaceAction extends EditorAction {
286354

287355
public static readonly ID = 'editor.action.trimTrailingWhitespace';
@@ -1111,6 +1179,7 @@ registerEditorAction(MoveLinesUpAction);
11111179
registerEditorAction(MoveLinesDownAction);
11121180
registerEditorAction(SortLinesAscendingAction);
11131181
registerEditorAction(SortLinesDescendingAction);
1182+
registerEditorAction(DeleteDuplicateLinesAction);
11141183
registerEditorAction(TrimTrailingWhitespaceAction);
11151184
registerEditorAction(DeleteLinesAction);
11161185
registerEditorAction(IndentLinesAction);

src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Selection } from 'vs/editor/common/core/selection';
1111
import { Handler } from 'vs/editor/common/editorCommon';
1212
import { ITextModel } from 'vs/editor/common/model';
1313
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
14-
import { DeleteAllLeftAction, DeleteAllRightAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/linesOperations';
14+
import { DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/linesOperations';
1515
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
1616
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
1717

@@ -143,6 +143,67 @@ suite('Editor Contrib - Line Operations', () => {
143143
});
144144
});
145145

146+
suite('DeleteDuplicateLinesAction', () => {
147+
test('should remove duplicate lines', function () {
148+
withTestCodeEditor(
149+
[
150+
'alpha',
151+
'beta',
152+
'beta',
153+
'beta',
154+
'alpha',
155+
'omicron',
156+
], {}, (editor) => {
157+
let model = editor.getModel()!;
158+
let deleteDuplicateLinesAction = new DeleteDuplicateLinesAction();
159+
160+
editor.setSelection(new Selection(1, 3, 6, 4));
161+
executeAction(deleteDuplicateLinesAction, editor);
162+
assert.deepStrictEqual(model.getLinesContent(), [
163+
'alpha',
164+
'beta',
165+
'omicron',
166+
]);
167+
assertSelection(editor, new Selection(1, 1, 3, 7));
168+
});
169+
});
170+
171+
test('should remove duplicate lines in multiple selections', function () {
172+
withTestCodeEditor(
173+
[
174+
'alpha',
175+
'beta',
176+
'beta',
177+
'omicron',
178+
'',
179+
'alpha',
180+
'alpha',
181+
'beta'
182+
], {}, (editor) => {
183+
let model = editor.getModel()!;
184+
let deleteDuplicateLinesAction = new DeleteDuplicateLinesAction();
185+
186+
editor.setSelections([new Selection(1, 2, 4, 3), new Selection(6, 2, 8, 3)]);
187+
executeAction(deleteDuplicateLinesAction, editor);
188+
assert.deepStrictEqual(model.getLinesContent(), [
189+
'alpha',
190+
'beta',
191+
'omicron',
192+
'',
193+
'alpha',
194+
'beta'
195+
]);
196+
let expectedSelections = [
197+
new Selection(1, 1, 3, 7),
198+
new Selection(5, 1, 6, 4)
199+
];
200+
editor.getSelections()!.forEach((actualSelection, index) => {
201+
assert.deepStrictEqual(actualSelection.toString(), expectedSelections[index].toString());
202+
});
203+
});
204+
});
205+
});
206+
146207

147208
suite('DeleteAllLeftAction', () => {
148209
test('should delete to the left of the cursor', function () {

0 commit comments

Comments
 (0)