smartSelect.ts 6.9 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import * as arrays from 'vs/base/common/arrays';
7 8
import { asThenable, first } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
J
Johannes Rieken 已提交
9
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
10 11 12 13
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { Position } from 'vs/editor/common/core/position';
J
Johannes Rieken 已提交
14
import { Range } from 'vs/editor/common/core/range';
15
import { IEditorContribution } from 'vs/editor/common/editorCommon';
16
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
17 18 19 20
import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { DefaultSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/defaultProvider';
import * as nls from 'vs/nls';
21
import { MenuId } from 'vs/platform/actions/common/actions';
22
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
E
Erich Gamma 已提交
23 24 25 26 27

// --- selection state machine

class State {

28
	public editor: ICodeEditor;
M
Matt Bierner 已提交
29 30 31
	public next?: State;
	public previous?: State;
	public selection: Range | null;
E
Erich Gamma 已提交
32

33
	constructor(editor: ICodeEditor) {
E
Erich Gamma 已提交
34 35 36 37 38 39 40
		this.editor = editor;
		this.selection = editor.getSelection();
	}
}

// -- action implementation

A
Alex Dima 已提交
41 42
class SmartSelectController implements IEditorContribution {

43
	private static readonly ID = 'editor.contrib.smartSelectController';
A
Alex Dima 已提交
44

45
	public static get(editor: ICodeEditor): SmartSelectController {
A
Alex Dima 已提交
46
		return editor.getContribution<SmartSelectController>(SmartSelectController.ID);
A
Alex Dima 已提交
47
	}
E
Erich Gamma 已提交
48

49
	private _editor: ICodeEditor;
M
Matt Bierner 已提交
50
	private _state?: State;
A
Alex Dima 已提交
51
	private _ignoreSelection: boolean;
E
Erich Gamma 已提交
52

53 54
	constructor(editor: ICodeEditor) {
		this._editor = editor;
A
Alex Dima 已提交
55
		this._ignoreSelection = false;
E
Erich Gamma 已提交
56 57
	}

58
	dispose(): void {
A
Alex Dima 已提交
59 60
	}

61
	getId(): string {
A
Alex Dima 已提交
62 63 64
		return SmartSelectController.ID;
	}

65 66 67
	run(forward: boolean): Promise<void> | void {
		if (!this._editor.hasModel()) {
			return;
M
Matt Bierner 已提交
68
		}
E
Erich Gamma 已提交
69

70 71 72 73 74 75
		const selection = this._editor.getSelection();
		const model = this._editor.getModel();

		if (!modes.SelectionRangeRegistry.has(model)) {
			return;
		}
E
Erich Gamma 已提交
76 77

		// forget about current state
A
Alex Dima 已提交
78
		if (this._state) {
79
			if (this._state.editor !== this._editor) {
M
Matt Bierner 已提交
80
				this._state = undefined;
E
Erich Gamma 已提交
81 82 83
			}
		}

M
Matt Bierner 已提交
84
		let promise: Promise<void> = Promise.resolve(void 0);
E
Erich Gamma 已提交
85

86 87 88 89 90 91 92 93
		if (!this._state) {
			promise = provideSelectionRanges(model, selection.getStartPosition(), CancellationToken.None).then(ranges => {
				if (!arrays.isNonEmptyArray(ranges)) {
					// invalid result
					return;
				}
				if (!this._editor.hasModel() || !this._editor.getSelection().equalsSelection(selection)) {
					// invalid editor state
E
Erich Gamma 已提交
94 95 96
					return;
				}

M
Matt Bierner 已提交
97
				let lastState: State | undefined;
98
				ranges.filter(range => {
E
Erich Gamma 已提交
99 100 101
					// filter ranges inside the selection
					return range.containsPosition(selection.getStartPosition()) && range.containsPosition(selection.getEndPosition());

102
				}).forEach(range => {
E
Erich Gamma 已提交
103
					// create ranges
104
					const state = new State(this._editor);
E
Erich Gamma 已提交
105 106 107 108 109 110 111 112 113
					state.selection = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
					if (lastState) {
						state.next = lastState;
						lastState.previous = state;
					}
					lastState = state;
				});

				// insert current selection
114
				const editorState = new State(this._editor);
E
Erich Gamma 已提交
115 116 117 118
				editorState.next = lastState;
				if (lastState) {
					lastState.previous = editorState;
				}
A
Alex Dima 已提交
119
				this._state = editorState;
E
Erich Gamma 已提交
120 121

				// listen to caret move and forget about state
122
				const unhook = this._editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
A
Alex Dima 已提交
123
					if (this._ignoreSelection) {
E
Erich Gamma 已提交
124 125
						return;
					}
M
Matt Bierner 已提交
126
					this._state = undefined;
A
Alex Dima 已提交
127
					unhook.dispose();
E
Erich Gamma 已提交
128 129 130 131 132 133
				});
			});
		}

		return promise.then(() => {

A
Alex Dima 已提交
134
			if (!this._state) {
E
Erich Gamma 已提交
135 136 137
				return;
			}

A
Alex Dima 已提交
138 139
			this._state = forward ? this._state.next : this._state.previous;
			if (!this._state) {
E
Erich Gamma 已提交
140 141 142
				return;
			}

A
Alex Dima 已提交
143
			this._ignoreSelection = true;
E
Erich Gamma 已提交
144
			try {
M
Matt Bierner 已提交
145
				if (this._state.selection) {
146
					this._editor.setSelection(this._state.selection);
M
Matt Bierner 已提交
147
				}
E
Erich Gamma 已提交
148
			} finally {
A
Alex Dima 已提交
149
				this._ignoreSelection = false;
E
Erich Gamma 已提交
150 151
			}

A
Alex Dima 已提交
152
			return;
E
Erich Gamma 已提交
153 154 155 156
		});
	}
}

A
Alex Dima 已提交
157
abstract class AbstractSmartSelect extends EditorAction {
E
Erich Gamma 已提交
158

A
Alex Dima 已提交
159
	private _forward: boolean;
E
Erich Gamma 已提交
160

J
Johannes Rieken 已提交
161
	constructor(forward: boolean, opts: IActionOptions) {
162
		super(opts);
A
Alex Dima 已提交
163
		this._forward = forward;
E
Erich Gamma 已提交
164 165
	}

166
	async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
167 168
		let controller = SmartSelectController.get(editor);
		if (controller) {
169
			await controller.run(this._forward);
170
		}
A
Alex Dima 已提交
171 172
	}
}
E
Erich Gamma 已提交
173

A
Alex Dima 已提交
174 175
class GrowSelectionAction extends AbstractSmartSelect {
	constructor() {
176 177 178 179 180 181
		super(true, {
			id: 'editor.action.smartSelect.grow',
			label: nls.localize('smartSelect.grow', "Expand Select"),
			alias: 'Expand Select',
			precondition: null,
			kbOpts: {
182
				kbExpr: EditorContextKeys.editorTextFocus,
183
				primary: KeyMod.Shift | KeyMod.Alt | KeyCode.RightArrow,
A
Alex Dima 已提交
184
				mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyMod.Shift | KeyCode.RightArrow },
185
				weight: KeybindingWeight.EditorContrib
186 187 188 189 190 191
			},
			menubarOpts: {
				menuId: MenuId.MenubarSelectionMenu,
				group: '1_basic',
				title: nls.localize({ key: 'miSmartSelectGrow', comment: ['&& denotes a mnemonic'] }, "&&Expand Selection"),
				order: 2
192 193
			}
		});
A
Alex Dima 已提交
194 195
	}
}
E
Erich Gamma 已提交
196

A
Alex Dima 已提交
197 198
class ShrinkSelectionAction extends AbstractSmartSelect {
	constructor() {
199 200 201 202 203 204
		super(false, {
			id: 'editor.action.smartSelect.shrink',
			label: nls.localize('smartSelect.shrink', "Shrink Select"),
			alias: 'Shrink Select',
			precondition: null,
			kbOpts: {
205
				kbExpr: EditorContextKeys.editorTextFocus,
206
				primary: KeyMod.Shift | KeyMod.Alt | KeyCode.LeftArrow,
A
Alex Dima 已提交
207
				mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyMod.Shift | KeyCode.LeftArrow },
208
				weight: KeybindingWeight.EditorContrib
209 210 211 212 213 214
			},
			menubarOpts: {
				menuId: MenuId.MenubarSelectionMenu,
				group: '1_basic',
				title: nls.localize({ key: 'miSmartSelectShrink', comment: ['&& denotes a mnemonic'] }, "&&Shrink Selection"),
				order: 3
215 216
			}
		});
E
Erich Gamma 已提交
217 218
	}
}
219

220
registerEditorContribution(SmartSelectController);
221 222
registerEditorAction(GrowSelectionAction);
registerEditorAction(ShrinkSelectionAction);
223 224 225 226 227 228 229

export function provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<Range[] | undefined | null> {
	const provider = modes.SelectionRangeRegistry.ordered(model);
	return first(provider.map(pro => () => asThenable(() => pro.provideSelectionRanges(model, position, token))), arrays.isNonEmptyArray);
}

modes.SelectionRangeRegistry.register('*', new DefaultSelectionRangeProvider());