Skip to content

Commit 37a7ec0

Browse files
committed
editor: introduce anchor selection
1 parent 4b60ba1 commit 37a7ec0

3 files changed

Lines changed: 182 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*---------------------------------------------------------------------------------------------
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+
.monaco-editor .selection-anchor {
7+
background-color: #007ACC;
8+
width: 2px !important;
9+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*---------------------------------------------------------------------------------------------
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 'vs/css!./anchorSelect';
7+
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
8+
import { localize } from 'vs/nls';
9+
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
10+
import { Selection } from 'vs/editor/common/core/selection';
11+
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
12+
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
13+
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
14+
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
15+
import { IEditorContribution } from 'vs/editor/common/editorCommon';
16+
import { TrackedRangeStickiness } from 'vs/editor/common/model';
17+
import { MarkdownString } from 'vs/base/common/htmlContent';
18+
19+
export const SelectionAnchorSet = new RawContextKey('selectionAnchorSet', false);
20+
21+
class SelectionAnchorController implements IEditorContribution {
22+
23+
public static readonly ID = 'editor.contrib.selectionAnchorController';
24+
25+
static get(editor: ICodeEditor): SelectionAnchorController {
26+
return editor.getContribution<SelectionAnchorController>(SelectionAnchorController.ID);
27+
}
28+
29+
private decorationId: string | undefined;
30+
private selectionAnchorSetContextKey: IContextKey<boolean>;
31+
32+
constructor(
33+
private editor: ICodeEditor,
34+
@IContextKeyService contextKeyService: IContextKeyService
35+
) {
36+
this.selectionAnchorSetContextKey = SelectionAnchorSet.bindTo(contextKeyService);
37+
}
38+
39+
setSelectionAnchor(): void {
40+
if (this.editor.hasModel()) {
41+
const position = this.editor.getPosition();
42+
const previousDecorations = this.decorationId ? [this.decorationId] : [];
43+
const newDecorationId = this.editor.deltaDecorations(previousDecorations, [{
44+
range: Selection.fromPositions(position, position),
45+
options: {
46+
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
47+
hoverMessage: new MarkdownString().appendText(localize('selectionAnchor', "Selection Anchor")),
48+
className: 'selection-anchor'
49+
}
50+
}]);
51+
this.decorationId = newDecorationId.length === 1 ? newDecorationId[0] : undefined;
52+
this.selectionAnchorSetContextKey.set(!!this.decorationId);
53+
}
54+
}
55+
56+
goToSelectionAnchor(): void {
57+
if (this.editor.hasModel() && this.decorationId) {
58+
const anchorPosition = this.editor.getModel().getDecorationRange(this.decorationId);
59+
if (anchorPosition) {
60+
this.editor.setPosition(anchorPosition.getStartPosition());
61+
}
62+
}
63+
}
64+
65+
selectFromAnchorToCursor(): void {
66+
if (this.editor.hasModel() && this.decorationId) {
67+
const start = this.editor.getModel().getDecorationRange(this.decorationId);
68+
if (start) {
69+
const end = this.editor.getPosition();
70+
this.editor.setSelection(Selection.fromPositions(start.getStartPosition(), end));
71+
this.cancelSelectionAnchor();
72+
}
73+
}
74+
}
75+
76+
cancelSelectionAnchor(): void {
77+
if (this.decorationId) {
78+
this.editor.deltaDecorations([this.decorationId], []);
79+
this.decorationId = undefined;
80+
this.selectionAnchorSetContextKey.set(false);
81+
}
82+
}
83+
84+
dispose(): void {
85+
this.cancelSelectionAnchor();
86+
}
87+
}
88+
89+
class SetSelectionAnchor extends EditorAction {
90+
constructor() {
91+
super({
92+
id: 'editor.action.setSelectionAnchor',
93+
label: localize('setSelectionAnchor', "Set Selection Anchor"),
94+
alias: 'Set Selection Anchor',
95+
precondition: undefined,
96+
kbOpts: {
97+
kbExpr: EditorContextKeys.editorTextFocus,
98+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_B),
99+
weight: KeybindingWeight.EditorContrib
100+
}
101+
});
102+
}
103+
104+
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
105+
const controller = SelectionAnchorController.get(editor);
106+
controller.setSelectionAnchor();
107+
}
108+
}
109+
110+
class GoToSelectionAnchor extends EditorAction {
111+
constructor() {
112+
super({
113+
id: 'editor.action.goToSelectionAnchor',
114+
label: localize('goToSelectionAnchor', "Go to Selection Anchor"),
115+
alias: 'Go to Selection Anchor',
116+
precondition: SelectionAnchorSet,
117+
});
118+
}
119+
120+
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
121+
const controller = SelectionAnchorController.get(editor);
122+
controller.goToSelectionAnchor();
123+
}
124+
}
125+
126+
class SelectFromAnchorToCursor extends EditorAction {
127+
constructor() {
128+
super({
129+
id: 'editor.action.selectFromAnchorToCursor',
130+
label: localize('selectFromAnchorToCursor', "Select from Anchor to Cursor"),
131+
alias: 'Select from Anchor to Cursor',
132+
precondition: SelectionAnchorSet,
133+
kbOpts: {
134+
kbExpr: EditorContextKeys.editorTextFocus,
135+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_K),
136+
weight: KeybindingWeight.EditorContrib
137+
}
138+
});
139+
}
140+
141+
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
142+
const controller = SelectionAnchorController.get(editor);
143+
controller.selectFromAnchorToCursor();
144+
}
145+
}
146+
147+
class CancelSelectionAnchor extends EditorAction {
148+
constructor() {
149+
super({
150+
id: 'editor.action.cancelSelectionAnchor',
151+
label: localize('cancelSelectionAnchor', "Cancel Selection Anchor"),
152+
alias: 'Cancel Selection Anchor',
153+
precondition: SelectionAnchorSet,
154+
kbOpts: {
155+
kbExpr: EditorContextKeys.editorTextFocus,
156+
primary: KeyCode.Escape,
157+
weight: KeybindingWeight.EditorContrib
158+
}
159+
});
160+
}
161+
162+
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
163+
const controller = SelectionAnchorController.get(editor);
164+
controller.cancelSelectionAnchor();
165+
}
166+
}
167+
168+
registerEditorContribution(SelectionAnchorController.ID, SelectionAnchorController);
169+
registerEditorAction(SetSelectionAnchor);
170+
registerEditorAction(GoToSelectionAnchor);
171+
registerEditorAction(SelectFromAnchorToCursor);
172+
registerEditorAction(CancelSelectionAnchor);

src/vs/editor/editor.all.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import 'vs/editor/contrib/viewportSemanticTokens/viewportSemanticTokens';
4545
import 'vs/editor/contrib/wordHighlighter/wordHighlighter';
4646
import 'vs/editor/contrib/wordOperations/wordOperations';
4747
import 'vs/editor/contrib/wordPartOperations/wordPartOperations';
48+
import 'vs/editor/contrib/anchorSelect/anchorSelect';
4849

4950
// Load up these strings even in VSCode, even if they are not used
5051
// in order to get them translated

0 commit comments

Comments
 (0)