提交 d81a1bcd 编写于 作者: J Johannes Rieken

add internal api for selection ranges provider, #63935

上级 b10fc8fc
...@@ -1031,6 +1031,14 @@ export interface DocumentColorProvider { ...@@ -1031,6 +1031,14 @@ export interface DocumentColorProvider {
*/ */
provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): ProviderResult<IColorPresentation[]>; provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): ProviderResult<IColorPresentation[]>;
} }
export interface SelectionRangeProvider {
/**
* Provide ranges that should be selected from the given position.
*/
provideSelectionRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<Range[]>;
}
export interface FoldingContext { export interface FoldingContext {
} }
/** /**
...@@ -1337,6 +1345,11 @@ export const LinkProviderRegistry = new LanguageFeatureRegistry<LinkProvider>(); ...@@ -1337,6 +1345,11 @@ export const LinkProviderRegistry = new LanguageFeatureRegistry<LinkProvider>();
*/ */
export const ColorProviderRegistry = new LanguageFeatureRegistry<DocumentColorProvider>(); export const ColorProviderRegistry = new LanguageFeatureRegistry<DocumentColorProvider>();
/**
* @internal
*/
export const SelectionRangeRegistry = new LanguageFeatureRegistry<SelectionRangeProvider>();
/** /**
* @internal * @internal
*/ */
......
...@@ -3,52 +3,20 @@ ...@@ -3,52 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri'; import { SelectionRangeProvider } from 'vs/editor/common/modes';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { build, find } from './tokenTree';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModel } from 'vs/editor/common/model';
import { build, find } from 'vs/editor/contrib/smartSelect/tokenTree';
/** export class DefaultSelectionRangeProvider implements SelectionRangeProvider {
* Interface used to compute a hierachry of logical ranges.
*/
export interface ILogicalSelectionEntry {
type?: string;
range: Range;
}
export class TokenSelectionSupport {
private _modelService: IModelService;
constructor(@IModelService modelService: IModelService) {
this._modelService = modelService;
}
public getRangesToPositionSync(resource: URI, position: Position): ILogicalSelectionEntry[] {
const model = this._modelService.getModel(resource);
let entries: ILogicalSelectionEntry[] = [];
if (model) {
this._doGetRangesToPosition(model, position).forEach(range => {
entries.push({
type: void 0,
range
});
});
}
return entries;
}
private _doGetRangesToPosition(model: ITextModel, position: Position): Range[] {
provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Range[] {
let tree = build(model); let tree = build(model);
let lastRange: Range | undefined;
let node = find(tree, position); let node = find(tree, position);
let ranges: Range[] = []; let ranges: Range[] = [];
let lastRange: Range | undefined;
while (node) { while (node) {
if (!lastRange || !Range.equalsRange(lastRange, node.range)) { if (!lastRange || !Range.equalsRange(lastRange, node.range)) {
ranges.push(node.range); ranges.push(node.range);
...@@ -59,5 +27,4 @@ export class TokenSelectionSupport { ...@@ -59,5 +27,4 @@ export class TokenSelectionSupport {
ranges = ranges.reverse(); ranges = ranges.reverse();
return ranges; return ranges;
} }
} }
...@@ -3,19 +3,23 @@ ...@@ -3,19 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as arrays from 'vs/base/common/arrays'; import * as arrays from 'vs/base/common/arrays';
import { asThenable, first } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, ServicesAccessor, IActionOptions, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ITextModel } from 'vs/editor/common/model';
import { TokenSelectionSupport, ILogicalSelectionEntry } from './tokenSelectionSupport'; import * as modes from 'vs/editor/common/modes';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { DefaultSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/defaultProvider';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import * as nls from 'vs/nls';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { MenuId } from 'vs/platform/actions/common/actions'; import { MenuId } from 'vs/platform/actions/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
// --- selection state machine // --- selection state machine
...@@ -42,62 +46,62 @@ class SmartSelectController implements IEditorContribution { ...@@ -42,62 +46,62 @@ class SmartSelectController implements IEditorContribution {
return editor.getContribution<SmartSelectController>(SmartSelectController.ID); return editor.getContribution<SmartSelectController>(SmartSelectController.ID);
} }
private _tokenSelectionSupport: TokenSelectionSupport; private _editor: ICodeEditor;
private _state?: State; private _state?: State;
private _ignoreSelection: boolean; private _ignoreSelection: boolean;
constructor( constructor(editor: ICodeEditor) {
private editor: ICodeEditor, this._editor = editor;
@IInstantiationService instantiationService: IInstantiationService
) {
this._tokenSelectionSupport = instantiationService.createInstance(TokenSelectionSupport);
this._ignoreSelection = false; this._ignoreSelection = false;
} }
public dispose(): void { dispose(): void {
} }
public getId(): string { getId(): string {
return SmartSelectController.ID; return SmartSelectController.ID;
} }
public run(forward: boolean): Promise<void> { run(forward: boolean): Promise<void> | void {
if (!this.editor.hasModel()) { if (!this._editor.hasModel()) {
return Promise.resolve(void 0); return;
} }
const selection = this.editor.getSelection(); const selection = this._editor.getSelection();
const model = this.editor.getModel(); const model = this._editor.getModel();
if (!modes.SelectionRangeRegistry.has(model)) {
return;
}
// forget about current state // forget about current state
if (this._state) { if (this._state) {
if (this._state.editor !== this.editor) { if (this._state.editor !== this._editor) {
this._state = undefined; this._state = undefined;
} }
} }
let promise: Promise<void> = Promise.resolve(void 0); let promise: Promise<void> = Promise.resolve(void 0);
if (!this._state) {
promise = Promise.resolve(this._tokenSelectionSupport.getRangesToPositionSync(model.uri, selection.getStartPosition())).then((elements: ILogicalSelectionEntry[]) => {
if (arrays.isFalsyOrEmpty(elements)) { 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
return; return;
} }
let lastState: State | undefined; let lastState: State | undefined;
elements.filter((element) => { ranges.filter(range => {
if (!this.editor.hasModel()) {
return false;
}
// filter ranges inside the selection // filter ranges inside the selection
const selection = this.editor.getSelection();
const range = new Range(element.range.startLineNumber, element.range.startColumn, element.range.endLineNumber, element.range.endColumn);
return range.containsPosition(selection.getStartPosition()) && range.containsPosition(selection.getEndPosition()); return range.containsPosition(selection.getStartPosition()) && range.containsPosition(selection.getEndPosition());
}).forEach((element) => { }).forEach(range => {
// create ranges // create ranges
const range = element.range; const state = new State(this._editor);
const state = new State(this.editor);
state.selection = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); state.selection = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
if (lastState) { if (lastState) {
state.next = lastState; state.next = lastState;
...@@ -107,7 +111,7 @@ class SmartSelectController implements IEditorContribution { ...@@ -107,7 +111,7 @@ class SmartSelectController implements IEditorContribution {
}); });
// insert current selection // insert current selection
const editorState = new State(this.editor); const editorState = new State(this._editor);
editorState.next = lastState; editorState.next = lastState;
if (lastState) { if (lastState) {
lastState.previous = editorState; lastState.previous = editorState;
...@@ -115,7 +119,7 @@ class SmartSelectController implements IEditorContribution { ...@@ -115,7 +119,7 @@ class SmartSelectController implements IEditorContribution {
this._state = editorState; this._state = editorState;
// listen to caret move and forget about state // listen to caret move and forget about state
const unhook = this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { const unhook = this._editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
if (this._ignoreSelection) { if (this._ignoreSelection) {
return; return;
} }
...@@ -139,7 +143,7 @@ class SmartSelectController implements IEditorContribution { ...@@ -139,7 +143,7 @@ class SmartSelectController implements IEditorContribution {
this._ignoreSelection = true; this._ignoreSelection = true;
try { try {
if (this._state.selection) { if (this._state.selection) {
this.editor.setSelection(this._state.selection); this._editor.setSelection(this._state.selection);
} }
} finally { } finally {
this._ignoreSelection = false; this._ignoreSelection = false;
...@@ -159,12 +163,11 @@ abstract class AbstractSmartSelect extends EditorAction { ...@@ -159,12 +163,11 @@ abstract class AbstractSmartSelect extends EditorAction {
this._forward = forward; this._forward = forward;
} }
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
let controller = SmartSelectController.get(editor); let controller = SmartSelectController.get(editor);
if (controller) { if (controller) {
return controller.run(this._forward); await controller.run(this._forward);
} }
return undefined;
} }
} }
...@@ -217,3 +220,10 @@ class ShrinkSelectionAction extends AbstractSmartSelect { ...@@ -217,3 +220,10 @@ class ShrinkSelectionAction extends AbstractSmartSelect {
registerEditorContribution(SmartSelectController); registerEditorContribution(SmartSelectController);
registerEditorAction(GrowSelectionAction); registerEditorAction(GrowSelectionAction);
registerEditorAction(ShrinkSelectionAction); registerEditorAction(ShrinkSelectionAction);
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());
...@@ -7,7 +7,6 @@ import { URI } from 'vs/base/common/uri'; ...@@ -7,7 +7,6 @@ import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { LanguageIdentifier } from 'vs/editor/common/modes'; import { LanguageIdentifier } from 'vs/editor/common/modes';
import { TokenSelectionSupport } from 'vs/editor/contrib/smartSelect/tokenSelectionSupport';
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode'; import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
...@@ -16,6 +15,8 @@ import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/jav ...@@ -16,6 +15,8 @@ import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/jav
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect';
import { CancellationToken } from 'vs/base/common/cancellation';
class MockJSMode extends MockMode { class MockJSMode extends MockMode {
...@@ -39,13 +40,11 @@ class MockJSMode extends MockMode { ...@@ -39,13 +40,11 @@ class MockJSMode extends MockMode {
suite('TokenSelectionSupport', () => { suite('TokenSelectionSupport', () => {
let modelService: ModelServiceImpl | null = null; let modelService: ModelServiceImpl | null = null;
let tokenSelectionSupport: TokenSelectionSupport;
let mode: MockJSMode | null = null; let mode: MockJSMode | null = null;
setup(() => { setup(() => {
const configurationService = new TestConfigurationService(); const configurationService = new TestConfigurationService();
modelService = new ModelServiceImpl(null, configurationService, new TestTextResourcePropertiesService(configurationService)); modelService = new ModelServiceImpl(null, configurationService, new TestTextResourcePropertiesService(configurationService));
tokenSelectionSupport = new TokenSelectionSupport(modelService);
mode = new MockJSMode(); mode = new MockJSMode();
}); });
...@@ -54,13 +53,13 @@ suite('TokenSelectionSupport', () => { ...@@ -54,13 +53,13 @@ suite('TokenSelectionSupport', () => {
mode.dispose(); mode.dispose();
}); });
function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): void { async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): Promise<void> {
let uri = URI.file('test.js'); let uri = URI.file('test.js');
modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri); let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
let actual = tokenSelectionSupport.getRangesToPositionSync(uri, new Position(lineNumber, column)); let actual = await provideSelectionRanges(model, new Position(lineNumber, column), CancellationToken.None);
let actualStr = actual.map(r => new Range(r.range.startLineNumber, r.range.startColumn, r.range.endLineNumber, r.range.endColumn).toString()); let actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
let desiredStr = ranges.map(r => String(r)); let desiredStr = ranges.map(r => String(r));
assert.deepEqual(actualStr, desiredStr); assert.deepEqual(actualStr, desiredStr);
...@@ -70,7 +69,7 @@ suite('TokenSelectionSupport', () => { ...@@ -70,7 +69,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #1', () => { test('getRangesToPosition #1', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
'function a(bar, foo){', 'function a(bar, foo){',
'\tif (bar) {', '\tif (bar) {',
'\t\treturn (bar + (2 * foo))', '\t\treturn (bar + (2 * foo))',
...@@ -93,7 +92,7 @@ suite('TokenSelectionSupport', () => { ...@@ -93,7 +92,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #56886. Skip empty lines correctly.', () => { test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
'function a(bar, foo){', 'function a(bar, foo){',
'\tif (bar) {', '\tif (bar) {',
'', '',
...@@ -109,7 +108,7 @@ suite('TokenSelectionSupport', () => { ...@@ -109,7 +108,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => { test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
'function a(bar, foo){', 'function a(bar, foo){',
'\tif (bar) {', '\tif (bar) {',
' ', ' ',
...@@ -127,7 +126,7 @@ suite('TokenSelectionSupport', () => { ...@@ -127,7 +126,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => { test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
' [ ]', ' [ ]',
' { } ', ' { } ',
'( ) ' '( ) '
...@@ -141,7 +140,7 @@ suite('TokenSelectionSupport', () => { ...@@ -141,7 +140,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => { test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
' [] ', ' [] ',
' { } ', ' { } ',
' ( ) ' ' ( ) '
...@@ -154,7 +153,7 @@ suite('TokenSelectionSupport', () => { ...@@ -154,7 +153,7 @@ suite('TokenSelectionSupport', () => {
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => { test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
assertGetRangesToPosition([ return assertGetRangesToPosition([
' [] ', ' [] ',
' { } ', ' { } ',
'selectthis( ) ' 'selectthis( ) '
......
...@@ -5294,6 +5294,13 @@ declare namespace monaco.languages { ...@@ -5294,6 +5294,13 @@ declare namespace monaco.languages {
provideColorPresentations(model: editor.ITextModel, colorInfo: IColorInformation, token: CancellationToken): ProviderResult<IColorPresentation[]>; provideColorPresentations(model: editor.ITextModel, colorInfo: IColorInformation, token: CancellationToken): ProviderResult<IColorPresentation[]>;
} }
export interface SelectionRangeProvider {
/**
* Provide ranges that should be selected from the given position.
*/
provideSelectionRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<Range[]>;
}
export interface FoldingContext { export interface FoldingContext {
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册