mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 20:10:11 -04:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -21,6 +21,7 @@ import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/edito
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.'));
|
||||
|
||||
@@ -322,3 +323,13 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .bracket-match { border: 1px solid ${bracketMatchBorder}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
// Go to menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '5_infile_nav',
|
||||
command: {
|
||||
id: 'editor.action.jumpToBracket',
|
||||
title: nls.localize({ key: 'miGoToBracket', comment: ['&& denotes a mnemonic'] }, "Go to &&Bracket")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
@@ -28,8 +28,8 @@ class MoveCaretAction extends EditorAction {
|
||||
let commands: ICommand[] = [];
|
||||
let selections = editor.getSelections();
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
commands.push(new MoveCaretCommand(selections[i], this.left));
|
||||
for (const selection of selections) {
|
||||
commands.push(new MoveCaretCommand(selection, this.left));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { flatten, mergeSort, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -13,73 +13,59 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
|
||||
import { CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind, CodeActionFilter } from './codeActionTrigger';
|
||||
|
||||
export function getCodeActions(
|
||||
model: ITextModel,
|
||||
rangeOrSelection: Range | Selection,
|
||||
trigger: CodeActionTrigger,
|
||||
token: CancellationToken
|
||||
): Promise<CodeAction[]> {
|
||||
const filter = trigger.filter || {};
|
||||
|
||||
export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Selection, trigger?: CodeActionTrigger, token: CancellationToken = CancellationToken.None): Promise<CodeAction[]> {
|
||||
const codeActionContext: CodeActionContext = {
|
||||
only: trigger && trigger.filter && trigger.filter.kind ? trigger.filter.kind.value : undefined,
|
||||
trigger: trigger && trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
only: filter.kind ? filter.kind.value : undefined,
|
||||
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
};
|
||||
|
||||
const promises = CodeActionProviderRegistry.all(model)
|
||||
.filter(provider => {
|
||||
if (!provider.providedCodeActionKinds) {
|
||||
return true;
|
||||
if (filter.kind && CodeActionKind.Source.contains(filter.kind) && rangeOrSelection.isEmpty()) {
|
||||
rangeOrSelection = model.getFullModelRange();
|
||||
}
|
||||
|
||||
const promises = getCodeActionProviders(model, filter).map(provider => {
|
||||
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => action && filtersAction(filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Avoid calling providers that we know will not return code actions of interest
|
||||
return provider.providedCodeActionKinds.some(providedKind => {
|
||||
// Filter out actions by kind
|
||||
// The provided kind can be either a subset of a superset of the filtered kind
|
||||
if (trigger && trigger.filter && trigger.filter.kind && !(trigger.filter.kind.contains(providedKind) || new CodeActionKind(providedKind).contains(trigger.filter.kind.value))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (trigger && CodeActionKind.Source.contains(providedKind) && (!trigger.filter || !trigger.filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.map(support => {
|
||||
return Promise.resolve(support.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => isValidAction(trigger && trigger.filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(flatten)
|
||||
.then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator));
|
||||
}
|
||||
|
||||
function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction): boolean {
|
||||
return action && isValidActionKind(filter, action.kind);
|
||||
}
|
||||
|
||||
function isValidActionKind(filter: CodeActionFilter | undefined, kind: string | undefined): boolean {
|
||||
// Filter out actions by kind
|
||||
if (filter && filter.kind && (!kind || !filter.kind.contains(kind))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (kind && CodeActionKind.Source.contains(kind) && (!filter || !filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
function getCodeActionProviders(
|
||||
model: ITextModel,
|
||||
filter: CodeActionFilter
|
||||
) {
|
||||
return CodeActionProviderRegistry.all(model)
|
||||
// Don't include providers that we know will not return code actions of interest
|
||||
.filter(provider => {
|
||||
if (!provider.providedCodeActionKinds) {
|
||||
// We don't know what type of actions this provider will return.
|
||||
return true;
|
||||
}
|
||||
return provider.providedCodeActionKinds.some(kind => mayIncludeActionsOfKind(filter, new CodeActionKind(kind)));
|
||||
});
|
||||
}
|
||||
|
||||
function codeActionsComparator(a: CodeAction, b: CodeAction): number {
|
||||
@@ -107,5 +93,9 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args)
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
return getCodeActions(model, model.validateRange(range), { type: 'manual', filter: { includeSourceActions: true } });
|
||||
return getCodeActions(
|
||||
model,
|
||||
model.validateRange(range),
|
||||
{ type: 'manual', filter: { includeSourceActions: true } },
|
||||
CancellationToken.None);
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionModel, CodeActionsComputeEvent, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
||||
import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
|
||||
import { CodeActionContextMenu } from './codeActionWidget';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
@@ -69,7 +69,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
this._disposables.push(
|
||||
this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })),
|
||||
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
|
||||
this._model.onDidChangeFixes(e => this._onCodeActionsEvent(e)),
|
||||
this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e)),
|
||||
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
|
||||
);
|
||||
}
|
||||
@@ -79,39 +79,39 @@ export class QuickFixController implements IEditorContribution {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
private _onCodeActionsEvent(e: CodeActionsComputeEvent): void {
|
||||
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
|
||||
if (this._activeRequest) {
|
||||
this._activeRequest.cancel();
|
||||
this._activeRequest = undefined;
|
||||
}
|
||||
|
||||
if (e && e.actions) {
|
||||
this._activeRequest = e.actions;
|
||||
}
|
||||
if (newState.type === CodeActionsState.Type.Triggered) {
|
||||
this._activeRequest = newState.actions;
|
||||
|
||||
if (e && e.actions && e.trigger.filter && e.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
// Apply if we only have one action or requested autoApply, otherwise show menu
|
||||
e.actions.then(fixes => {
|
||||
if (fixes.length > 0 && e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
} else {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
}
|
||||
}).catch(onUnexpectedError);
|
||||
return;
|
||||
}
|
||||
if (newState.trigger.filter && newState.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
newState.actions.then(fixes => {
|
||||
if (fixes.length > 0) {
|
||||
// Apply if we only have one action or requested autoApply
|
||||
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
|
||||
if (e && e.trigger.type === 'manual') {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
} else if (e && e.actions) {
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
// * manage light bulb
|
||||
if (this._codeActionContextMenu.isVisible) {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
}).catch(onUnexpectedError);
|
||||
} else if (newState.trigger.type === 'manual') {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
} else {
|
||||
this._lightBulbWidget.model = e;
|
||||
// auto magically triggered
|
||||
// * update an existing list of code actions
|
||||
// * manage light bulb
|
||||
if (this._codeActionContextMenu.isVisible) {
|
||||
this._codeActionContextMenu.show(newState.actions, newState.position);
|
||||
} else {
|
||||
this._lightBulbWidget.tryShow(newState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._lightBulbWidget.hide();
|
||||
@@ -122,13 +122,11 @@ export class QuickFixController implements IEditorContribution {
|
||||
return QuickFixController.ID;
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(coords: { x: number, y: number }): void {
|
||||
if (this._lightBulbWidget.model && this._lightBulbWidget.model.actions) {
|
||||
this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords);
|
||||
}
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void {
|
||||
this._codeActionContextMenu.show(e.state.actions, e);
|
||||
}
|
||||
|
||||
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Thenable<CodeAction[] | undefined> {
|
||||
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise<CodeAction[] | undefined> {
|
||||
return this._model.trigger({ type: 'manual', filter, autoApply });
|
||||
}
|
||||
|
||||
@@ -158,7 +156,7 @@ export async function applyCodeAction(
|
||||
await bulkEditService.apply(action.edit, { editor });
|
||||
}
|
||||
if (action.command) {
|
||||
await commandService.executeCommand(action.command.id, ...action.command.arguments);
|
||||
await commandService.executeCommand(action.command.id, ...(action.command.arguments || []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +166,10 @@ function showCodeActionsForEditorSelection(
|
||||
filter?: CodeActionFilter,
|
||||
autoApply?: CodeActionAutoApply
|
||||
) {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = QuickFixController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
@@ -206,38 +208,41 @@ export class QuickFixAction extends EditorAction {
|
||||
|
||||
|
||||
class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any): CodeActionCommandArgs {
|
||||
public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
|
||||
return new CodeActionCommandArgs(defaults.kind, defaults.apply, false);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg));
|
||||
CodeActionCommandArgs.getKindFromUser(arg, defaults.kind),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply),
|
||||
CodeActionCommandArgs.getPreferredUser(arg));
|
||||
}
|
||||
|
||||
private static getApplyFromUser(arg: any) {
|
||||
private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) {
|
||||
switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
|
||||
case 'first':
|
||||
return CodeActionAutoApply.First;
|
||||
|
||||
case 'never':
|
||||
return CodeActionAutoApply.Never;
|
||||
|
||||
case 'ifsingle':
|
||||
default:
|
||||
return CodeActionAutoApply.IfSingle;
|
||||
case 'first': return CodeActionAutoApply.First;
|
||||
case 'never': return CodeActionAutoApply.Never;
|
||||
case 'ifsingle': return CodeActionAutoApply.IfSingle;
|
||||
default: return defaultAutoApply;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any) {
|
||||
private static getKindFromUser(arg: any, defaultKind: CodeActionKind) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: CodeActionKind.Empty;
|
||||
: defaultKind;
|
||||
}
|
||||
|
||||
private static getPreferredUser(arg: any): boolean {
|
||||
return typeof arg.preferred === 'boolean'
|
||||
? arg.preferred
|
||||
: false;
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public readonly kind: CodeActionKind,
|
||||
public readonly apply: CodeActionAutoApply
|
||||
public readonly apply: CodeActionAutoApply,
|
||||
public readonly preferred: boolean,
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -253,8 +258,17 @@ export class CodeActionCommand extends EditorCommand {
|
||||
}
|
||||
|
||||
public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg);
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply);
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Empty,
|
||||
apply: CodeActionAutoApply.IfSingle,
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
|
||||
{
|
||||
kind: args.kind,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,11 +301,18 @@ export class RefactorAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Refactor,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
|
||||
{ kind: CodeActionKind.Refactor },
|
||||
CodeActionAutoApply.Never);
|
||||
{
|
||||
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,11 +337,19 @@ export class SourceAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
kind: CodeActionKind.Source,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.source.noneMessage', "No source actions available"),
|
||||
{ kind: CodeActionKind.Source, includeSourceActions: true },
|
||||
CodeActionAutoApply.Never);
|
||||
{
|
||||
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,4 +379,38 @@ export class OrganizeImportsAction extends EditorAction {
|
||||
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AutoFixAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.autoFix';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: AutoFixAction.Id,
|
||||
label: nls.localize('autoFix.label', "Auto Fix..."),
|
||||
alias: 'Auto Fix',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.writable,
|
||||
contextKeyForSupportedActions(CodeActionKind.QuickFix)),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.US_DOT,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_DOT
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return showCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
|
||||
{
|
||||
kind: CodeActionKind.QuickFix,
|
||||
onlyIncludePreferredActions: true
|
||||
},
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction, AutoFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
|
||||
|
||||
registerEditorContribution(QuickFixController);
|
||||
@@ -12,4 +12,5 @@ registerEditorAction(QuickFixAction);
|
||||
registerEditorAction(RefactorAction);
|
||||
registerEditorAction(SourceAction);
|
||||
registerEditorAction(OrganizeImportsAction);
|
||||
registerEditorAction(AutoFixAction);
|
||||
registerEditorCommand(new CodeActionCommand());
|
||||
|
||||
@@ -28,7 +28,7 @@ export class CodeActionOracle {
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
private readonly _markerService: IMarkerService,
|
||||
private _signalChange: (e: CodeActionsComputeEvent) => any,
|
||||
private _signalChange: (newState: CodeActionsState.State) => void,
|
||||
private readonly _delay: number = 250,
|
||||
private readonly _progressService?: IProgressService,
|
||||
) {
|
||||
@@ -112,26 +112,16 @@ export class CodeActionOracle {
|
||||
return selection ? selection : undefined;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Thenable<CodeAction[] | undefined> {
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise<CodeAction[] | undefined> {
|
||||
if (!selection) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
trigger,
|
||||
rangeOrSelection: undefined,
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
trigger,
|
||||
rangeOrSelection: undefined,
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
this._signalChange(CodeActionsState.Empty);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
@@ -143,22 +133,38 @@ export class CodeActionOracle {
|
||||
this._progressService.showWhile(actions, 250);
|
||||
}
|
||||
|
||||
this._signalChange({
|
||||
this._signalChange(new CodeActionsState.Triggered(
|
||||
trigger,
|
||||
rangeOrSelection: selection,
|
||||
selection,
|
||||
position,
|
||||
actions
|
||||
});
|
||||
));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CodeActionsComputeEvent {
|
||||
trigger: CodeActionTrigger;
|
||||
rangeOrSelection: Range | Selection | undefined;
|
||||
position: Position | undefined;
|
||||
actions: CancelablePromise<CodeAction[]> | undefined;
|
||||
export namespace CodeActionsState {
|
||||
|
||||
export const enum Type {
|
||||
Empty,
|
||||
Triggered,
|
||||
}
|
||||
|
||||
export const Empty = new class { readonly type = Type.Empty; };
|
||||
|
||||
export class Triggered {
|
||||
readonly type = Type.Triggered;
|
||||
|
||||
constructor(
|
||||
public readonly trigger: CodeActionTrigger,
|
||||
public readonly rangeOrSelection: Range | Selection,
|
||||
public readonly position: Position,
|
||||
public readonly actions: CancelablePromise<CodeAction[]>,
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State = typeof Empty | Triggered;
|
||||
}
|
||||
|
||||
export class CodeActionModel {
|
||||
@@ -166,7 +172,8 @@ export class CodeActionModel {
|
||||
private _editor: ICodeEditor;
|
||||
private _markerService: IMarkerService;
|
||||
private _codeActionOracle?: CodeActionOracle;
|
||||
private _onDidChangeFixes = new Emitter<CodeActionsComputeEvent>();
|
||||
private _state: CodeActionsState.State = CodeActionsState.Empty;
|
||||
private _onDidChangeState = new Emitter<CodeActionsState.State>();
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _supportedCodeActions: IContextKey<string>;
|
||||
|
||||
@@ -178,7 +185,7 @@ export class CodeActionModel {
|
||||
|
||||
this._disposables.push(this._editor.onDidChangeModel(() => this._update()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update()));
|
||||
this._disposables.push(CodeActionProviderRegistry.onDidChange(this._update, this));
|
||||
this._disposables.push(CodeActionProviderRegistry.onDidChange(() => this._update()));
|
||||
|
||||
this._update();
|
||||
}
|
||||
@@ -188,23 +195,26 @@ export class CodeActionModel {
|
||||
dispose(this._codeActionOracle);
|
||||
}
|
||||
|
||||
get onDidChangeFixes(): Event<CodeActionsComputeEvent> {
|
||||
return this._onDidChangeFixes.event;
|
||||
get onDidChangeState(): Event<CodeActionsState.State> {
|
||||
return this._onDidChangeState.event;
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
|
||||
if (this._codeActionOracle) {
|
||||
this._codeActionOracle.dispose();
|
||||
this._codeActionOracle = undefined;
|
||||
this._onDidChangeFixes.fire(undefined);
|
||||
}
|
||||
|
||||
if (this._state.type === CodeActionsState.Type.Triggered) {
|
||||
// this._state.actions.cancel();
|
||||
}
|
||||
this.setState(CodeActionsState.Empty);
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (model
|
||||
&& CodeActionProviderRegistry.has(model)
|
||||
&& !this._editor.getConfiguration().readOnly) {
|
||||
|
||||
&& !this._editor.getConfiguration().readOnly
|
||||
) {
|
||||
const supportedActions: string[] = [];
|
||||
for (const provider of CodeActionProviderRegistry.all(model)) {
|
||||
if (Array.isArray(provider.providedCodeActionKinds)) {
|
||||
@@ -214,17 +224,25 @@ export class CodeActionModel {
|
||||
|
||||
this._supportedCodeActions.set(supportedActions.join(' '));
|
||||
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p), undefined, this._progressService);
|
||||
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, newState => this.setState(newState), undefined, this._progressService);
|
||||
this._codeActionOracle.trigger({ type: 'auto' });
|
||||
} else {
|
||||
this._supportedCodeActions.reset();
|
||||
}
|
||||
}
|
||||
|
||||
trigger(trigger: CodeActionTrigger): Thenable<CodeAction[] | undefined> {
|
||||
public trigger(trigger: CodeActionTrigger): Promise<CodeAction[] | undefined> {
|
||||
if (this._codeActionOracle) {
|
||||
return this._codeActionOracle.trigger(trigger);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private setState(newState: CodeActionsState.State) {
|
||||
if (newState === this._state) {
|
||||
return;
|
||||
}
|
||||
this._state = newState;
|
||||
this._onDidChangeState.fire(newState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
@@ -13,25 +14,72 @@ export class CodeActionKind {
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
public static readonly Source = new CodeActionKind('source');
|
||||
public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports');
|
||||
public static readonly SourceFixAll = new CodeActionKind('source.fixAll');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public contains(other: string): boolean {
|
||||
return this.value === other || startsWith(other, this.value + CodeActionKind.sep);
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
|
||||
public intersects(other: CodeActionKind): boolean {
|
||||
return this.contains(other) || other.contains(this);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CodeActionAutoApply {
|
||||
IfSingle = 1,
|
||||
First = 2,
|
||||
Never = 3
|
||||
IfSingle,
|
||||
First,
|
||||
Never,
|
||||
}
|
||||
|
||||
export interface CodeActionFilter {
|
||||
readonly kind?: CodeActionKind;
|
||||
readonly includeSourceActions?: boolean;
|
||||
readonly onlyIncludePreferredActions?: boolean;
|
||||
}
|
||||
|
||||
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
|
||||
// A provided kind may be a subset or superset of our filtered kind.
|
||||
if (filter.kind && !filter.kind.intersects(providedKind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean {
|
||||
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
|
||||
|
||||
// Filter out actions by kind
|
||||
if (filter.kind) {
|
||||
if (!actionKind || !filter.kind.contains(actionKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (!filter.includeSourceActions) {
|
||||
if (actionKind && CodeActionKind.Source.contains(actionKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.onlyIncludePreferredActions) {
|
||||
if (!action.isPreferred) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export interface CodeActionTrigger {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { always } from 'vs/base/common/async';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -27,48 +26,45 @@ export class CodeActionContextMenu {
|
||||
private readonly _onApplyCodeAction: (action: CodeAction) => Promise<any>
|
||||
) { }
|
||||
|
||||
show(fixes: Thenable<CodeAction[]>, at: { x: number; y: number } | Position) {
|
||||
|
||||
const actionsPromise = fixes ? fixes.then(value => {
|
||||
return value.map(action => {
|
||||
return new Action(action.command ? action.command.id : action.title, action.title, undefined, true, () => {
|
||||
return always(
|
||||
this._onApplyCodeAction(action),
|
||||
() => this._onDidExecuteCodeAction.fire(undefined));
|
||||
});
|
||||
});
|
||||
}).then(actions => {
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
return actions;
|
||||
}) : Promise.resolve([] as Action[]);
|
||||
|
||||
actionsPromise.then(actions => {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at;
|
||||
},
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
});
|
||||
async show(actionsToShow: Promise<CodeAction[]>, at?: { x: number; y: number } | Position): Promise<void> {
|
||||
const codeActions = await actionsToShow;
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
const actions = codeActions.map(action => this.codeActionToAction(action));
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at || { x: 0, y: 0 };
|
||||
},
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
});
|
||||
}
|
||||
|
||||
private codeActionToAction(action: CodeAction): Action {
|
||||
const id = action.command ? action.command.id : action.title;
|
||||
const title = action.isPreferred ? `${action.title} ★` : action.title;
|
||||
return new Action(id, title, undefined, true, () =>
|
||||
this._onApplyCodeAction(action)
|
||||
.finally(() => this._onDidExecuteCodeAction.fire(undefined)));
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
private _toCoords(position: Position): { x: number, y: number } {
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
this._editor.revealPosition(position, ScrollType.Immediate);
|
||||
this._editor.render();
|
||||
|
||||
|
||||
@@ -6,45 +6,49 @@
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./lightBulbWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionsComputeEvent } from './codeActionModel';
|
||||
import { CodeActionsState } from './codeActionModel';
|
||||
|
||||
export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
|
||||
private static readonly _posPref = [ContentWidgetPositionPreference.EXACT];
|
||||
|
||||
private readonly _domNode: HTMLDivElement;
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
private readonly _onClick = new Emitter<{ x: number, y: number }>();
|
||||
|
||||
readonly onClick: Event<{ x: number, y: number }> = this._onClick.event;
|
||||
private readonly _onClick = this._register(new Emitter<{ x: number; y: number; state: CodeActionsState.Triggered }>());
|
||||
public readonly onClick = this._onClick.event;
|
||||
|
||||
private _position: IContentWidgetPosition | null;
|
||||
private _model: CodeActionsComputeEvent | null;
|
||||
private _state: CodeActionsState.State = CodeActionsState.Empty;
|
||||
private _futureFixes = new CancellationTokenSource();
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'lightbulb-glyph';
|
||||
|
||||
this._editor = editor;
|
||||
this._editor.addContentWidget(this);
|
||||
|
||||
this._disposables.push(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
|
||||
this._disposables.push(this._editor.onDidChangeModelContent(_ => {
|
||||
this._register(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
|
||||
this._register(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
|
||||
this._register(this._editor.onDidChangeModelContent(_ => {
|
||||
// cancel when the line in question has been removed
|
||||
const editorModel = this._editor.getModel();
|
||||
if (!this.model || !this.model.position || !editorModel || this.model.position.lineNumber >= editorModel.getLineCount()) {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered || !editorModel || this._state.position.lineNumber >= editorModel.getLineCount()) {
|
||||
this._futureFixes.cancel();
|
||||
}
|
||||
}));
|
||||
this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => {
|
||||
this._register(dom.addStandardDisposableListener(this._domNode, 'click', e => {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that focus / cursor location is not lost when clicking widget icon
|
||||
this._editor.focus();
|
||||
// a bit of extra work to make sure the menu
|
||||
@@ -53,16 +57,17 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
const { lineHeight } = this._editor.getConfiguration();
|
||||
|
||||
let pad = Math.floor(lineHeight / 3);
|
||||
if (this._position && this._model && this._model.position && this._position.position !== null && this._position.position.lineNumber < this._model.position.lineNumber) {
|
||||
if (this._position && this._position.position !== null && this._position.position.lineNumber < this._state.position.lineNumber) {
|
||||
pad += lineHeight;
|
||||
}
|
||||
|
||||
this._onClick.fire({
|
||||
x: e.posx,
|
||||
y: top + height + pad
|
||||
y: top + height + pad,
|
||||
state: this._state
|
||||
});
|
||||
}));
|
||||
this._disposables.push(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
|
||||
this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
|
||||
if ((e.buttons & 1) !== 1) {
|
||||
return;
|
||||
}
|
||||
@@ -75,7 +80,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
monitor.dispose();
|
||||
});
|
||||
}));
|
||||
this._disposables.push(this._editor.onDidChangeConfiguration(e => {
|
||||
this._register(this._editor.onDidChangeConfiguration(e => {
|
||||
// hide when told to do so
|
||||
if (e.contribInfo && !this._editor.getConfiguration().contribInfo.lightbulbEnabled) {
|
||||
this.hide();
|
||||
@@ -84,7 +89,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
super.dispose();
|
||||
this._editor.removeContentWidget(this);
|
||||
}
|
||||
|
||||
@@ -100,9 +105,9 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
set model(value: CodeActionsComputeEvent | null) {
|
||||
tryShow(newState: CodeActionsState.State) {
|
||||
|
||||
if (!value || this._position && (!value.position || this._position.position && this._position.position.lineNumber !== value.position.lineNumber)) {
|
||||
if (newState.type !== CodeActionsState.Type.Triggered || this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) {
|
||||
// hide when getting a 'hide'-request or when currently
|
||||
// showing on another line
|
||||
this.hide();
|
||||
@@ -113,14 +118,14 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
|
||||
this._futureFixes = new CancellationTokenSource();
|
||||
const { token } = this._futureFixes;
|
||||
this._model = value;
|
||||
this._state = newState;
|
||||
|
||||
if (!this._model || !this._model.actions) {
|
||||
if (this._state.type === CodeActionsState.Empty.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = this._model.rangeOrSelection;
|
||||
this._model.actions.then(fixes => {
|
||||
const selection = this._state.rangeOrSelection;
|
||||
this._state.actions.then(fixes => {
|
||||
if (!token.isCancellationRequested && fixes && fixes.length > 0 && selection) {
|
||||
this._show();
|
||||
} else {
|
||||
@@ -131,10 +136,6 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
});
|
||||
}
|
||||
|
||||
get model(): CodeActionsComputeEvent | null {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
set title(value: string) {
|
||||
this._domNode.title = value;
|
||||
}
|
||||
@@ -148,10 +149,10 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
if (!config.contribInfo.lightbulbEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!this._model || !this._model.position) {
|
||||
if (this._state.type !== CodeActionsState.Type.Triggered) {
|
||||
return;
|
||||
}
|
||||
const { lineNumber, column } = this._model.position;
|
||||
const { lineNumber, column } = this._state.position;
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
@@ -188,7 +189,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
|
||||
hide(): void {
|
||||
this._position = null;
|
||||
this._model = null;
|
||||
this._state = CodeActionsState.Empty;
|
||||
this._futureFixes.cancel();
|
||||
this._editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRe
|
||||
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
suite('CodeAction', () => {
|
||||
|
||||
@@ -115,7 +117,7 @@ suite('CodeAction', () => {
|
||||
testData.tsLint.abc
|
||||
];
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1));
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None);
|
||||
assert.equal(actions.length, 6);
|
||||
assert.deepEqual(actions, expected);
|
||||
});
|
||||
@@ -134,20 +136,20 @@ suite('CodeAction', () => {
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 2);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
assert.strictEqual(actions[1].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a.b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 0);
|
||||
}
|
||||
});
|
||||
@@ -156,14 +158,14 @@ suite('CodeAction', () => {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] {
|
||||
return [
|
||||
{ title: context.only, kind: context.only }
|
||||
{ title: context.only || '', kind: context.only }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
});
|
||||
@@ -181,13 +183,13 @@ suite('CodeAction', () => {
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'b');
|
||||
}
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } });
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
}
|
||||
@@ -211,8 +213,53 @@ suite('CodeAction', () => {
|
||||
filter: {
|
||||
kind: CodeActionKind.QuickFix
|
||||
}
|
||||
});
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 0);
|
||||
assert.strictEqual(wasInvoked, false);
|
||||
});
|
||||
|
||||
test('getCodeActions requests for source actions should expand source actions range to entire document #53525', async function () {
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions(model: ITextModel, range: Range): CodeAction[] {
|
||||
return [{
|
||||
title: rangeToString(range),
|
||||
kind: CodeActionKind.Source.value,
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 1, 1), {
|
||||
type: 'manual',
|
||||
filter: {
|
||||
kind: CodeActionKind.Source,
|
||||
includeSourceActions: true,
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, rangeToString(model.getFullModelRange()));
|
||||
}
|
||||
|
||||
{
|
||||
const range = new Range(1, 1, 1, 2);
|
||||
|
||||
// But we should not expand for non-empty selections
|
||||
const actions = await getCodeActions(model, range, {
|
||||
type: 'manual',
|
||||
filter: {
|
||||
kind: CodeActionKind.Source,
|
||||
includeSourceActions: true,
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, rangeToString(range));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function rangeToString(range: Range): string {
|
||||
return `${range.startLineNumber},${range.startColumn} ${range.endLineNumber},${range.endColumn} `;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionProviderRegistry, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { CodeActionOracle } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
|
||||
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { MarkerService } from 'vs/platform/markers/common/markerService';
|
||||
|
||||
@@ -47,11 +47,11 @@ suite('CodeAction', () => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
e.actions!.then(fixes => {
|
||||
oracle.dispose();
|
||||
assert.equal(fixes.length, 1);
|
||||
done();
|
||||
@@ -85,10 +85,10 @@ suite('CodeAction', () => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
e.actions.then(fixes => {
|
||||
e.actions!.then(fixes => {
|
||||
oracle.dispose();
|
||||
assert.equal(fixes.length, 1);
|
||||
resolve(undefined);
|
||||
@@ -107,7 +107,7 @@ suite('CodeAction', () => {
|
||||
});
|
||||
disposables.push(reg);
|
||||
|
||||
editor.getModel().setValue('// @ts-check\n2\ncon\n');
|
||||
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
|
||||
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4,
|
||||
@@ -120,7 +120,7 @@ suite('CodeAction', () => {
|
||||
// case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker
|
||||
await new Promise(resolve => {
|
||||
|
||||
let oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
let oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.deepEqual(selection.selectionStartLineNumber, 1);
|
||||
@@ -130,7 +130,7 @@ suite('CodeAction', () => {
|
||||
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
|
||||
|
||||
oracle.dispose();
|
||||
resolve(null);
|
||||
resolve(undefined);
|
||||
}, 5);
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
@@ -142,7 +142,7 @@ suite('CodeAction', () => {
|
||||
disposables.push(reg);
|
||||
|
||||
let triggerCount = 0;
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
++triggerCount;
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) {
|
||||
const result: ICodeLensSymbol[] = [];
|
||||
return getCodeLensData(model, CancellationToken.None).then(value => {
|
||||
|
||||
let resolve: Thenable<any>[] = [];
|
||||
let resolve: Promise<any>[] = [];
|
||||
|
||||
for (const item of value) {
|
||||
if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) {
|
||||
|
||||
@@ -27,9 +27,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
private _globalToDispose: IDisposable[];
|
||||
private _localToDispose: IDisposable[];
|
||||
private _lenses: CodeLens[];
|
||||
private _currentFindCodeLensSymbolsPromise: CancelablePromise<ICodeLensData[]>;
|
||||
private _currentFindCodeLensSymbolsPromise: CancelablePromise<ICodeLensData[]> | null;
|
||||
private _modelChangeCounter: number;
|
||||
private _currentResolveCodeLensSymbolsPromise: CancelablePromise<any>;
|
||||
private _currentResolveCodeLensSymbolsPromise: CancelablePromise<any> | null;
|
||||
private _detectVisibleLenses: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
@@ -176,7 +176,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
scrollState.restore(this._editor);
|
||||
} else {
|
||||
// No accessors available
|
||||
this._disposeAllLenses(null, null);
|
||||
this._disposeAllLenses(undefined, undefined);
|
||||
}
|
||||
}));
|
||||
this._localToDispose.push(this._editor.onDidChangeConfiguration(e => {
|
||||
@@ -187,11 +187,11 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
}
|
||||
}));
|
||||
this._localToDispose.push(this._editor.onMouseUp(e => {
|
||||
if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element.tagName === 'A') {
|
||||
if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') {
|
||||
for (const lens of this._lenses) {
|
||||
let command = lens.getCommand(e.target.element as HTMLLinkElement);
|
||||
if (command) {
|
||||
this._commandService.executeCommand(command.id, ...command.arguments).catch(err => this._notificationService.error(err));
|
||||
this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
scheduler.schedule();
|
||||
}
|
||||
|
||||
private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void {
|
||||
let helper = new CodeLensHelper();
|
||||
this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor));
|
||||
if (decChangeAccessor) {
|
||||
@@ -210,13 +210,13 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
private _renderCodeLensSymbols(symbols: ICodeLensData[]): void {
|
||||
if (!this._editor.getModel()) {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let maxLineNumber = this._editor.getModel().getLineCount();
|
||||
let groups: ICodeLensData[][] = [];
|
||||
let lastGroup: ICodeLensData[];
|
||||
let lastGroup: ICodeLensData[] | undefined;
|
||||
|
||||
for (let symbol of symbols) {
|
||||
let line = symbol.symbol.range.startLineNumber;
|
||||
@@ -307,7 +307,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
const promises = toResolve.map((request, i) => {
|
||||
|
||||
const resolvedSymbols = new Array<ICodeLensSymbol>(request.length);
|
||||
const resolvedSymbols = new Array<ICodeLensSymbol | undefined | null>(request.length);
|
||||
const promises = request.map((request, i) => {
|
||||
if (typeof request.provider.resolveCodeLens === 'function') {
|
||||
return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => {
|
||||
@@ -315,7 +315,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
});
|
||||
}
|
||||
resolvedSymbols[i] = request.symbol;
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./codelensWidget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { escape, format } from 'vs/base/common/strings';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
@@ -83,8 +83,8 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
const { fontInfo, lineHeight } = this._editor.getConfiguration();
|
||||
this._domNode.style.height = `${Math.round(lineHeight * 1.1)}px`;
|
||||
this._domNode.style.lineHeight = `${lineHeight}px`;
|
||||
this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * .9)}px`;
|
||||
this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * .45)}px`;
|
||||
this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`;
|
||||
this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`;
|
||||
this._domNode.innerHTML = ' ';
|
||||
}
|
||||
|
||||
@@ -95,9 +95,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
}
|
||||
}
|
||||
|
||||
withCommands(symbols: ICodeLensSymbol[]): void {
|
||||
withCommands(inSymbols: Array<ICodeLensSymbol | undefined | null>): void {
|
||||
this._commands = Object.create(null);
|
||||
symbols = coalesce(symbols);
|
||||
const symbols = coalesce(inSymbols);
|
||||
if (isFalsyOrEmpty(symbols)) {
|
||||
this._domNode.innerHTML = 'no commands';
|
||||
return;
|
||||
@@ -105,16 +105,18 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
|
||||
let html: string[] = [];
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
let command = symbols[i].command;
|
||||
let title = escape(command.title);
|
||||
let part: string;
|
||||
if (command.id) {
|
||||
part = format('<a id={0}>{1}</a>', i, title);
|
||||
this._commands[i] = command;
|
||||
} else {
|
||||
part = format('<span>{0}</span>', title);
|
||||
const command = symbols[i].command;
|
||||
if (command) {
|
||||
const title = escape(command.title);
|
||||
let part: string;
|
||||
if (command.id) {
|
||||
part = `<a id=${i}>${title}</a>`;
|
||||
this._commands[i] = command;
|
||||
} else {
|
||||
part = `<span>${title}</span>`;
|
||||
}
|
||||
html.push(part);
|
||||
}
|
||||
html.push(part);
|
||||
}
|
||||
|
||||
this._domNode.innerHTML = html.join('<span> | </span>');
|
||||
@@ -136,6 +138,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
}
|
||||
|
||||
setSymbolRange(range: Range): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const lineNumber = range.startLineNumber;
|
||||
const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(lineNumber);
|
||||
this._widgetPosition = {
|
||||
@@ -206,7 +211,7 @@ export class CodeLens {
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
|
||||
let range: Range;
|
||||
let range: Range | undefined;
|
||||
this._data.forEach((codeLensData, i) => {
|
||||
|
||||
helper.addDecoration({
|
||||
@@ -222,16 +227,18 @@ export class CodeLens {
|
||||
}
|
||||
});
|
||||
|
||||
this._contentWidget = new CodeLensContentWidget(editor, range);
|
||||
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
|
||||
if (range) {
|
||||
this._contentWidget = new CodeLensContentWidget(editor, range);
|
||||
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
|
||||
|
||||
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
|
||||
this._editor.addContentWidget(this._contentWidget);
|
||||
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
|
||||
this._editor.addContentWidget(this._contentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
while (this._decorationIds.length) {
|
||||
helper.removeDecoration(this._decorationIds.pop());
|
||||
helper.removeDecoration(this._decorationIds.pop()!);
|
||||
}
|
||||
if (viewZoneChangeAccessor) {
|
||||
viewZoneChangeAccessor.removeZone(this._viewZoneId);
|
||||
@@ -240,16 +247,20 @@ export class CodeLens {
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
if (!this._editor.hasModel()) {
|
||||
return false;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
return this._decorationIds.some((id, i) => {
|
||||
const range = this._editor.getModel().getDecorationRange(id);
|
||||
const range = model.getDecorationRange(id);
|
||||
const symbol = this._data[i].symbol;
|
||||
return range && Range.isEmpty(symbol.range) === range.isEmpty();
|
||||
return !!(range && Range.isEmpty(symbol.range) === range.isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
updateCodeLensSymbols(data: ICodeLensData[], helper: CodeLensHelper): void {
|
||||
while (this._decorationIds.length) {
|
||||
helper.removeDecoration(this._decorationIds.pop());
|
||||
helper.removeDecoration(this._decorationIds.pop()!);
|
||||
}
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
@@ -261,7 +272,7 @@ export class CodeLens {
|
||||
});
|
||||
}
|
||||
|
||||
computeIfNecessary(model: ITextModel): ICodeLensData[] {
|
||||
computeIfNecessary(model: ITextModel): ICodeLensData[] | null {
|
||||
this._contentWidget.updateVisibility(); // trigger the fade in
|
||||
if (!this._contentWidget.isVisible()) {
|
||||
return null;
|
||||
@@ -269,12 +280,15 @@ export class CodeLens {
|
||||
|
||||
// Read editor current state
|
||||
for (let i = 0; i < this._decorationIds.length; i++) {
|
||||
this._data[i].symbol.range = model.getDecorationRange(this._decorationIds[i]);
|
||||
const range = model.getDecorationRange(this._decorationIds[i]);
|
||||
if (range) {
|
||||
this._data[i].symbol.range = range;
|
||||
}
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
updateCommands(symbols: ICodeLensSymbol[]): void {
|
||||
updateCommands(symbols: Array<ICodeLensSymbol | undefined | null>): void {
|
||||
this._contentWidget.withCommands(symbols);
|
||||
}
|
||||
|
||||
@@ -287,22 +301,25 @@ export class CodeLens {
|
||||
}
|
||||
|
||||
getLineNumber(): number {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
return range.startLineNumber;
|
||||
if (this._editor.hasModel()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
return range.startLineNumber;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
if (this.isValid()) {
|
||||
if (this.isValid() && this._editor.hasModel()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
this._viewZone.afterLineNumber = range.startLineNumber - 1;
|
||||
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
|
||||
|
||||
this._viewZone.afterLineNumber = range.startLineNumber - 1;
|
||||
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
|
||||
|
||||
this._contentWidget.setSymbolRange(range);
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
this._contentWidget.setSymbolRange(range);
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ export class ColorDetector implements IEditorContribution {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._colorDatas.get(decorations[0].id);
|
||||
return this._colorDatas.get(decorations[0].id)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,8 @@ export class BlockCommentCommand implements editorCommon.ICommand {
|
||||
this._usedEndToken = ops.length === 1 ? endToken : null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
builder.addTrackedEditOperation(ops[i].range, ops[i].text);
|
||||
for (const op of ops) {
|
||||
builder.addTrackedEditOperation(op.range, op.text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ abstract class CommentLineAction extends EditorAction {
|
||||
let selections = editor.getSelections();
|
||||
let opts = model.getOptions();
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
commands.push(new LineCommentCommand(selections[i], opts.tabSize, this._type));
|
||||
for (const selection of selections) {
|
||||
commands.push(new LineCommentCommand(selection, opts.tabSize, this._type));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
@@ -56,12 +56,13 @@ class ToggleCommentLineAction extends CommentLineAction {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.US_SLASH,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '5_insert',
|
||||
title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"),
|
||||
order: 1
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - Remove from menu
|
||||
// menubarOpts: {
|
||||
// menuId: MenuId.MenubarEditMenu,
|
||||
// group: '5_insert',
|
||||
// title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"),
|
||||
// order: 1
|
||||
// }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -112,12 +113,13 @@ class BlockCommentAction extends EditorAction {
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '5_insert',
|
||||
title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"),
|
||||
order: 2
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - Remove from menu
|
||||
// menubarOpts: {
|
||||
// menuId: MenuId.MenubarEditMenu,
|
||||
// group: '5_insert',
|
||||
// title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"),
|
||||
// order: 2
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,8 +130,8 @@ class BlockCommentAction extends EditorAction {
|
||||
|
||||
let commands: ICommand[] = [];
|
||||
let selections = editor.getSelections();
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
commands.push(new BlockCommentCommand(selections[i]));
|
||||
for (const selection of selections) {
|
||||
commands.push(new BlockCommentCommand(selection));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
|
||||
@@ -299,8 +299,8 @@ export class LineCommentCommand implements editorCommon.ICommand {
|
||||
}
|
||||
}
|
||||
this._selectionId = builder.trackSelection(s);
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
builder.addEditOperation(ops[i].range, ops[i].text);
|
||||
for (const op of ops) {
|
||||
builder.addEditOperation(op.range, op.text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -950,7 +950,9 @@ suite('Editor Contrib - Line Comment in mixed modes', () => {
|
||||
|
||||
this._register(modes.TokenizationRegistry.register(this.getLanguageIdentifier().language, {
|
||||
getInitialState: (): modes.IState => NULL_STATE,
|
||||
tokenize: undefined,
|
||||
tokenize: () => {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
tokenize2: (line: string, state: modes.IState): TokenizationResult2 => {
|
||||
let languageId = (/^ /.test(line) ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export class ContextMenuController implements IEditorContribution {
|
||||
|
||||
@@ -45,7 +46,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
|
||||
this._toDispose.push(this._editor.onContextMenu((e: IEditorMouseEvent) => this._onContextMenu(e)));
|
||||
this._toDispose.push(this._editor.onDidScrollChange((e: IScrollEvent) => {
|
||||
if (this._contextMenuIsBeingShownCount > 0) {
|
||||
if (this._contextMenuIsBeingShownCount > 0 && e.scrollTopChanged) {
|
||||
this._contextViewService.hideContextView();
|
||||
}
|
||||
}));
|
||||
@@ -60,6 +61,10 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _onContextMenu(e: IEditorMouseEvent): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._editor.getConfiguration().contribInfo.contextmenu) {
|
||||
this._editor.focus();
|
||||
// Ensure the cursor is at the position of the mouse click
|
||||
@@ -88,7 +93,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
|
||||
// Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
|
||||
let anchor: IAnchor;
|
||||
let anchor: IAnchor | null = null;
|
||||
if (e.target.type !== MouseTargetType.TEXTAREA) {
|
||||
anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 };
|
||||
}
|
||||
@@ -97,10 +102,13 @@ export class ContextMenuController implements IEditorContribution {
|
||||
this.showContextMenu(anchor);
|
||||
}
|
||||
|
||||
public showContextMenu(anchor?: IAnchor): void {
|
||||
public showContextMenu(anchor?: IAnchor | null): void {
|
||||
if (!this._editor.getConfiguration().contribInfo.contextmenu) {
|
||||
return; // Context menu is turned off through configuration
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._contextMenuService) {
|
||||
this._editor.focus();
|
||||
@@ -108,7 +116,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
|
||||
// Find actions available for menu
|
||||
const menuActions = this._getMenuActions();
|
||||
const menuActions = this._getMenuActions(this._editor.getModel());
|
||||
|
||||
// Show menu if we have actions to show
|
||||
if (menuActions.length > 0) {
|
||||
@@ -116,11 +124,11 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private _getMenuActions(): IAction[] {
|
||||
private _getMenuActions(model: ITextModel): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
|
||||
let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService);
|
||||
const groups = contextMenu.getActions({ arg: this._editor.getModel().uri });
|
||||
const groups = contextMenu.getActions({ arg: model.uri });
|
||||
contextMenu.dispose();
|
||||
|
||||
for (let group of groups) {
|
||||
@@ -133,6 +141,9 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _doShowContextMenu(actions: IAction[], anchor: IAnchor | null = null): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable hover
|
||||
const oldHoverSetting = this._editor.getConfiguration().contribInfo.hover;
|
||||
@@ -160,7 +171,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
// Show menu
|
||||
this._contextMenuIsBeingShownCount++;
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getAnchor: () => anchor!,
|
||||
|
||||
getActions: () => actions,
|
||||
|
||||
@@ -178,8 +189,8 @@ export class ContextMenuController implements IEditorContribution {
|
||||
return new ActionItem(action, action, { icon: true, label: true, isMenu: true });
|
||||
},
|
||||
|
||||
getKeyBinding: (action): ResolvedKeybinding => {
|
||||
return this._keybindingFor(action);
|
||||
getKeyBinding: (action): ResolvedKeybinding | undefined => {
|
||||
return this._keybindingFor(action) || undefined;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled: boolean) => {
|
||||
@@ -192,7 +203,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
});
|
||||
}
|
||||
|
||||
private _keybindingFor(action: IAction): ResolvedKeybinding {
|
||||
private _keybindingFor(action: IAction): ResolvedKeybinding | undefined {
|
||||
return this._keybindingService.lookupKeybinding(action.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
/* .monaco-tree.no-icons .outline-element .outline-element-icon {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
.monaco-tree .outline-element .outline-element-label {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -20,12 +20,14 @@ export abstract class TreeElement {
|
||||
|
||||
abstract id: string;
|
||||
abstract children: { [id: string]: TreeElement };
|
||||
abstract parent: TreeElement;
|
||||
abstract parent: TreeElement | undefined;
|
||||
|
||||
abstract adopt(newParent: TreeElement): TreeElement;
|
||||
|
||||
remove(): void {
|
||||
delete this.parent.children[this.id];
|
||||
if (this.parent) {
|
||||
delete this.parent.children[this.id];
|
||||
}
|
||||
}
|
||||
|
||||
static findId(candidate: DocumentSymbol | string, container: TreeElement): string {
|
||||
@@ -36,20 +38,20 @@ export abstract class TreeElement {
|
||||
candidateId = `${container.id}/${candidate}`;
|
||||
} else {
|
||||
candidateId = `${container.id}/${candidate.name}`;
|
||||
if (container.children[candidateId] !== void 0) {
|
||||
if (container.children[candidateId] !== undefined) {
|
||||
candidateId = `${container.id}/${candidate.name}_${candidate.range.startLineNumber}_${candidate.range.startColumn}`;
|
||||
}
|
||||
}
|
||||
|
||||
let id = candidateId;
|
||||
for (let i = 0; container.children[id] !== void 0; i++) {
|
||||
for (let i = 0; container.children[id] !== undefined; i++) {
|
||||
id = `${candidateId}_${i}`;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static getElementById(id: string, element: TreeElement): TreeElement {
|
||||
static getElementById(id: string, element: TreeElement): TreeElement | undefined {
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -88,18 +90,18 @@ export abstract class TreeElement {
|
||||
export class OutlineElement extends TreeElement {
|
||||
|
||||
children: { [id: string]: OutlineElement; } = Object.create(null);
|
||||
score: FuzzyScore = [0, []];
|
||||
marker: { count: number, topSev: MarkerSeverity };
|
||||
score: FuzzyScore | undefined = FuzzyScore.Default;
|
||||
marker: { count: number, topSev: MarkerSeverity } | undefined;
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
public parent: OutlineModel | OutlineGroup | OutlineElement,
|
||||
public parent: TreeElement | undefined,
|
||||
readonly symbol: DocumentSymbol
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
adopt(parent: OutlineModel | OutlineGroup | OutlineElement): OutlineElement {
|
||||
adopt(parent: TreeElement): OutlineElement {
|
||||
let res = new OutlineElement(this.id, parent, this.symbol);
|
||||
forEach(this.children, entry => res.children[entry.key] = entry.value.adopt(res));
|
||||
return res;
|
||||
@@ -112,33 +114,33 @@ export class OutlineGroup extends TreeElement {
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
public parent: OutlineModel,
|
||||
public parent: TreeElement | undefined,
|
||||
readonly provider: DocumentSymbolProvider,
|
||||
readonly providerIndex: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
adopt(parent: OutlineModel): OutlineGroup {
|
||||
adopt(parent: TreeElement): OutlineGroup {
|
||||
let res = new OutlineGroup(this.id, parent, this.provider, this.providerIndex);
|
||||
forEach(this.children, entry => res.children[entry.key] = entry.value.adopt(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
updateMatches(pattern: string, topMatch: OutlineElement): OutlineElement {
|
||||
updateMatches(pattern: string, topMatch: OutlineElement | undefined): OutlineElement | undefined {
|
||||
for (const key in this.children) {
|
||||
topMatch = this._updateMatches(pattern, this.children[key], topMatch);
|
||||
}
|
||||
return topMatch;
|
||||
}
|
||||
|
||||
private _updateMatches(pattern: string, item: OutlineElement, topMatch: OutlineElement): OutlineElement {
|
||||
private _updateMatches(pattern: string, item: OutlineElement, topMatch: OutlineElement | undefined): OutlineElement | undefined {
|
||||
|
||||
item.score = pattern
|
||||
? fuzzyScore(pattern, pattern.toLowerCase(), 0, item.symbol.name, item.symbol.name.toLowerCase(), 0, true)
|
||||
: [-100, []];
|
||||
: FuzzyScore.Default;
|
||||
|
||||
if (item.score && (!topMatch || item.score[0] > topMatch.score[0])) {
|
||||
if (item.score && (!topMatch || !topMatch.score || item.score[0] > topMatch.score[0])) {
|
||||
topMatch = item;
|
||||
}
|
||||
for (const key in item.children) {
|
||||
@@ -146,17 +148,17 @@ export class OutlineGroup extends TreeElement {
|
||||
topMatch = this._updateMatches(pattern, child, topMatch);
|
||||
if (!item.score && child.score) {
|
||||
// don't filter parents with unfiltered children
|
||||
item.score = [-100, []];
|
||||
item.score = FuzzyScore.Default;
|
||||
}
|
||||
}
|
||||
return topMatch;
|
||||
}
|
||||
|
||||
getItemEnclosingPosition(position: IPosition): OutlineElement {
|
||||
getItemEnclosingPosition(position: IPosition): OutlineElement | undefined {
|
||||
return position ? this._getItemEnclosingPosition(position, this.children) : undefined;
|
||||
}
|
||||
|
||||
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement {
|
||||
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement | undefined {
|
||||
for (let key in children) {
|
||||
let item = children[key];
|
||||
if (!item.symbol.range || !Range.containsPosition(item.symbol.range, position)) {
|
||||
@@ -174,7 +176,6 @@ export class OutlineGroup extends TreeElement {
|
||||
}
|
||||
|
||||
private _updateMarker(markers: IMarker[], item: OutlineElement): void {
|
||||
|
||||
item.marker = undefined;
|
||||
|
||||
// find the proper start index to check for item/marker overlap.
|
||||
@@ -190,14 +191,14 @@ export class OutlineGroup extends TreeElement {
|
||||
}
|
||||
|
||||
let myMarkers: IMarker[] = [];
|
||||
let myTopSev: MarkerSeverity;
|
||||
let myTopSev: MarkerSeverity | undefined;
|
||||
|
||||
for (; start < markers.length && Range.areIntersecting(item.symbol.range, markers[start]); start++) {
|
||||
// remove markers intersecting with this outline element
|
||||
// and store them in a 'private' array.
|
||||
let marker = markers[start];
|
||||
myMarkers.push(marker);
|
||||
markers[start] = undefined;
|
||||
(markers as Array<IMarker | undefined>)[start] = undefined;
|
||||
if (!myTopSev || marker.severity > myTopSev) {
|
||||
myTopSev = marker.severity;
|
||||
}
|
||||
@@ -224,7 +225,7 @@ export class OutlineGroup extends TreeElement {
|
||||
|
||||
export class OutlineModel extends TreeElement {
|
||||
|
||||
private static readonly _requests = new LRUCache<string, { promiseCnt: number, source: CancellationTokenSource, promise: Promise<any>, model: OutlineModel }>(9, .75);
|
||||
private static readonly _requests = new LRUCache<string, { promiseCnt: number, source: CancellationTokenSource, promise: Promise<any>, model: OutlineModel | undefined }>(9, 0.75);
|
||||
private static readonly _keys = new class {
|
||||
|
||||
private _counter = 1;
|
||||
@@ -265,25 +266,25 @@ export class OutlineModel extends TreeElement {
|
||||
OutlineModel._requests.set(key, data);
|
||||
}
|
||||
|
||||
if (data.model) {
|
||||
if (data!.model) {
|
||||
// resolved -> return data
|
||||
return Promise.resolve(data.model);
|
||||
}
|
||||
|
||||
// increase usage counter
|
||||
data.promiseCnt += 1;
|
||||
data!.promiseCnt += 1;
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
// last -> cancel provider request, remove cached promise
|
||||
if (--data.promiseCnt === 0) {
|
||||
data.source.cancel();
|
||||
if (--data!.promiseCnt === 0) {
|
||||
data!.source.cancel();
|
||||
OutlineModel._requests.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
data.promise.then(model => {
|
||||
data.model = model;
|
||||
data!.promise.then(model => {
|
||||
data!.model = model;
|
||||
resolve(model);
|
||||
}, err => {
|
||||
OutlineModel._requests.delete(key);
|
||||
@@ -331,7 +332,7 @@ export class OutlineModel extends TreeElement {
|
||||
container.children[res.id] = res;
|
||||
}
|
||||
|
||||
static get(element: TreeElement): OutlineModel {
|
||||
static get(element: TreeElement | undefined): OutlineModel | undefined {
|
||||
while (element) {
|
||||
if (element instanceof OutlineModel) {
|
||||
return element;
|
||||
@@ -373,8 +374,8 @@ export class OutlineModel extends TreeElement {
|
||||
} else {
|
||||
// adopt all elements of the first group
|
||||
let group = first(this._groups);
|
||||
for (let key in group.children) {
|
||||
let child = group.children[key];
|
||||
for (let key in group!.children) {
|
||||
let child = group!.children[key];
|
||||
child.parent = this;
|
||||
this.children[child.id] = child;
|
||||
}
|
||||
@@ -394,13 +395,13 @@ export class OutlineModel extends TreeElement {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _matches: [string, OutlineElement];
|
||||
private _matches: [string, OutlineElement | undefined];
|
||||
|
||||
updateMatches(pattern: string): OutlineElement {
|
||||
updateMatches(pattern: string): OutlineElement | undefined {
|
||||
if (this._matches && this._matches[0] === pattern) {
|
||||
return this._matches[1];
|
||||
}
|
||||
let topMatch: OutlineElement;
|
||||
let topMatch: OutlineElement | undefined;
|
||||
for (const key in this._groups) {
|
||||
topMatch = this._groups[key].updateMatches(pattern, topMatch);
|
||||
}
|
||||
@@ -408,9 +409,9 @@ export class OutlineModel extends TreeElement {
|
||||
return topMatch;
|
||||
}
|
||||
|
||||
getItemEnclosingPosition(position: IPosition, context?: OutlineElement): OutlineElement {
|
||||
getItemEnclosingPosition(position: IPosition, context?: OutlineElement): OutlineElement | undefined {
|
||||
|
||||
let preferredGroup: OutlineGroup;
|
||||
let preferredGroup: OutlineGroup | undefined;
|
||||
if (context) {
|
||||
let candidate = context.parent;
|
||||
while (candidate && !preferredGroup) {
|
||||
@@ -432,7 +433,7 @@ export class OutlineModel extends TreeElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
getItemById(id: string): TreeElement {
|
||||
getItemById(id: string): TreeElement | undefined {
|
||||
return TreeElement.getElementById(id, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ export class OutlineRenderer implements IRenderer {
|
||||
renderElement(tree: ITree, element: OutlineGroup | OutlineElement, templateId: string, template: OutlineTemplate): void {
|
||||
if (element instanceof OutlineElement) {
|
||||
template.icon.className = `outline-element-icon ${symbolKindToCssClass(element.symbol.kind)}`;
|
||||
template.label.set(element.symbol.name, element.score ? createMatches(element.score[1]) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind]));
|
||||
template.label.set(element.symbol.name, element.score ? createMatches(element.score) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind]));
|
||||
template.detail.innerText = element.symbol.detail || '';
|
||||
this._renderMarkerInfo(element, template);
|
||||
|
||||
@@ -240,9 +240,8 @@ export class OutlineRenderer implements IRenderer {
|
||||
};
|
||||
|
||||
disposeTemplate(tree: ITree, templateId: string, template: OutlineTemplate): void {
|
||||
template.label.dispose();
|
||||
// noop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class OutlineTreeState {
|
||||
|
||||
@@ -82,16 +82,16 @@ suite('OutlineModel', function () {
|
||||
}
|
||||
|
||||
function fakeMarker(range: Range): IMarker {
|
||||
return { ...range, owner: 'ffff', message: 'test', severity: MarkerSeverity.Error, resource: null };
|
||||
return { ...range, owner: 'ffff', message: 'test', severity: MarkerSeverity.Error, resource: null! };
|
||||
}
|
||||
|
||||
test('OutlineElement - updateMarker', function () {
|
||||
|
||||
let e0 = new OutlineElement('foo1', null, fakeSymbolInformation(new Range(1, 1, 1, 10)));
|
||||
let e1 = new OutlineElement('foo2', null, fakeSymbolInformation(new Range(2, 1, 5, 1)));
|
||||
let e2 = new OutlineElement('foo3', null, fakeSymbolInformation(new Range(6, 1, 10, 10)));
|
||||
let e0 = new OutlineElement('foo1', null!, fakeSymbolInformation(new Range(1, 1, 1, 10)));
|
||||
let e1 = new OutlineElement('foo2', null!, fakeSymbolInformation(new Range(2, 1, 5, 1)));
|
||||
let e2 = new OutlineElement('foo3', null!, fakeSymbolInformation(new Range(6, 1, 10, 10)));
|
||||
|
||||
let group = new OutlineGroup('group', null, null, 1);
|
||||
let group = new OutlineGroup('group', null!, null!, 1);
|
||||
group.children[e0.id] = e0;
|
||||
group.children[e1.id] = e1;
|
||||
group.children[e2.id] = e2;
|
||||
@@ -113,11 +113,11 @@ suite('OutlineModel', function () {
|
||||
|
||||
test('OutlineElement - updateMarker, 2', function () {
|
||||
|
||||
let p = new OutlineElement('A', null, fakeSymbolInformation(new Range(1, 1, 11, 1)));
|
||||
let c1 = new OutlineElement('A/B', null, fakeSymbolInformation(new Range(2, 4, 5, 4)));
|
||||
let c2 = new OutlineElement('A/C', null, fakeSymbolInformation(new Range(6, 4, 9, 4)));
|
||||
let p = new OutlineElement('A', null!, fakeSymbolInformation(new Range(1, 1, 11, 1)));
|
||||
let c1 = new OutlineElement('A/B', null!, fakeSymbolInformation(new Range(2, 4, 5, 4)));
|
||||
let c2 = new OutlineElement('A/C', null!, fakeSymbolInformation(new Range(6, 4, 9, 4)));
|
||||
|
||||
let group = new OutlineGroup('group', null, null, 1);
|
||||
let group = new OutlineGroup('group', null!, null!, 1);
|
||||
group.children[p.id] = p;
|
||||
p.children[c1.id] = c1;
|
||||
p.children[c2.id] = c2;
|
||||
@@ -155,16 +155,16 @@ suite('OutlineModel', function () {
|
||||
|
||||
let model = new class extends OutlineModel {
|
||||
constructor() {
|
||||
super(null);
|
||||
super(null!);
|
||||
}
|
||||
readyForTesting() {
|
||||
this._groups = this.children as any;
|
||||
}
|
||||
};
|
||||
model.children['g1'] = new OutlineGroup('g1', model, null, 1);
|
||||
model.children['g1'] = new OutlineGroup('g1', model, null!, 1);
|
||||
model.children['g1'].children['c1'] = new OutlineElement('c1', model.children['g1'], fakeSymbolInformation(new Range(1, 1, 11, 1)));
|
||||
|
||||
model.children['g2'] = new OutlineGroup('g2', model, null, 1);
|
||||
model.children['g2'] = new OutlineGroup('g2', model, null!, 1);
|
||||
model.children['g2'].children['c2'] = new OutlineElement('c2', model.children['g2'], fakeSymbolInformation(new Range(1, 1, 7, 1)));
|
||||
model.children['g2'].children['c2'].children['c2.1'] = new OutlineElement('c2.1', model.children['g2'].children['c2'], fakeSymbolInformation(new Range(1, 3, 2, 19)));
|
||||
model.children['g2'].children['c2'].children['c2.2'] = new OutlineElement('c2.2', model.children['g2'].children['c2'], fakeSymbolInformation(new Range(4, 1, 6, 10)));
|
||||
|
||||
@@ -28,9 +28,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const SEARCH_STRING_MAX_LENGTH = 524288;
|
||||
|
||||
export function getSelectionSearchString(editor: ICodeEditor): string {
|
||||
let selection = editor.getSelection();
|
||||
export function getSelectionSearchString(editor: ICodeEditor): string | null {
|
||||
if (!editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selection = editor.getSelection();
|
||||
// if selection spans multiple lines, default search string to empty
|
||||
if (selection.startLineNumber === selection.endLineNumber) {
|
||||
if (selection.isEmpty()) {
|
||||
@@ -71,7 +74,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
private _findWidgetVisible: IContextKey<boolean>;
|
||||
protected _state: FindReplaceState;
|
||||
protected _updateHistoryDelayer: Delayer<void>;
|
||||
private _model: FindModelBoundToEditorModel;
|
||||
private _model: FindModelBoundToEditorModel | null;
|
||||
private _storageService: IStorageService;
|
||||
private _clipboardService: IClipboardService;
|
||||
protected readonly _contextKeyService: IContextKeyService;
|
||||
@@ -178,7 +181,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
}
|
||||
|
||||
public isFindInputFocused(): boolean {
|
||||
return CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);
|
||||
return !!CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);
|
||||
}
|
||||
|
||||
public getState(): FindReplaceState {
|
||||
@@ -218,12 +221,14 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
if (this._state.searchScope) {
|
||||
this._state.change({ searchScope: null }, true);
|
||||
} else {
|
||||
let selection = this._editor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
if (this._editor.hasModel()) {
|
||||
let selection = this._editor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +247,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
protected _start(opts: IFindStartOptions): void {
|
||||
this.disposeModel();
|
||||
|
||||
if (!this._editor.getModel()) {
|
||||
if (!this._editor.hasModel()) {
|
||||
// cannot do anything with an editor that doesn't have a model...
|
||||
return;
|
||||
}
|
||||
@@ -338,6 +343,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
public getGlobalBufferTerm(): string {
|
||||
if (this._editor.getConfiguration().contribInfo.find.globalFindClipboard
|
||||
&& this._clipboardService
|
||||
&& this._editor.hasModel()
|
||||
&& !this._editor.getModel().isTooLargeForSyncing()
|
||||
) {
|
||||
return this._clipboardService.readFindText();
|
||||
@@ -348,6 +354,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
|
||||
public setGlobalBufferTerm(text: string) {
|
||||
if (this._editor.getConfiguration().contribInfo.find.globalFindClipboard
|
||||
&& this._clipboardService
|
||||
&& this._editor.hasModel()
|
||||
&& !this._editor.getModel().isTooLargeForSyncing()
|
||||
) {
|
||||
this._clipboardService.writeFindText(text);
|
||||
@@ -430,7 +437,7 @@ export class StartFindAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
controller.start({
|
||||
@@ -481,7 +488,7 @@ export class StartFindWithSelectionAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
export abstract class MatchFindAction extends EditorAction {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller && !this._run(controller)) {
|
||||
controller.start({
|
||||
@@ -544,7 +551,7 @@ export class PreviousMatchFindAction extends MatchFindAction {
|
||||
}
|
||||
|
||||
export abstract class SelectionMatchFindAction extends EditorAction {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
@@ -634,8 +641,8 @@ export class StartFindReplaceAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (editor.getConfiguration().readOnly) {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
if (!editor.hasModel() || editor.getConfiguration().readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,8 +80,7 @@ export class FindDecorations implements IDisposable {
|
||||
|
||||
public getCurrentMatchesPosition(desiredRange: Range): number {
|
||||
let candidates = this._editor.getModel().getDecorationsInRange(desiredRange);
|
||||
for (let i = 0, len = candidates.length; i < len; i++) {
|
||||
const candidate = candidates[i];
|
||||
for (const candidate of candidates) {
|
||||
const candidateOpts = candidate.options;
|
||||
if (candidateOpts === FindDecorations._FIND_MATCH_DECORATION || candidateOpts === FindDecorations._CURRENT_FIND_MATCH_DECORATION) {
|
||||
return this._getDecorationIndex(candidate.id);
|
||||
|
||||
@@ -131,7 +131,7 @@ export class FindModelBoundToEditorModel {
|
||||
// The find model is disposed during a find state changed event
|
||||
return;
|
||||
}
|
||||
if (!this._editor.getModel()) {
|
||||
if (!this._editor.hasModel()) {
|
||||
// The find model will be disposed momentarily
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
this._domNode.setAttribute('role', 'presentation');
|
||||
this._domNode.setAttribute('aria-hidden', 'true');
|
||||
|
||||
let inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder);
|
||||
const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder) || undefined;
|
||||
|
||||
this.caseSensitive = this._register(new CaseSensitiveCheckbox({
|
||||
appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
|
||||
@@ -179,7 +179,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme) {
|
||||
let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) };
|
||||
let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) || undefined };
|
||||
this.caseSensitive.style(inputStyles);
|
||||
this.wholeWords.style(inputStyles);
|
||||
this.regex.style(inputStyles);
|
||||
|
||||
@@ -41,7 +41,7 @@ export interface INewFindReplaceState {
|
||||
wholeWordOverride?: FindOptionOverride;
|
||||
matchCase?: boolean;
|
||||
matchCaseOverride?: FindOptionOverride;
|
||||
searchScope?: Range;
|
||||
searchScope?: Range | null;
|
||||
}
|
||||
|
||||
function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {
|
||||
|
||||
@@ -111,7 +111,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
private _replaceFocusTracker: dom.IFocusTracker;
|
||||
private _replaceInputFocused: IContextKey<boolean>;
|
||||
private _viewZone: FindWidgetViewZone;
|
||||
private _viewZoneId: number;
|
||||
private _viewZoneId?: number;
|
||||
|
||||
private _resizeSash: Sash;
|
||||
private _resized: boolean;
|
||||
@@ -210,7 +210,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
return;
|
||||
}
|
||||
this._codeEditor.changeViewZones((accessor) => {
|
||||
accessor.removeZone(this._viewZoneId);
|
||||
if (this._viewZoneId) {
|
||||
accessor.removeZone(this._viewZoneId);
|
||||
}
|
||||
this._viewZoneId = undefined;
|
||||
});
|
||||
}));
|
||||
@@ -239,7 +241,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IOverlayWidgetPosition {
|
||||
public getPosition(): IOverlayWidgetPosition | null {
|
||||
if (this._isVisible) {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER
|
||||
@@ -401,8 +403,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
|
||||
let selection = this._codeEditor.getSelection();
|
||||
let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
|
||||
const selection = this._codeEditor.getSelection();
|
||||
const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
|
||||
if (isSelection && this._codeEditor.getConfiguration().contribInfo.find.autoFindInSelection) {
|
||||
this._toggleSelectionFind.checked = true;
|
||||
} else {
|
||||
@@ -425,24 +427,27 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
|
||||
let adjustEditorScrollTop = true;
|
||||
if (this._codeEditor.getConfiguration().contribInfo.find.seedSearchStringFromSelection && selection) {
|
||||
let editorCoords = dom.getDomNodePagePosition(this._codeEditor.getDomNode());
|
||||
let startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());
|
||||
let startLeft = editorCoords.left + startCoords.left;
|
||||
let startTop = startCoords.top;
|
||||
const domNode = this._codeEditor.getDomNode();
|
||||
if (domNode) {
|
||||
const editorCoords = dom.getDomNodePagePosition(domNode);
|
||||
const startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());
|
||||
const startLeft = editorCoords.left + (startCoords ? startCoords.left : 0);
|
||||
const startTop = startCoords ? startCoords.top : 0;
|
||||
|
||||
if (startTop < this._viewZone.heightInPx) {
|
||||
if (selection.endLineNumber > selection.startLineNumber) {
|
||||
adjustEditorScrollTop = false;
|
||||
}
|
||||
if (startTop < this._viewZone.heightInPx) {
|
||||
if (selection.endLineNumber > selection.startLineNumber) {
|
||||
adjustEditorScrollTop = false;
|
||||
}
|
||||
|
||||
let leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;
|
||||
if (startLeft > leftOfFindWidget) {
|
||||
adjustEditorScrollTop = false;
|
||||
}
|
||||
let endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());
|
||||
let endLeft = editorCoords.left + endCoords.left;
|
||||
if (endLeft > leftOfFindWidget) {
|
||||
adjustEditorScrollTop = false;
|
||||
const leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;
|
||||
if (startLeft > leftOfFindWidget) {
|
||||
adjustEditorScrollTop = false;
|
||||
}
|
||||
const endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());
|
||||
const endLeft = editorCoords.left + (endCoords ? endCoords.left : 0);
|
||||
if (endLeft > leftOfFindWidget) {
|
||||
adjustEditorScrollTop = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,12 +614,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
}
|
||||
|
||||
private _updateSearchScope(): void {
|
||||
if (!this._codeEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
let selection = this._codeEditor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
let currentMatch = this._state.currentMatch;
|
||||
const currentMatch = this._state.currentMatch;
|
||||
if (selection.startLineNumber !== selection.endLineNumber) {
|
||||
if (!Range.equalsRange(selection, currentMatch)) {
|
||||
// Reseed find scope
|
||||
@@ -634,13 +643,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
private _onFindInputKeyDown(e: IKeyboardEvent): void {
|
||||
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(null, onUnexpectedError);
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(null, onUnexpectedError);
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
@@ -725,7 +734,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
|
||||
appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
|
||||
appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
|
||||
validation: (value: string): InputBoxMessage => {
|
||||
validation: (value: string): InputBoxMessage | null => {
|
||||
if (value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -780,7 +789,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
|
||||
className: 'previous',
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(null, onUnexpectedError);
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -789,7 +798,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
|
||||
className: 'next',
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(null, onUnexpectedError);
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -806,15 +815,17 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
|
||||
onChange: () => {
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
let selection = this._codeEditor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
if (this._codeEditor.hasModel()) {
|
||||
let selection = this._codeEditor.getSelection();
|
||||
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
|
||||
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
|
||||
}
|
||||
if (!selection.isEmpty()) {
|
||||
this._state.change({ searchScope: selection }, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._state.change({ searchScope: null }, true);
|
||||
this._state.change({ searchScope: undefined }, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -824,7 +835,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
|
||||
className: 'close-fw',
|
||||
onTrigger: () => {
|
||||
this._state.change({ isRevealed: false, searchScope: null }, false);
|
||||
this._state.change({ isRevealed: false, searchScope: undefined }, false);
|
||||
},
|
||||
onKeyDown: (e) => {
|
||||
if (e.equals(KeyCode.Tab)) {
|
||||
@@ -850,7 +861,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
let replaceInput = document.createElement('div');
|
||||
replaceInput.className = 'replace-input';
|
||||
replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px';
|
||||
this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, null, {
|
||||
this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, undefined, {
|
||||
ariaLabel: NLS_REPLACE_INPUT_LABEL,
|
||||
placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
|
||||
history: []
|
||||
@@ -950,8 +961,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
return;
|
||||
}
|
||||
|
||||
let inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH;
|
||||
let maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
|
||||
const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH;
|
||||
const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth!) || 0;
|
||||
if (width > maxWidth) {
|
||||
return;
|
||||
}
|
||||
@@ -1119,7 +1130,7 @@ export class SimpleButton extends Widget {
|
||||
// theming
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const addBackgroundColorRule = (selector: string, color: Color): void => {
|
||||
const addBackgroundColorRule = (selector: string, color: Color | null): void => {
|
||||
if (color) {
|
||||
collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`);
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ export class ReplaceAllCommand implements editorCommon.ICommand {
|
||||
}
|
||||
resultOps.push(previousOp);
|
||||
|
||||
for (let i = 0; i < resultOps.length; i++) {
|
||||
builder.addEditOperation(resultOps[i].range, resultOps[i].text);
|
||||
for (const op of resultOps) {
|
||||
builder.addEditOperation(op.range, op.text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
.monaco-workbench .simple-find-part {
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
top: -40px;
|
||||
top: -45px;
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
|
||||
@@ -26,7 +26,7 @@ const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||
|
||||
export abstract class SimpleFindWidget extends Widget {
|
||||
private _findInput: FindInput;
|
||||
private _domNode: HTMLElement;
|
||||
private _domNode?: HTMLElement;
|
||||
private _innerDomNode: HTMLElement;
|
||||
private _isVisible: boolean = false;
|
||||
private _focusTracker: dom.IFocusTracker;
|
||||
@@ -186,7 +186,7 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
public getDomNode() {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ export class TestFindController extends CommonFindController {
|
||||
}
|
||||
}
|
||||
|
||||
function fromRange(rng: Range): number[] {
|
||||
return [rng.startLineNumber, rng.startColumn, rng.endLineNumber, rng.endColumn];
|
||||
function fromSelection(slc: Selection): number[] {
|
||||
return [slc.startLineNumber, slc.startColumn, slc.endLineNumber, slc.endColumn];
|
||||
}
|
||||
|
||||
suite('FindController', () => {
|
||||
@@ -67,8 +67,8 @@ suite('FindController', () => {
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
getInteger: (key: string) => undefined,
|
||||
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
|
||||
remove: (key) => void 0
|
||||
} as IStorageService);
|
||||
remove: (key) => undefined
|
||||
} as any);
|
||||
|
||||
if (platform.isMacintosh) {
|
||||
serviceCollection.set(IClipboardService, <any>{
|
||||
@@ -122,7 +122,7 @@ suite('FindController', () => {
|
||||
nextMatchFindAction.run(null, editor);
|
||||
assert.equal(findState.searchString, 'ABC');
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
@@ -175,14 +175,14 @@ suite('FindController', () => {
|
||||
findState.change({ searchString: 'ABC' }, true);
|
||||
|
||||
// The first ABC is highlighted.
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
|
||||
|
||||
// I hit Esc to exit the Find dialog.
|
||||
findController.closeFindWidget();
|
||||
findController.hasFocus = false;
|
||||
|
||||
// The cursor is now at end of the first line, with ABC on that line highlighted.
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
|
||||
|
||||
// I hit delete to remove it and change the text to XYZ.
|
||||
editor.pushUndoStop();
|
||||
@@ -195,10 +195,10 @@ suite('FindController', () => {
|
||||
// ABC
|
||||
// XYZ
|
||||
// ABC
|
||||
assert.equal(editor.getModel().getLineContent(1), 'XYZ');
|
||||
assert.equal(editor.getModel()!.getLineContent(1), 'XYZ');
|
||||
|
||||
// The cursor is at end of the first line.
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 4, 1, 4]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]);
|
||||
|
||||
// I hit F3 to "Find Next" to find the next occurrence of ABC, but instead it searches for XYZ.
|
||||
nextMatchFindAction.run(null, editor);
|
||||
@@ -224,10 +224,10 @@ suite('FindController', () => {
|
||||
});
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 26, 1, 29]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 8, 1, 11]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
@@ -250,10 +250,10 @@ suite('FindController', () => {
|
||||
startFindAction.run(null, editor);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 9, 2, 13]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [1, 9, 1, 13]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
@@ -330,7 +330,7 @@ suite('FindController', () => {
|
||||
findController.getState().change({ searchString: '\\b\\s{3}\\b', replaceString: ' ', isRegex: true }, false);
|
||||
findController.moveToNextMatch();
|
||||
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[1, 39, 1, 42]
|
||||
]);
|
||||
|
||||
@@ -357,7 +357,7 @@ suite('FindController', () => {
|
||||
findController.getState().change({ searchString: '^', replaceString: 'x', isRegex: true }, false);
|
||||
findController.moveToNextMatch();
|
||||
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[2, 1, 2, 1]
|
||||
]);
|
||||
|
||||
@@ -388,7 +388,7 @@ suite('FindController', () => {
|
||||
// cmd+f3
|
||||
nextSelectionMatchFindAction.run(null, editor);
|
||||
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[3, 1, 3, 9]
|
||||
]);
|
||||
|
||||
@@ -419,7 +419,7 @@ suite('FindController', () => {
|
||||
// cmd+f3
|
||||
nextSelectionMatchFindAction.run(null, editor);
|
||||
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[3, 1, 3, 9]
|
||||
]);
|
||||
|
||||
@@ -442,8 +442,8 @@ suite('FindController query options persistence', () => {
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
getInteger: (key: string) => undefined,
|
||||
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
|
||||
remove: (key) => void 0
|
||||
} as IStorageService);
|
||||
remove: (key) => undefined
|
||||
} as any);
|
||||
|
||||
test('matchCase', () => {
|
||||
withTestCodeEditor([
|
||||
@@ -464,7 +464,7 @@ suite('FindController query options persistence', () => {
|
||||
// I type ABC.
|
||||
findState.change({ searchString: 'ABC' }, true);
|
||||
// The second ABC is highlighted as matchCase is true.
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 2, 4]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 4]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
@@ -491,7 +491,7 @@ suite('FindController query options persistence', () => {
|
||||
// I type AB.
|
||||
findState.change({ searchString: 'AB' }, true);
|
||||
// The second AB is highlighted as wholeWord is true.
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 2, 3]);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 3]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Cursor } from 'vs/editor/common/controller/cursor';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -18,7 +18,7 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
|
||||
suite('FindModel', () => {
|
||||
|
||||
function findTest(testName: string, callback: (editor: ICodeEditor, cursor: Cursor) => void): void {
|
||||
function findTest(testName: string, callback: (editor: IActiveCodeEditor, cursor: Cursor) => void): void {
|
||||
test(testName, () => {
|
||||
const textArr = [
|
||||
'// my cool header',
|
||||
@@ -34,7 +34,7 @@ suite('FindModel', () => {
|
||||
'// blablablaciao',
|
||||
''
|
||||
];
|
||||
withTestCodeEditor(textArr, {}, callback);
|
||||
withTestCodeEditor(textArr, {}, (editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor));
|
||||
|
||||
const text = textArr.join('\n');
|
||||
const ptBuilder = new PieceTreeTextBufferBuilder();
|
||||
@@ -45,7 +45,9 @@ suite('FindModel', () => {
|
||||
withTestCodeEditor([],
|
||||
{
|
||||
model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null)
|
||||
}, callback);
|
||||
},
|
||||
(editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,7 +56,7 @@ suite('FindModel', () => {
|
||||
}
|
||||
|
||||
function _getFindState(editor: ICodeEditor) {
|
||||
let model = editor.getModel();
|
||||
let model = editor.getModel()!;
|
||||
let currentFindMatches: Range[] = [];
|
||||
let allFindMatches: Range[] = [];
|
||||
|
||||
@@ -76,8 +78,8 @@ suite('FindModel', () => {
|
||||
};
|
||||
}
|
||||
|
||||
function assertFindState(editor: ICodeEditor, cursor: number[], highlighted: number[], findDecorations: number[][]): void {
|
||||
assert.deepEqual(fromRange(editor.getSelection()), cursor, 'cursor');
|
||||
function assertFindState(editor: ICodeEditor, cursor: number[], highlighted: number[] | null, findDecorations: number[][]): void {
|
||||
assert.deepEqual(fromRange(editor.getSelection()!), cursor, 'cursor');
|
||||
|
||||
let expectedState = {
|
||||
highlighted: highlighted ? [highlighted] : [],
|
||||
@@ -1177,7 +1179,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1191,7 +1193,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1204,7 +1206,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, hi!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, hi!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1216,7 +1218,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1227,7 +1229,7 @@ suite('FindModel', () => {
|
||||
[6, 14, 6, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1236,7 +1238,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, hi!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1269,7 +1271,7 @@ suite('FindModel', () => {
|
||||
[11, 10, 11, 13]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// blablablaciao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// blablablaciao');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1281,7 +1283,7 @@ suite('FindModel', () => {
|
||||
[11, 11, 11, 14]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// ciaoblablaciao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// ciaoblablaciao');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1292,7 +1294,7 @@ suite('FindModel', () => {
|
||||
[11, 12, 11, 15]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// ciaociaoblaciao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaoblaciao');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1301,7 +1303,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// ciaociaociaociao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1338,7 +1340,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
|
||||
findModel.replaceAll();
|
||||
assertFindState(
|
||||
@@ -1347,9 +1349,9 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, hi!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1388,10 +1390,10 @@ suite('FindModel', () => {
|
||||
[9, 1, 9, 3]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hello world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(9), ' cout << "helloworld again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "helloworld again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1420,7 +1422,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// ciaociaociaociao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1449,10 +1451,10 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(11), '// <');
|
||||
assert.equal(editor.getModel().getLineContent(12), '\t><');
|
||||
assert.equal(editor.getModel().getLineContent(13), '\t><');
|
||||
assert.equal(editor.getModel().getLineContent(14), '\t>ciao');
|
||||
assert.equal(editor.getModel()!.getLineContent(11), '// <');
|
||||
assert.equal(editor.getModel()!.getLineContent(12), '\t><');
|
||||
assert.equal(editor.getModel()!.getLineContent(13), '\t><');
|
||||
assert.equal(editor.getModel()!.getLineContent(14), '\t>ciao');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1481,8 +1483,8 @@ suite('FindModel', () => {
|
||||
[]
|
||||
);
|
||||
|
||||
assert.equal(editor.getModel().getLineContent(2), '#bar "cool.h"');
|
||||
assert.equal(editor.getModel().getLineContent(3), '#bar <iostream>');
|
||||
assert.equal(editor.getModel()!.getLineContent(2), '#bar "cool.h"');
|
||||
assert.equal(editor.getModel()!.getLineContent(3), '#bar <iostream>');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1671,7 +1673,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1683,7 +1685,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1694,7 +1696,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1703,7 +1705,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1742,7 +1744,7 @@ suite('FindModel', () => {
|
||||
]
|
||||
);
|
||||
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1754,7 +1756,7 @@ suite('FindModel', () => {
|
||||
[7, 14, 7, 19],
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1765,7 +1767,7 @@ suite('FindModel', () => {
|
||||
[7, 14, 7, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1774,7 +1776,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1798,9 +1800,9 @@ suite('FindModel', () => {
|
||||
|
||||
findModel.replaceAll();
|
||||
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
|
||||
|
||||
assertFindState(
|
||||
editor,
|
||||
@@ -1841,7 +1843,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1853,7 +1855,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hilo world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hilo world, Hello!" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1864,7 +1866,7 @@ suite('FindModel', () => {
|
||||
[8, 14, 8, 19]
|
||||
]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hilo world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hilo world again" << endl;');
|
||||
|
||||
findModel.replace();
|
||||
assertFindState(
|
||||
@@ -1873,7 +1875,7 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "hilo world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hilo world again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -1898,10 +1900,10 @@ suite('FindModel', () => {
|
||||
|
||||
findModel.replaceAll();
|
||||
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hello girl again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(9), ' cout << "hellogirl again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello girl again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hellogirl again" << endl;');
|
||||
|
||||
assertFindState(
|
||||
editor,
|
||||
@@ -1931,8 +1933,8 @@ suite('FindModel', () => {
|
||||
|
||||
findModel.replaceAll();
|
||||
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;');
|
||||
|
||||
assertFindState(
|
||||
editor,
|
||||
@@ -1969,9 +1971,9 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << " world, !" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << " world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(8), ' cout << " world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << " world, !" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << " world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(8), ' cout << " world again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
@@ -2023,9 +2025,9 @@ suite('FindModel', () => {
|
||||
null,
|
||||
[]
|
||||
);
|
||||
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel().getLineContent(9), ' cout << "hiworld again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
|
||||
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hiworld again" << endl;');
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
|
||||
@@ -36,7 +36,7 @@ export const ID = 'editor.contrib.folding';
|
||||
|
||||
export interface RangeProvider {
|
||||
readonly id: string;
|
||||
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions | null>;
|
||||
compute(cancelationToken: CancellationToken): Promise<FoldingRegions | null>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export class FoldingController implements IEditorContribution {
|
||||
|
||||
private foldingStateMemento: FoldingStateMemento | null;
|
||||
|
||||
private foldingModelPromise: Thenable<FoldingModel | null> | null;
|
||||
private foldingModelPromise: Promise<FoldingModel | null> | null;
|
||||
private updateScheduler: Delayer<FoldingModel | null> | null;
|
||||
|
||||
private globalToDispose: IDisposable[];
|
||||
@@ -136,10 +136,10 @@ export class FoldingController implements IEditorContribution {
|
||||
}
|
||||
if (this.foldingModel) { // disposed ?
|
||||
let collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel!.getMemento();
|
||||
let provider = this.rangeProvider ? this.rangeProvider.id : void 0;
|
||||
let provider = this.rangeProvider ? this.rangeProvider.id : undefined;
|
||||
return { collapsedRegions, lineCount: model.getLineCount(), provider };
|
||||
}
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,7 +432,7 @@ abstract class FoldingAction<T> extends EditorAction {
|
||||
|
||||
abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T): void;
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Thenable<void> {
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise<void> {
|
||||
let foldingController = FoldingController.get(editor);
|
||||
if (!foldingController) {
|
||||
return;
|
||||
|
||||
@@ -153,7 +153,7 @@ export class FoldingModel {
|
||||
if (collapsedRanges.length > 0) {
|
||||
return collapsedRanges;
|
||||
}
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +291,6 @@ export function setCollapseStateLevelsUp(foldingModel: FoldingModel, doCollapse:
|
||||
* Folds or unfolds all regions that have a given level, except if they contain one of the blocked lines.
|
||||
* @param foldLevel level. Level == 1 is the top level
|
||||
* @param doCollapse Wheter to collase or expand
|
||||
* @param blockedLineNumbers
|
||||
*/
|
||||
export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: number, doCollapse: boolean, blockedLineNumbers: number[]): void {
|
||||
let filter = (region: FoldingRegion, level: number) => level === foldLevel && region.isCollapsed !== doCollapse && !blockedLineNumbers.some(line => region.containsLine(line));
|
||||
|
||||
@@ -18,9 +18,9 @@ export class FoldingRegions {
|
||||
private _endIndexes: Uint32Array;
|
||||
private _collapseStates: Uint32Array;
|
||||
private _parentsComputed: boolean;
|
||||
private _types: (string | undefined)[] | undefined;
|
||||
private _types: Array<string | undefined> | undefined;
|
||||
|
||||
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: (string | undefined)[]) {
|
||||
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array<string | undefined>) {
|
||||
if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {
|
||||
throw new Error('invalid startIndexes or endIndexes size');
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export class FoldingRegions {
|
||||
}
|
||||
|
||||
public getType(index: number): string | undefined {
|
||||
return this._types ? this._types[index] : void 0;
|
||||
return this._types ? this._types[index] : undefined;
|
||||
}
|
||||
|
||||
public hasTypes() {
|
||||
|
||||
@@ -26,7 +26,7 @@ export class IndentRangeProvider implements RangeProvider {
|
||||
dispose() {
|
||||
}
|
||||
|
||||
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions> {
|
||||
compute(cancelationToken: CancellationToken): Promise<FoldingRegions> {
|
||||
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this.editorModel.getLanguageIdentifier().id);
|
||||
let offSide = foldingRules && !!foldingRules.offSide;
|
||||
let markers = foldingRules && foldingRules.markers;
|
||||
@@ -113,7 +113,7 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol
|
||||
const tabSize = model.getOptions().tabSize;
|
||||
let result = new RangesCollector(foldingRangesLimit);
|
||||
|
||||
let pattern: RegExp | undefined = void 0;
|
||||
let pattern: RegExp | undefined = undefined;
|
||||
if (markers) {
|
||||
pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
|
||||
}
|
||||
|
||||
@@ -40,15 +40,15 @@ export class InitializingRangeProvider implements RangeProvider {
|
||||
dispose(): void {
|
||||
if (this.decorationIds) {
|
||||
this.editorModel.deltaDecorations(this.decorationIds, []);
|
||||
this.decorationIds = void 0;
|
||||
this.decorationIds = undefined;
|
||||
}
|
||||
if (typeof this.timeout === 'number') {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = void 0;
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions> {
|
||||
compute(cancelationToken: CancellationToken): Promise<FoldingRegions> {
|
||||
let foldingRangeData: IFoldingRangeData[] = [];
|
||||
if (this.decorationIds) {
|
||||
for (let id of this.decorationIds) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export class SyntaxRangeProvider implements RangeProvider {
|
||||
constructor(private editorModel: ITextModel, private providers: FoldingRangeProvider[], private limit = MAX_FOLDING_REGIONS) {
|
||||
}
|
||||
|
||||
compute(cancellationToken: CancellationToken): Thenable<FoldingRegions | null> {
|
||||
compute(cancellationToken: CancellationToken): Promise<FoldingRegions | null> {
|
||||
return collectSyntaxRanges(this.providers, this.editorModel, cancellationToken).then(ranges => {
|
||||
if (ranges) {
|
||||
let res = sanitizeRanges(ranges, this.limit);
|
||||
@@ -43,7 +43,7 @@ export class SyntaxRangeProvider implements RangeProvider {
|
||||
|
||||
}
|
||||
|
||||
function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Thenable<IFoldingRangeData[] | null> {
|
||||
function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Promise<IFoldingRangeData[] | null> {
|
||||
let rangeData: IFoldingRangeData[] | null = null;
|
||||
let promises = providers.map((provider, i) => {
|
||||
return Promise.resolve(provider.provideFoldingRanges(model, foldingContext, cancellationToken)).then(ranges => {
|
||||
@@ -73,7 +73,7 @@ export class RangesCollector {
|
||||
private _endIndexes: number[];
|
||||
private _nestingLevels: number[];
|
||||
private _nestingLevelCounts: number[];
|
||||
private _types: (string | undefined)[];
|
||||
private _types: Array<string | undefined>;
|
||||
private _length: number;
|
||||
private _foldingRangesLimit: number;
|
||||
|
||||
@@ -127,7 +127,7 @@ export class RangesCollector {
|
||||
|
||||
let startIndexes = new Uint32Array(this._foldingRangesLimit);
|
||||
let endIndexes = new Uint32Array(this._foldingRangesLimit);
|
||||
let types: (string | undefined)[] = [];
|
||||
let types: Array<string | undefined> = [];
|
||||
for (let i = 0, k = 0; i < this._length; i++) {
|
||||
let level = this._nestingLevels[i];
|
||||
if (level < maxLevel || (level === maxLevel && entries++ < this._foldingRangesLimit)) {
|
||||
@@ -155,7 +155,7 @@ export function sanitizeRanges(rangeData: IFoldingRangeData[], limit: number): F
|
||||
});
|
||||
let collector = new RangesCollector(limit);
|
||||
|
||||
let top: IFoldingRangeData | undefined = void 0;
|
||||
let top: IFoldingRangeData | undefined = undefined;
|
||||
let previous: IFoldingRangeData[] = [];
|
||||
for (let entry of sorted) {
|
||||
if (!top) {
|
||||
|
||||
@@ -96,7 +96,7 @@ suite('Folding Model', () => {
|
||||
try {
|
||||
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
let r1 = r(1, 3, false);
|
||||
@@ -135,7 +135,7 @@ suite('Folding Model', () => {
|
||||
try {
|
||||
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
let r1 = r(1, 3, false);
|
||||
@@ -181,7 +181,7 @@ suite('Folding Model', () => {
|
||||
try {
|
||||
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
let r1 = r(1, 3, false);
|
||||
@@ -193,7 +193,7 @@ suite('Folding Model', () => {
|
||||
|
||||
textModel.applyEdits([EditOperation.insert(new Position(4, 1), '//hello\n')]);
|
||||
|
||||
foldingModel.update(computeRanges(textModel, false, void 0));
|
||||
foldingModel.update(computeRanges(textModel, false, undefined));
|
||||
|
||||
assertRanges(foldingModel, [r(1, 3, true), r(5, 8, false), r(6, 7, true)]);
|
||||
} finally {
|
||||
@@ -221,7 +221,7 @@ suite('Folding Model', () => {
|
||||
try {
|
||||
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
let r1 = r(1, 12, false);
|
||||
@@ -235,7 +235,7 @@ suite('Folding Model', () => {
|
||||
|
||||
textModel.applyEdits([EditOperation.delete(new Range(6, 11, 9, 0))]);
|
||||
|
||||
foldingModel.update(computeRanges(textModel, false, void 0));
|
||||
foldingModel.update(computeRanges(textModel, false, undefined));
|
||||
|
||||
assertRanges(foldingModel, [r(1, 9, false), r(2, 8, false), r(3, 5, false), r(6, 8, false)]);
|
||||
} finally {
|
||||
@@ -258,7 +258,7 @@ suite('Folding Model', () => {
|
||||
try {
|
||||
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
let r1 = r(1, 3, false);
|
||||
|
||||
@@ -44,7 +44,7 @@ suite('Hidden Range Model', () => {
|
||||
|
||||
assert.equal(hiddenRangeModel.hasRanges(), false);
|
||||
|
||||
let ranges = computeRanges(textModel, false, void 0);
|
||||
let ranges = computeRanges(textModel, false, undefined);
|
||||
foldingModel.update(ranges);
|
||||
|
||||
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!]);
|
||||
|
||||
@@ -17,11 +17,15 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export class NoProviderError extends Error {
|
||||
|
||||
static readonly Name = 'NOPRO';
|
||||
static is(thing: any): thing is NoProviderError {
|
||||
return thing instanceof Error && thing.name === NoProviderError._name;
|
||||
}
|
||||
|
||||
private static readonly _name = 'NOPRO';
|
||||
|
||||
constructor(message?: string) {
|
||||
super();
|
||||
this.name = NoProviderError.Name;
|
||||
this.name = NoProviderError._name;
|
||||
if (message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@@ -3,30 +3,32 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, IActionOptions } from 'vs/editor/browser/editorExtensions';
|
||||
import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
|
||||
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState';
|
||||
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, FormattingOptions, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { getOnTypeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
|
||||
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
|
||||
import * as nls from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
|
||||
function alertFormattingEdits(edits: ISingleEditOperation[]): void {
|
||||
|
||||
@@ -55,6 +57,134 @@ function alertFormattingEdits(edits: ISingleEditOperation[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
export const enum FormatRangeType {
|
||||
Full,
|
||||
Selection,
|
||||
}
|
||||
|
||||
export function formatDocumentRange(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, rangeOrRangeType: Range | FormatRangeType, options: FormattingOptions, token: CancellationToken): Promise<void> {
|
||||
|
||||
const provider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel());
|
||||
if (provider.length === 0) {
|
||||
return Promise.reject(new NoProviderError());
|
||||
}
|
||||
|
||||
// Know how often multiple providers clash and (for now)
|
||||
// continue picking the 'first' provider
|
||||
if (provider.length !== 1) {
|
||||
/* __GDPR__
|
||||
"manyformatters" : {
|
||||
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
telemetryService.publicLog('manyformatters', {
|
||||
type: 'range',
|
||||
language: editor.getModel().getLanguageIdentifier().language,
|
||||
count: provider.length,
|
||||
});
|
||||
provider.length = 1;
|
||||
}
|
||||
|
||||
let allEdits: ISingleEditOperation[] = [];
|
||||
|
||||
editor.pushUndoStop();
|
||||
return sequence(provider.map(provider => {
|
||||
// create a formatting task per provider. they run sequentially,
|
||||
// potentially undoing the working of a previous formatter
|
||||
return () => {
|
||||
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
const model = editor.getModel();
|
||||
|
||||
let range: Range;
|
||||
if (rangeOrRangeType === FormatRangeType.Full) {
|
||||
// full
|
||||
range = model.getFullModelRange();
|
||||
|
||||
} else if (rangeOrRangeType === FormatRangeType.Selection) {
|
||||
// selection or line (when empty)
|
||||
range = editor.getSelection();
|
||||
if (range.isEmpty()) {
|
||||
range = new Range(range.startLineNumber, 1, range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
|
||||
}
|
||||
} else {
|
||||
// as is
|
||||
range = rangeOrRangeType;
|
||||
}
|
||||
return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).then(edits => {
|
||||
// break edits into smaller edits
|
||||
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
|
||||
}).then(edits => {
|
||||
// make edit only when the editor didn't change while
|
||||
// computing and only when there are edits
|
||||
if (state.validate(editor) && isNonEmptyArray(edits)) {
|
||||
FormattingEdit.execute(editor, edits);
|
||||
allEdits = allEdits.concat(edits);
|
||||
}
|
||||
});
|
||||
};
|
||||
})).then(() => {
|
||||
alertFormattingEdits(allEdits);
|
||||
editor.pushUndoStop();
|
||||
editor.focus();
|
||||
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
|
||||
});
|
||||
}
|
||||
|
||||
export function formatDocument(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, options: FormattingOptions, token: CancellationToken): Promise<void> {
|
||||
const provider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel());
|
||||
if (provider.length === 0) {
|
||||
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Full, options, token);
|
||||
}
|
||||
|
||||
// Know how often multiple providers clash and (for now)
|
||||
// continue picking the 'first' provider
|
||||
if (provider.length !== 1) {
|
||||
/* __GDPR__
|
||||
"manyformatters" : {
|
||||
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
telemetryService.publicLog('manyformatters', {
|
||||
type: 'document',
|
||||
language: editor.getModel().getLanguageIdentifier().language,
|
||||
count: provider.length,
|
||||
});
|
||||
provider.length = 1;
|
||||
}
|
||||
|
||||
let allEdits: ISingleEditOperation[] = [];
|
||||
|
||||
editor.pushUndoStop();
|
||||
return sequence(provider.map(provider => {
|
||||
// create a formatting task per provider. they run sequentially,
|
||||
// potentially undoing the working of a previous formatter
|
||||
return () => {
|
||||
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
const model = editor.getModel();
|
||||
return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).then(edits => {
|
||||
// break edits into smaller edits
|
||||
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
|
||||
}).then(edits => {
|
||||
// make edit only when the editor didn't change while
|
||||
// computing and only when there are edits
|
||||
if (state.validate(editor) && isNonEmptyArray(edits)) {
|
||||
FormattingEdit.execute(editor, edits);
|
||||
allEdits = allEdits.concat(edits);
|
||||
}
|
||||
});
|
||||
};
|
||||
})).then(() => {
|
||||
alertFormattingEdits(allEdits);
|
||||
editor.pushUndoStop();
|
||||
editor.focus();
|
||||
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
|
||||
});
|
||||
}
|
||||
|
||||
class FormatOnType implements editorCommon.IEditorContribution {
|
||||
|
||||
private static readonly ID = 'editor.contrib.autoFormat';
|
||||
@@ -87,7 +217,7 @@ class FormatOnType implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
// no model
|
||||
if (!this.editor.getModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,6 +243,9 @@ class FormatOnType implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
private trigger(ch: string): void {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editor.getSelections().length > 1) {
|
||||
return;
|
||||
@@ -157,12 +290,14 @@ class FormatOnType implements editorCommon.IEditorContribution {
|
||||
|
||||
unbind.dispose();
|
||||
|
||||
if (canceled || isFalsyOrEmpty(edits)) {
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
FormattingEdit.execute(this.editor, edits);
|
||||
alertFormattingEdits(edits);
|
||||
if (isNonEmptyArray(edits)) {
|
||||
FormattingEdit.execute(this.editor, edits);
|
||||
alertFormattingEdits(edits);
|
||||
}
|
||||
|
||||
}, (err) => {
|
||||
unbind.dispose();
|
||||
@@ -184,14 +319,14 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
|
||||
|
||||
private static readonly ID = 'editor.contrib.formatOnPaste';
|
||||
|
||||
private editor: ICodeEditor;
|
||||
private workerService: IEditorWorkerService;
|
||||
private callOnDispose: IDisposable[];
|
||||
private callOnModel: IDisposable[];
|
||||
|
||||
constructor(editor: ICodeEditor, @IEditorWorkerService workerService: IEditorWorkerService) {
|
||||
this.editor = editor;
|
||||
this.workerService = workerService;
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
@IEditorWorkerService private readonly workerService: IEditorWorkerService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
this.callOnDispose = [];
|
||||
this.callOnModel = [];
|
||||
|
||||
@@ -212,15 +347,14 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
// no model
|
||||
if (!this.editor.getModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model = this.editor.getModel();
|
||||
|
||||
// no support
|
||||
let [support] = DocumentRangeFormattingEditProviderRegistry.ordered(model);
|
||||
if (!support || !support.provideDocumentRangeFormattingEdits) {
|
||||
if (!DocumentRangeFormattingEditProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,23 +364,17 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
private trigger(range: Range): void {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.editor.getSelections().length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
const { tabSize, insertSpaces } = model.getOptions();
|
||||
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
|
||||
getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }, CancellationToken.None).then(edits => {
|
||||
return this.workerService.computeMoreMinimalEdits(model.uri, edits);
|
||||
}).then(edits => {
|
||||
if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) {
|
||||
return;
|
||||
}
|
||||
FormattingEdit.execute(this.editor, edits);
|
||||
alertFormattingEdits(edits);
|
||||
});
|
||||
formatDocumentRange(this.telemetryService, this.workerService, this.editor, range, { tabSize, insertSpaces }, CancellationToken.None);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
@@ -259,48 +387,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractFormatAction extends EditorAction {
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
|
||||
const formattingPromise = this._getFormattingEdits(editor, CancellationToken.None);
|
||||
if (!formattingPromise) {
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
|
||||
// Capture the state of the editor
|
||||
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
|
||||
// Receive formatted value from worker
|
||||
return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits)).then(edits => {
|
||||
if (!state.validate(editor) || isFalsyOrEmpty(edits)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FormattingEdit.execute(editor, edits);
|
||||
alertFormattingEdits(edits);
|
||||
editor.focus();
|
||||
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
|
||||
}, err => {
|
||||
if (err instanceof Error && err.name === NoProviderError.Name) {
|
||||
this._notifyNoProviderError(notificationService, editor.getModel().getLanguageIdentifier().language);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]>;
|
||||
|
||||
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
|
||||
notificationService.info(nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", language));
|
||||
}
|
||||
}
|
||||
|
||||
export class FormatDocumentAction extends AbstractFormatAction {
|
||||
export class FormatDocumentAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@@ -323,18 +410,23 @@ export class FormatDocumentAction extends AbstractFormatAction {
|
||||
});
|
||||
}
|
||||
|
||||
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
|
||||
const model = editor.getModel();
|
||||
const { tabSize, insertSpaces } = model.getOptions();
|
||||
return getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token);
|
||||
}
|
||||
|
||||
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
|
||||
notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", language));
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
const { tabSize, insertSpaces } = editor.getModel().getOptions();
|
||||
return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
|
||||
if (NoProviderError.is(err)) {
|
||||
notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FormatSelectionAction extends AbstractFormatAction {
|
||||
export class FormatSelectionAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@@ -355,20 +447,19 @@ export class FormatSelectionAction extends AbstractFormatAction {
|
||||
});
|
||||
}
|
||||
|
||||
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
|
||||
const model = editor.getModel();
|
||||
let selection = editor.getSelection();
|
||||
if (selection.isEmpty()) {
|
||||
const maxColumn = model.getLineMaxColumn(selection.startLineNumber);
|
||||
selection = selection.setStartPosition(selection.startLineNumber, 1);
|
||||
selection = selection.setEndPosition(selection.endLineNumber, maxColumn);
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const { tabSize, insertSpaces } = model.getOptions();
|
||||
return getDocumentRangeFormattingEdits(model, selection, { tabSize, insertSpaces }, token);
|
||||
}
|
||||
|
||||
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
|
||||
notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", language));
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
const { tabSize, insertSpaces } = editor.getModel().getOptions();
|
||||
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
|
||||
if (NoProviderError.is(err)) {
|
||||
notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,21 +472,51 @@ registerEditorAction(FormatSelectionAction);
|
||||
// and we keep it here such that existing keybinding configurations etc will still work
|
||||
CommandsRegistry.registerCommand('editor.action.format', accessor => {
|
||||
const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
|
||||
if (editor) {
|
||||
return new class extends AbstractFormatAction {
|
||||
constructor() {
|
||||
super({} as IActionOptions);
|
||||
}
|
||||
_getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
|
||||
const model = editor.getModel();
|
||||
const editorSelection = editor.getSelection();
|
||||
const { tabSize, insertSpaces } = model.getOptions();
|
||||
|
||||
return editorSelection.isEmpty()
|
||||
? getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token)
|
||||
: getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces }, token);
|
||||
}
|
||||
}().run(accessor, editor);
|
||||
if (!editor || !editor.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
const { tabSize, insertSpaces } = editor.getModel().getOptions();
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
const telemetryService = accessor.get(ITelemetryService);
|
||||
|
||||
if (editor.getSelection().isEmpty()) {
|
||||
return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None);
|
||||
} else {
|
||||
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('editor.action.formatInspect', accessor => {
|
||||
|
||||
const editor = accessor.get(ICodeEditorService).getActiveCodeEditor();
|
||||
if (!editor || !editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
console.log(`Available Formatters for: ${editor.getModel().uri.toString(true)}`);
|
||||
// range formatters
|
||||
const documentRangeProvider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel());
|
||||
console.group('Range Formatters');
|
||||
if (documentRangeProvider.length === 0) {
|
||||
console.log('none');
|
||||
} else {
|
||||
documentRangeProvider.forEach(value => console.log(value.displayName));
|
||||
}
|
||||
console.groupEnd();
|
||||
|
||||
// whole document formatters
|
||||
const documentProvider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel());
|
||||
console.group('Document Formatters');
|
||||
if (documentProvider.length === 0) {
|
||||
console.log('none');
|
||||
} else {
|
||||
documentProvider.forEach(value => console.log(value.displayName));
|
||||
}
|
||||
console.groupEnd();
|
||||
});
|
||||
|
||||
MenuRegistry.addCommand({
|
||||
id: 'editor.action.formatInspect',
|
||||
category: nls.localize('cat', "Developer"),
|
||||
title: nls.localize('title', "Print Available Formatters..."),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { DefinitionLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult } from 'vs/editor/common/modes';
|
||||
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ function getDefinitions<T>(
|
||||
model: ITextModel,
|
||||
position: Position,
|
||||
registry: LanguageFeatureRegistry<T>,
|
||||
provide: (provider: T, model: ITextModel, position: Position) => DefinitionLink | DefinitionLink[] | null | undefined | Thenable<DefinitionLink | DefinitionLink[] | null | undefined>
|
||||
): Thenable<DefinitionLink[]> {
|
||||
provide: (provider: T, model: ITextModel, position: Position) => ProviderResult<LocationLink | LocationLink[]>
|
||||
): Promise<LocationLink[]> {
|
||||
const provider = registry.ordered(model);
|
||||
|
||||
// get results
|
||||
const promises = provider.map((provider): Thenable<DefinitionLink | DefinitionLink[] | null | undefined> => {
|
||||
const promises = provider.map((provider): Promise<LocationLink | LocationLink[] | null | undefined> => {
|
||||
return Promise.resolve(provide(provider, model, position)).then(undefined, err => {
|
||||
onUnexpectedExternalError(err);
|
||||
return null;
|
||||
@@ -34,25 +34,25 @@ function getDefinitions<T>(
|
||||
}
|
||||
|
||||
|
||||
export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideDefinition(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, DeclarationProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideDeclaration(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideImplementation(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideTypeDefinition(model, position, token);
|
||||
});
|
||||
|
||||
@@ -12,10 +12,10 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import * as corePosition from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { DefinitionLink, Location } from 'vs/editor/common/modes';
|
||||
import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes';
|
||||
import { MessageController } from 'vs/editor/contrib/message/messageController';
|
||||
import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
|
||||
import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController';
|
||||
@@ -29,7 +29,6 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
|
||||
export class DefinitionActionConfig {
|
||||
|
||||
constructor(
|
||||
@@ -44,14 +43,14 @@ export class DefinitionActionConfig {
|
||||
|
||||
export class DefinitionAction extends EditorAction {
|
||||
|
||||
private _configuration: DefinitionActionConfig;
|
||||
private readonly _configuration: DefinitionActionConfig;
|
||||
|
||||
constructor(configuration: DefinitionActionConfig, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._configuration = configuration;
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Thenable<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const progressService = accessor.get(IProgressService);
|
||||
@@ -59,7 +58,7 @@ export class DefinitionAction extends EditorAction {
|
||||
const model = editor.getModel();
|
||||
const pos = editor.getPosition();
|
||||
|
||||
const definitionPromise = this._getTargetLocationForPosition(model, pos, CancellationToken.None).then(references => {
|
||||
const definitionPromise = this._getTargetLocationForPosition(model, pos, CancellationToken.None).then(async references => {
|
||||
|
||||
if (model.isDisposed() || editor.getModel() !== model) {
|
||||
// new model, no more model
|
||||
@@ -69,20 +68,15 @@ export class DefinitionAction extends EditorAction {
|
||||
// * remove falsy references
|
||||
// * find reference at the current pos
|
||||
let idxOfCurrent = -1;
|
||||
const result: DefinitionLink[] = [];
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
let reference = references[i];
|
||||
const result: LocationLink[] = [];
|
||||
for (const reference of references) {
|
||||
if (!reference || !reference.range) {
|
||||
continue;
|
||||
}
|
||||
let { uri, range } = reference;
|
||||
let newLen = result.push({
|
||||
uri,
|
||||
range
|
||||
});
|
||||
const newLen = result.push(reference);
|
||||
if (this._configuration.filterCurrent
|
||||
&& uri.toString() === model.uri.toString()
|
||||
&& Range.containsPosition(range, pos)
|
||||
&& reference.uri.toString() === model.uri.toString()
|
||||
&& Range.containsPosition(reference.range, pos)
|
||||
&& idxOfCurrent === -1
|
||||
) {
|
||||
idxOfCurrent = newLen - 1;
|
||||
@@ -98,11 +92,11 @@ export class DefinitionAction extends EditorAction {
|
||||
} else if (result.length === 1 && idxOfCurrent !== -1) {
|
||||
// only the position at which we are -> adjust selection
|
||||
let [current] = result;
|
||||
this._openReference(editor, editorService, current, false);
|
||||
return this._openReference(editor, editorService, current, false).then(() => undefined);
|
||||
|
||||
} else {
|
||||
// handle multile results
|
||||
this._onResult(editorService, editor, new ReferencesModel(result));
|
||||
return this._onResult(editorService, editor, new ReferencesModel(result));
|
||||
}
|
||||
|
||||
}, (err) => {
|
||||
@@ -114,7 +108,7 @@ export class DefinitionAction extends EditorAction {
|
||||
return definitionPromise;
|
||||
}
|
||||
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitionsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
@@ -128,7 +122,7 @@ export class DefinitionAction extends EditorAction {
|
||||
return model.references.length > 1 && nls.localize('meta.title', " – {0} definitions", model.references.length);
|
||||
}
|
||||
|
||||
private _onResult(editorService: ICodeEditorService, editor: ICodeEditor, model: ReferencesModel) {
|
||||
private async _onResult(editorService: ICodeEditorService, editor: ICodeEditor, model: ReferencesModel): Promise<void> {
|
||||
|
||||
const msg = model.getAriaMessage();
|
||||
alert(msg);
|
||||
@@ -136,22 +130,31 @@ export class DefinitionAction extends EditorAction {
|
||||
if (this._configuration.openInPeek) {
|
||||
this._openInPeek(editorService, editor, model);
|
||||
} else {
|
||||
let next = model.nearestReference(editor.getModel().uri, editor.getPosition());
|
||||
this._openReference(editor, editorService, next, this._configuration.openToSide).then(editor => {
|
||||
if (editor && model.references.length > 1) {
|
||||
this._openInPeek(editorService, editor, model);
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
});
|
||||
const next = model.nearestReference(editor.getModel().uri, editor.getPosition());
|
||||
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide);
|
||||
if (targetEditor && model.references.length > 1) {
|
||||
this._openInPeek(editorService, targetEditor, model);
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location, sideBySide: boolean): Thenable<ICodeEditor> {
|
||||
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise<ICodeEditor> {
|
||||
// range is the target-selection-range when we have one
|
||||
// and the the fallback is the 'full' range
|
||||
let range: IRange = undefined;
|
||||
if (isLocationLink(reference)) {
|
||||
range = reference.targetSelectionRange;
|
||||
}
|
||||
if (!range) {
|
||||
range = reference.range;
|
||||
}
|
||||
|
||||
return editorService.openCodeEditor({
|
||||
resource: reference.uri,
|
||||
options: {
|
||||
selection: Range.collapseToStart(reference.range),
|
||||
selection: Range.collapseToStart(range),
|
||||
revealIfOpened: true,
|
||||
revealInCenterIfOutsideViewport: true
|
||||
}
|
||||
@@ -233,7 +236,7 @@ export class PeekDefinitionAction extends DefinitionAction {
|
||||
static readonly id = 'editor.action.peekDefinition';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(void 0, true, false), {
|
||||
super(new DefinitionActionConfig(undefined, true, false), {
|
||||
id: PeekDefinitionAction.id,
|
||||
label: nls.localize('actions.previewDecl.label', "Peek Definition"),
|
||||
alias: 'Peek Definition',
|
||||
@@ -258,7 +261,7 @@ export class PeekDefinitionAction extends DefinitionAction {
|
||||
|
||||
export class DeclarationAction extends DefinitionAction {
|
||||
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDeclarationsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
@@ -305,7 +308,7 @@ export class GoToDeclarationAction extends DeclarationAction {
|
||||
|
||||
export class PeekDeclarationAction extends DeclarationAction {
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(void 0, true, false), {
|
||||
super(new DefinitionActionConfig(undefined, true, false), {
|
||||
id: 'editor.action.peekDeclaration',
|
||||
label: nls.localize('actions.peekDecl.label', "Peek Declaration"),
|
||||
alias: 'Peek Declaration',
|
||||
@@ -322,7 +325,7 @@ export class PeekDeclarationAction extends DeclarationAction {
|
||||
}
|
||||
|
||||
export class ImplementationAction extends DefinitionAction {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getImplementationsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
@@ -380,7 +383,7 @@ export class PeekImplementationAction extends ImplementationAction {
|
||||
}
|
||||
|
||||
export class TypeDefinitionAction extends DefinitionAction {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getTypeDefinitionsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
@@ -452,29 +455,31 @@ registerEditorAction(GoToTypeDefinitionAction);
|
||||
registerEditorAction(PeekTypeDefinitionAction);
|
||||
|
||||
// Go to menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToDeclaration',
|
||||
title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
// {{SQL CARBON EDIT}} - Disable unused menu items
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
// group: '4_symbol_nav',
|
||||
// command: {
|
||||
// id: 'editor.action.goToDeclaration',
|
||||
// title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
|
||||
// },
|
||||
// order: 2
|
||||
// });
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToTypeDefinition',
|
||||
title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
// group: '4_symbol_nav',
|
||||
// command: {
|
||||
// id: 'editor.action.goToTypeDefinition',
|
||||
// title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
|
||||
// },
|
||||
// order: 3
|
||||
// });
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToImplementation',
|
||||
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
|
||||
},
|
||||
order: 6
|
||||
});
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
// group: '4_symbol_nav',
|
||||
// command: {
|
||||
// id: 'editor.action.goToImplementation',
|
||||
// title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
|
||||
// },
|
||||
// order: 4
|
||||
// });
|
||||
// {{SQL CARBON EDIT - End}}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { DefinitionProviderRegistry, DefinitionLink } from 'vs/editor/common/modes';
|
||||
import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes';
|
||||
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { getDefinitionsAtPosition } from './goToDefinition';
|
||||
@@ -35,12 +35,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
private toUnhook: IDisposable[];
|
||||
private decorations: string[];
|
||||
private currentWordUnderMouse: IWordAtPosition;
|
||||
private previousPromise: CancelablePromise<DefinitionLink[]>;
|
||||
private previousPromise: CancelablePromise<LocationLink[]>;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@ITextModelService private textModelResolverService: ITextModelService,
|
||||
@IModeService private modeService: IModeService
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@IModeService private readonly modeService: IModeService
|
||||
) {
|
||||
this.toUnhook = [];
|
||||
this.decorations = [];
|
||||
@@ -152,8 +152,8 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
const previewValue = this.getPreviewValue(textEditorModel, startLineNumber);
|
||||
|
||||
let wordRange: Range;
|
||||
if (result.origin) {
|
||||
wordRange = Range.lift(result.origin);
|
||||
if (result.originSelectionRange) {
|
||||
wordRange = Range.lift(result.originSelectionRange);
|
||||
} else {
|
||||
wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
|
||||
}
|
||||
@@ -281,7 +281,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
DefinitionProviderRegistry.has(this.editor.getModel());
|
||||
}
|
||||
|
||||
private findDefinition(target: IMouseTarget, token: CancellationToken): Thenable<DefinitionLink[]> {
|
||||
private findDefinition(target: IMouseTarget, token: CancellationToken): Promise<LocationLink[] | null> {
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return Promise.resolve(null);
|
||||
@@ -290,7 +290,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
return getDefinitionsAtPosition(model, target.position, token);
|
||||
}
|
||||
|
||||
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Thenable<any> {
|
||||
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise<any> {
|
||||
this.editor.setPosition(target.position);
|
||||
const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: undefined, label: undefined, id: undefined, precondition: undefined });
|
||||
return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor));
|
||||
|
||||
@@ -23,6 +23,7 @@ import { compare } from 'vs/base/common/strings';
|
||||
import { binarySearch } from 'vs/base/common/arrays';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
class MarkerModel {
|
||||
|
||||
@@ -31,7 +32,7 @@ class MarkerModel {
|
||||
private _nextIdx: number;
|
||||
private _toUnbind: IDisposable[];
|
||||
private _ignoreSelectionChange: boolean;
|
||||
private readonly _onCurrentMarkerChanged: Emitter<IMarker>;
|
||||
private readonly _onCurrentMarkerChanged: Emitter<IMarker | undefined>;
|
||||
private readonly _onMarkerSetChanged: Emitter<MarkerModel>;
|
||||
|
||||
constructor(editor: ICodeEditor, markers: IMarker[]) {
|
||||
@@ -320,30 +321,30 @@ class MarkerNavigationAction extends EditorAction {
|
||||
this._multiFile = multiFile;
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Thenable<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
|
||||
const markerService = accessor.get(IMarkerService);
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const controller = MarkerController.get(editor);
|
||||
if (!controller) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const model = controller.getOrCreateModel();
|
||||
const atEdge = model.move(this._isNext, !this._multiFile);
|
||||
if (!atEdge || !this._multiFile) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// try with the next/prev file
|
||||
let markers = markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }).sort(MarkerNavigationAction.compareMarker);
|
||||
if (markers.length === 0) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let editorModel = editor.getModel();
|
||||
if (!editorModel) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let oldMarker = model.currentMarker || <IMarker>{ resource: editorModel!.uri, severity: MarkerSeverity.Error, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 };
|
||||
@@ -363,7 +364,7 @@ class MarkerNavigationAction extends EditorAction {
|
||||
// the next `resource` is this resource which
|
||||
// means we cycle within this file
|
||||
model.move(this._isNext, true);
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// close the widget for this editor-instance, open the resource
|
||||
@@ -468,3 +469,22 @@ registerEditorCommand(new MarkerCommand({
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape]
|
||||
}
|
||||
}));
|
||||
|
||||
// Go to menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '6_problem_nav',
|
||||
command: {
|
||||
id: 'editor.action.marker.nextInFiles',
|
||||
title: nls.localize({ key: 'miGotoNextProblem', comment: ['&& denotes a mnemonic'] }, "Next &&Problem")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '6_problem_nav',
|
||||
command: {
|
||||
id: 'editor.action.marker.prevInFiles',
|
||||
title: nls.localize({ key: 'miGotoPreviousProblem', comment: ['&& denotes a mnemonic'] }, "Previous &&Problem")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
@@ -6,7 +6,7 @@
|
||||
/* marker zone */
|
||||
|
||||
.monaco-editor .marker-widget {
|
||||
padding: 6px 12px;
|
||||
padding: 3px 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -35,6 +35,11 @@
|
||||
|
||||
.monaco-editor .marker-widget .descriptioncontainer .message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-editor .marker-widget .descriptioncontainer .message .details {
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.monaco-editor .marker-widget .descriptioncontainer .message .source,
|
||||
|
||||
@@ -80,22 +80,43 @@ class MessageWidget {
|
||||
|
||||
update({ source, message, relatedInformation, code }: IMarker): void {
|
||||
|
||||
if (source) {
|
||||
const lines = message.split(/\r\n|\r|\n/g);
|
||||
this._lines = lines.length;
|
||||
this._longestLineLength = 0;
|
||||
for (const line of lines) {
|
||||
this._longestLineLength = Math.max(line.length, this._longestLineLength);
|
||||
const lines = message.split(/\r\n|\r|\n/g);
|
||||
this._lines = lines.length;
|
||||
this._longestLineLength = 0;
|
||||
for (const line of lines) {
|
||||
this._longestLineLength = Math.max(line.length, this._longestLineLength);
|
||||
}
|
||||
|
||||
dom.clearNode(this._messageBlock);
|
||||
let lastLineElement = this._messageBlock;
|
||||
for (const line of lines) {
|
||||
lastLineElement = document.createElement('div');
|
||||
lastLineElement.innerText = line;
|
||||
this._editor.applyFontInfo(lastLineElement);
|
||||
this._messageBlock.appendChild(lastLineElement);
|
||||
}
|
||||
if (source || code) {
|
||||
const detailsElement = document.createElement('span');
|
||||
dom.addClass(detailsElement, 'details');
|
||||
lastLineElement.appendChild(detailsElement);
|
||||
if (source) {
|
||||
const sourceElement = document.createElement('span');
|
||||
sourceElement.innerText = source;
|
||||
dom.addClass(sourceElement, 'source');
|
||||
detailsElement.appendChild(sourceElement);
|
||||
}
|
||||
if (code) {
|
||||
const codeElement = document.createElement('span');
|
||||
codeElement.innerText = `(${code})`;
|
||||
dom.addClass(codeElement, 'code');
|
||||
detailsElement.appendChild(codeElement);
|
||||
}
|
||||
} else {
|
||||
this._lines = 1;
|
||||
this._longestLineLength = message.length;
|
||||
}
|
||||
|
||||
dom.clearNode(this._relatedBlock);
|
||||
|
||||
if (isNonEmptyArray(relatedInformation)) {
|
||||
this._relatedBlock.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * .66)}px`;
|
||||
const relatedInformationNode = this._relatedBlock.appendChild(document.createElement('div'));
|
||||
relatedInformationNode.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * 0.66)}px`;
|
||||
this._lines += 1;
|
||||
|
||||
for (const related of relatedInformation) {
|
||||
@@ -116,30 +137,10 @@ class MessageWidget {
|
||||
container.appendChild(relatedMessage);
|
||||
|
||||
this._lines += 1;
|
||||
this._relatedBlock.appendChild(container);
|
||||
relatedInformationNode.appendChild(container);
|
||||
}
|
||||
}
|
||||
|
||||
dom.clearNode(this._messageBlock);
|
||||
if (source) {
|
||||
const sourceElement = document.createElement('div');
|
||||
sourceElement.innerText = `[${source}] `;
|
||||
dom.addClass(sourceElement, 'source');
|
||||
this._editor.applyFontInfo(sourceElement);
|
||||
this._messageBlock.appendChild(sourceElement);
|
||||
}
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.innerText = message;
|
||||
this._editor.applyFontInfo(messageElement);
|
||||
this._messageBlock.appendChild(messageElement);
|
||||
if (code) {
|
||||
const codeElement = document.createElement('div');
|
||||
codeElement.innerText = ` [${code}]`;
|
||||
dom.addClass(codeElement, 'code');
|
||||
this._editor.applyFontInfo(codeElement);
|
||||
this._messageBlock.appendChild(codeElement);
|
||||
}
|
||||
|
||||
const fontInfo = this._editor.getConfiguration().fontInfo;
|
||||
const scrollWidth = Math.ceil(fontInfo.typicalFullwidthCharacterWidth * this._longestLineLength * 0.75);
|
||||
const scrollHeight = fontInfo.lineHeight * this._lines;
|
||||
|
||||
@@ -36,6 +36,14 @@
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.monaco-editor-hover hr {
|
||||
margin-top: 4px;
|
||||
margin-bottom: -6px;
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover p:first-child,
|
||||
.monaco-editor-hover ul:first-child {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as nls from 'vs/nls';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
@@ -25,6 +24,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
|
||||
|
||||
export class ModesHoverController implements IEditorContribution {
|
||||
|
||||
@@ -62,6 +62,7 @@ export class ModesHoverController implements IEditorContribution {
|
||||
constructor(private readonly _editor: ICodeEditor,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService,
|
||||
@IThemeService private readonly _themeService: IThemeService
|
||||
) {
|
||||
this._toUnhook = [];
|
||||
@@ -146,18 +147,17 @@ export class ModesHoverController implements IEditorContribution {
|
||||
private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {
|
||||
// const this._editor.getConfiguration().contribInfo.hover.sticky;
|
||||
let targetType = mouseEvent.target.type;
|
||||
const hasStopKey = (platform.isMacintosh ? mouseEvent.event.metaKey : mouseEvent.event.ctrlKey);
|
||||
|
||||
if (this._isMouseDown && this._hoverClicked && this.contentWidget.isColorPickerVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID && !hasStopKey) {
|
||||
if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID) {
|
||||
// mouse moved on top of content hover widget
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID && !hasStopKey) {
|
||||
if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) {
|
||||
// mouse moved on top of overlay hover widget
|
||||
return;
|
||||
}
|
||||
@@ -174,13 +174,13 @@ export class ModesHoverController implements IEditorContribution {
|
||||
if (targetType === MouseTargetType.CONTENT_TEXT) {
|
||||
this.glyphWidget.hide();
|
||||
|
||||
if (this._isHoverEnabled) {
|
||||
if (this._isHoverEnabled && mouseEvent.target.range) {
|
||||
this.contentWidget.startShowingAt(mouseEvent.target.range, HoverStartMode.Delayed, false);
|
||||
}
|
||||
} else if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN) {
|
||||
this.contentWidget.hide();
|
||||
|
||||
if (this._isHoverEnabled) {
|
||||
if (this._isHoverEnabled && mouseEvent.target.position) {
|
||||
this.glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber);
|
||||
}
|
||||
} else {
|
||||
@@ -206,7 +206,7 @@ export class ModesHoverController implements IEditorContribution {
|
||||
|
||||
private _createHoverWidget() {
|
||||
const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService);
|
||||
this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._themeService);
|
||||
this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService, this._openerService);
|
||||
this._glyphWidget = new ModesGlyphHoverWidget(this._editor, renderer);
|
||||
}
|
||||
|
||||
@@ -224,11 +224,9 @@ export class ModesHoverController implements IEditorContribution {
|
||||
|
||||
if (this._glyphWidget) {
|
||||
this._glyphWidget.dispose();
|
||||
this._glyphWidget = null;
|
||||
}
|
||||
if (this._contentWidget) {
|
||||
this._contentWidget.dispose();
|
||||
this._contentWidget = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,6 +254,9 @@ class ShowHoverAction extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
let controller = ModesHoverController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
@@ -283,6 +284,8 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (hoverBorder) {
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover { border: 1px solid ${hoverBorder}; }`);
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover hr { border-bottom: 0px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
|
||||
@@ -84,7 +84,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent
|
||||
return this._containerDomNode;
|
||||
}
|
||||
|
||||
public showAt(position: Position, range: Range, focus: boolean): void {
|
||||
public showAt(position: Position, range: Range | null, focus: boolean): void {
|
||||
// Position has changed
|
||||
this._showAtPosition = position;
|
||||
this._showAtRange = range;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { DocumentColorProvider, Hover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
|
||||
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
|
||||
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
|
||||
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
|
||||
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
|
||||
@@ -23,6 +23,13 @@ import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contri
|
||||
import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets';
|
||||
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
class ColorHover {
|
||||
@@ -34,7 +41,15 @@ class ColorHover {
|
||||
) { }
|
||||
}
|
||||
|
||||
type HoverPart = Hover | ColorHover;
|
||||
class MarkerHover {
|
||||
|
||||
constructor(
|
||||
public readonly range: IRange,
|
||||
public readonly marker: IMarker,
|
||||
) { }
|
||||
}
|
||||
|
||||
type HoverPart = MarkdownHover | ColorHover | MarkerHover;
|
||||
|
||||
class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
|
||||
@@ -42,7 +57,10 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
private _result: HoverPart[];
|
||||
private _range: Range | null;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private _markerDecorationsService: IMarkerDecorationsService
|
||||
) {
|
||||
this._editor = editor;
|
||||
this._range = null;
|
||||
}
|
||||
@@ -78,6 +96,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const lineNumber = this._range.startLineNumber;
|
||||
|
||||
if (lineNumber > this._editor.getModel().getLineCount()) {
|
||||
@@ -86,19 +105,25 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
}
|
||||
|
||||
const colorDetector = ColorDetector.get(this._editor);
|
||||
const maxColumn = this._editor.getModel().getLineMaxColumn(lineNumber);
|
||||
const maxColumn = model.getLineMaxColumn(lineNumber);
|
||||
const lineDecorations = this._editor.getLineDecorations(lineNumber);
|
||||
let didFindColor = false;
|
||||
|
||||
const result = lineDecorations.map(d => {
|
||||
const hoverRange = this._range;
|
||||
const result = lineDecorations.map((d): HoverPart | null => {
|
||||
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
|
||||
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
|
||||
|
||||
if (startColumn > this._range.startColumn || this._range.endColumn > endColumn) {
|
||||
if (startColumn > hoverRange.startColumn || hoverRange.endColumn > endColumn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const range = new Range(this._range.startLineNumber, startColumn, this._range.startLineNumber, endColumn);
|
||||
const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn);
|
||||
const marker = this._markerDecorationsService.getMarker(model, d);
|
||||
if (marker) {
|
||||
return new MarkerHover(range, marker);
|
||||
}
|
||||
|
||||
const colorData = colorDetector.getColorData(d.range.getStartPosition());
|
||||
|
||||
if (!didFindColor && colorData) {
|
||||
@@ -111,7 +136,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
return null;
|
||||
}
|
||||
|
||||
let contents: IMarkdownString[];
|
||||
let contents: IMarkdownString[] = [];
|
||||
|
||||
if (d.options.hoverMessage) {
|
||||
if (Array.isArray(d.options.hoverMessage)) {
|
||||
@@ -125,7 +150,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
}
|
||||
});
|
||||
|
||||
return result.filter(d => !!d);
|
||||
return coalesce(result);
|
||||
}
|
||||
|
||||
onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void {
|
||||
@@ -154,7 +179,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
|
||||
|
||||
private _getLoadingMessage(): HoverPart {
|
||||
return {
|
||||
range: this._range,
|
||||
range: this._range || undefined,
|
||||
contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]
|
||||
};
|
||||
}
|
||||
@@ -179,13 +204,15 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
markdownRenderer: MarkdownRenderer,
|
||||
private readonly _themeService: IThemeService
|
||||
markerDecorationsService: IMarkerDecorationsService,
|
||||
private readonly _themeService: IThemeService,
|
||||
private readonly _openerService: IOpenerService | null = NullOpenerService,
|
||||
) {
|
||||
super(ModesContentHoverWidget.ID, editor);
|
||||
|
||||
this._messages = [];
|
||||
this._lastRange = null;
|
||||
this._computer = new ModesContentComputer(this._editor);
|
||||
this._computer = new ModesContentComputer(this._editor, markerDecorationsService);
|
||||
this._highlightDecorations = [];
|
||||
this._isChangingDecorations = false;
|
||||
|
||||
@@ -248,14 +275,14 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
// The range might have changed, but the hover is visible
|
||||
// Instead of hiding it completely, filter out messages that are still in the new range and
|
||||
// kick off a new computation
|
||||
if (this._showAtPosition.lineNumber !== range.startLineNumber) {
|
||||
if (!this._showAtPosition || this._showAtPosition.lineNumber !== range.startLineNumber) {
|
||||
this.hide();
|
||||
} else {
|
||||
let filteredMessages: HoverPart[] = [];
|
||||
for (let i = 0, len = this._messages.length; i < len; i++) {
|
||||
const msg = this._messages[i];
|
||||
const rng = msg.range;
|
||||
if (rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
|
||||
if (rng && rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
|
||||
filteredMessages.push(msg);
|
||||
}
|
||||
}
|
||||
@@ -311,7 +338,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
|
||||
// update column from which to show
|
||||
let renderColumn = Number.MAX_VALUE;
|
||||
let highlightRange = Range.lift(messages[0].range);
|
||||
let highlightRange: Range | null = messages[0].range ? Range.lift(messages[0].range) : null;
|
||||
let fragment = document.createDocumentFragment();
|
||||
let isEmptyHoverContent = true;
|
||||
|
||||
@@ -323,24 +350,19 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
}
|
||||
|
||||
renderColumn = Math.min(renderColumn, msg.range.startColumn);
|
||||
highlightRange = Range.plusRange(highlightRange, msg.range);
|
||||
highlightRange = highlightRange ? Range.plusRange(highlightRange, msg.range) : Range.lift(msg.range);
|
||||
|
||||
if (!(msg instanceof ColorHover)) {
|
||||
msg.contents
|
||||
.filter(contents => !isEmptyMarkdownString(contents))
|
||||
.forEach(contents => {
|
||||
const renderedContents = this._markdownRenderer.render(contents);
|
||||
markdownDisposeable = renderedContents;
|
||||
fragment.appendChild($('div.hover-row', null, renderedContents.element));
|
||||
isEmptyHoverContent = false;
|
||||
});
|
||||
} else {
|
||||
if (msg instanceof ColorHover) {
|
||||
containColorPicker = true;
|
||||
|
||||
const { red, green, blue, alpha } = msg.color;
|
||||
const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
|
||||
const color = new Color(rgba);
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorModel = this._editor.getModel();
|
||||
let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
|
||||
let colorInfo = { range: msg.range, color: msg.color };
|
||||
@@ -350,7 +372,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio, this._themeService);
|
||||
|
||||
getColorPresentations(editorModel, colorInfo, msg.provider, CancellationToken.None).then(colorPresentations => {
|
||||
model.colorPresentations = colorPresentations;
|
||||
model.colorPresentations = colorPresentations || [];
|
||||
if (!this._editor.hasModel()) {
|
||||
// gone...
|
||||
return;
|
||||
}
|
||||
const originalText = this._editor.getModel().getValueInRange(msg.range);
|
||||
model.guessColorPresentation(color, originalText);
|
||||
|
||||
@@ -393,7 +419,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
alpha: color.rgba.a
|
||||
}
|
||||
}, msg.provider, CancellationToken.None).then((colorPresentations) => {
|
||||
model.colorPresentations = colorPresentations;
|
||||
model.colorPresentations = colorPresentations || [];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -409,6 +435,20 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
|
||||
this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, markdownDisposeable]);
|
||||
});
|
||||
} else {
|
||||
if (msg instanceof MarkerHover) {
|
||||
isEmptyHoverContent = false;
|
||||
fragment.appendChild($('div.hover-row', undefined, this.renderMarkerHover(msg)));
|
||||
} else {
|
||||
msg.contents
|
||||
.filter(contents => !isEmptyMarkdownString(contents))
|
||||
.forEach(contents => {
|
||||
const renderedContents = this._markdownRenderer.render(contents);
|
||||
markdownDisposeable = renderedContents;
|
||||
fragment.appendChild($('div.hover-row', undefined, renderedContents.element));
|
||||
isEmptyHoverContent = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -420,13 +460,50 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
}
|
||||
|
||||
this._isChangingDecorations = true;
|
||||
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, [{
|
||||
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{
|
||||
range: highlightRange,
|
||||
options: ModesContentHoverWidget._DECORATION_OPTIONS
|
||||
}]);
|
||||
}] : []);
|
||||
this._isChangingDecorations = false;
|
||||
}
|
||||
|
||||
private renderMarkerHover(markerHover: MarkerHover): HTMLElement {
|
||||
const hoverElement = $('div');
|
||||
const { source, message, code, relatedInformation } = markerHover.marker;
|
||||
|
||||
const messageElement = dom.append(hoverElement, $('span'));
|
||||
messageElement.style.whiteSpace = 'pre-wrap';
|
||||
messageElement.innerText = message.trim();
|
||||
this._editor.applyFontInfo(messageElement);
|
||||
|
||||
if (source || code) {
|
||||
const detailsElement = dom.append(hoverElement, $('span'));
|
||||
detailsElement.style.opacity = '0.6';
|
||||
detailsElement.style.paddingLeft = '6px';
|
||||
detailsElement.innerText = source && code ? `${source}(${code})` : `(${code})`;
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(relatedInformation)) {
|
||||
const listElement = dom.append(hoverElement, $('ul'));
|
||||
for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
|
||||
const item = dom.append(listElement, $('li'));
|
||||
const a = dom.append(item, $('a'));
|
||||
a.innerText = `${basename(resource.path)}(${startLineNumber}, ${startColumn})`;
|
||||
a.style.cursor = 'pointer';
|
||||
a.onclick = e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this._openerService) {
|
||||
this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError);
|
||||
}
|
||||
};
|
||||
const messageElement = dom.append<HTMLAnchorElement>(item, $('span'));
|
||||
messageElement.innerText = `: ${message}`;
|
||||
}
|
||||
}
|
||||
return hoverElement;
|
||||
}
|
||||
|
||||
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
|
||||
className: 'hoverHighlight'
|
||||
});
|
||||
@@ -439,10 +516,13 @@ function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean {
|
||||
for (let i = 0; i < first.length; i++) {
|
||||
const firstElement = first[i];
|
||||
const secondElement = second[i];
|
||||
if (firstElement instanceof ColorHover) {
|
||||
if (firstElement instanceof MarkerHover && secondElement instanceof MarkerHover) {
|
||||
return IMarkerData.makeKey(firstElement.marker) === IMarkerData.makeKey(secondElement.marker);
|
||||
}
|
||||
if (firstElement instanceof ColorHover || secondElement instanceof ColorHover) {
|
||||
return false;
|
||||
}
|
||||
if (secondElement instanceof ColorHover) {
|
||||
if (firstElement instanceof MarkerHover || secondElement instanceof MarkerHover) {
|
||||
return false;
|
||||
}
|
||||
if (!markedStringsEquals(firstElement.contents, secondElement.contents)) {
|
||||
|
||||
@@ -43,28 +43,25 @@ class MarginComputer implements IHoverComputer<IHoverMessage[]> {
|
||||
};
|
||||
};
|
||||
|
||||
let lineDecorations = this._editor.getLineDecorations(this._lineNumber);
|
||||
const lineDecorations = this._editor.getLineDecorations(this._lineNumber);
|
||||
|
||||
let result: IHoverMessage[] = [];
|
||||
const result: IHoverMessage[] = [];
|
||||
if (!lineDecorations) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (let i = 0, len = lineDecorations.length; i < len; i++) {
|
||||
let d = lineDecorations[i];
|
||||
|
||||
for (const d of lineDecorations) {
|
||||
if (!d.options.glyphMarginClassName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hoverMessage = d.options.glyphMarginHoverMessage;
|
||||
|
||||
if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(hoverMessage)) {
|
||||
result = result.concat(hoverMessage.map(toHoverMessage));
|
||||
result.push(...hoverMessage.map(toHoverMessage));
|
||||
} else {
|
||||
result.push(toHoverMessage(hoverMessage));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class InPlaceReplaceController implements IEditorContribution {
|
||||
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
const modelURI = model.uri;
|
||||
if (!this.editorWorkerService.canNavigateValueSet(modelURI)) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection!, up));
|
||||
@@ -152,7 +152,7 @@ class InPlaceReplaceUp extends EditorAction {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
|
||||
const controller = InPlaceReplaceController.get(editor);
|
||||
if (!controller) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return controller.run(this.id, true);
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class InPlaceReplaceDown extends EditorAction {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
|
||||
const controller = InPlaceReplaceController.get(editor);
|
||||
if (!controller) {
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return controller.run(this.id, false);
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
}
|
||||
|
||||
// no model
|
||||
if (!this.editor.getModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,11 @@ abstract class AbstractCopyLinesAction extends EditorAction {
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
|
||||
let commands: ICommand[] = [];
|
||||
let selections = editor.getSelections() || [];
|
||||
const commands: ICommand[] = [];
|
||||
const selections = editor.getSelections() || [];
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
commands.push(new CopyLinesCommand(selections[i], this.down));
|
||||
for (const selection of selections) {
|
||||
commands.push(new CopyLinesCommand(selection, this.down));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
@@ -114,8 +114,8 @@ abstract class AbstractMoveLinesAction extends EditorAction {
|
||||
let selections = editor.getSelections() || [];
|
||||
let autoIndent = editor.getConfiguration().autoIndent;
|
||||
|
||||
for (let i = 0; i < selections.length; i++) {
|
||||
commands.push(new MoveLinesCommand(selections[i], this.down, autoIndent));
|
||||
for (const selection of selections) {
|
||||
commands.push(new MoveLinesCommand(selection, this.down, autoIndent));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
@@ -181,8 +181,7 @@ export abstract class AbstractSortLinesAction extends EditorAction {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
const selections = editor.getSelections() || [];
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
for (const selection of selections) {
|
||||
if (!SortLinesCommand.canRun(editor.getModel(), selection, this.descending)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,18 +21,18 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'omicron',
|
||||
'beta',
|
||||
'alpha'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let sortLinesAscendingAction = new SortLinesAscendingAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 3, 5));
|
||||
sortLinesAscendingAction.run(null, editor);
|
||||
sortLinesAscendingAction.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), [
|
||||
'alpha',
|
||||
'beta',
|
||||
'omicron'
|
||||
]);
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 3, 7).toString());
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 3, 7).toString());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,12 +46,12 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'omicron',
|
||||
'beta',
|
||||
'alpha'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let sortLinesAscendingAction = new SortLinesAscendingAction();
|
||||
|
||||
editor.setSelections([new Selection(1, 1, 3, 5), new Selection(5, 1, 7, 5)]);
|
||||
sortLinesAscendingAction.run(null, editor);
|
||||
sortLinesAscendingAction.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), [
|
||||
'alpha',
|
||||
'beta',
|
||||
@@ -65,7 +65,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
new Selection(1, 1, 3, 7),
|
||||
new Selection(5, 1, 7, 7)
|
||||
];
|
||||
editor.getSelections().forEach((actualSelection, index) => {
|
||||
editor.getSelections()!.forEach((actualSelection, index) => {
|
||||
assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString());
|
||||
});
|
||||
});
|
||||
@@ -79,18 +79,18 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'alpha',
|
||||
'beta',
|
||||
'omicron'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let sortLinesDescendingAction = new SortLinesDescendingAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 3, 7));
|
||||
sortLinesDescendingAction.run(null, editor);
|
||||
sortLinesDescendingAction.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), [
|
||||
'omicron',
|
||||
'beta',
|
||||
'alpha'
|
||||
]);
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 3, 5).toString());
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 3, 5).toString());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,12 +104,12 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'alpha',
|
||||
'beta',
|
||||
'omicron'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let sortLinesDescendingAction = new SortLinesDescendingAction();
|
||||
|
||||
editor.setSelections([new Selection(1, 1, 3, 7), new Selection(5, 1, 7, 7)]);
|
||||
sortLinesDescendingAction.run(null, editor);
|
||||
sortLinesDescendingAction.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), [
|
||||
'omicron',
|
||||
'beta',
|
||||
@@ -123,7 +123,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
new Selection(1, 1, 3, 5),
|
||||
new Selection(5, 1, 7, 5)
|
||||
];
|
||||
editor.getSelections().forEach((actualSelection, index) => {
|
||||
editor.getSelections()!.forEach((actualSelection, index) => {
|
||||
assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString());
|
||||
});
|
||||
});
|
||||
@@ -138,16 +138,16 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'one',
|
||||
'two',
|
||||
'three'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let deleteAllLeftAction = new DeleteAllLeftAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 2, 1, 2));
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'ne', '001');
|
||||
|
||||
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(3, 2, 3, 2)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), 'wo', '002');
|
||||
assert.equal(model.getLineContent(3), 'hree', '003');
|
||||
});
|
||||
@@ -159,21 +159,21 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'one',
|
||||
'two',
|
||||
'three'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let deleteAllLeftAction = new DeleteAllLeftAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 1));
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'onetwo', '001');
|
||||
|
||||
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLinesContent()[0], 'onetwothree');
|
||||
assert.equal(model.getLinesContent().length, 1);
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLinesContent()[0], 'onetwothree');
|
||||
});
|
||||
});
|
||||
@@ -187,8 +187,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'my wife doesnt believe in me',
|
||||
'nonononono',
|
||||
'bitconneeeect'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let deleteAllLeftAction = new DeleteAllLeftAction();
|
||||
|
||||
const beforeSecondWasoSelection = new Selection(3, 5, 3, 5);
|
||||
@@ -198,7 +198,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
editor.setSelections([beforeSecondWasoSelection, endOfBCCSelection, endOfNonono]);
|
||||
let selections;
|
||||
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
selections = editor.getSelections();
|
||||
|
||||
assert.equal(model.getLineContent(2), '');
|
||||
@@ -226,7 +226,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
selections[2].endColumn
|
||||
], [5, 1, 5, 1]);
|
||||
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
selections = editor.getSelections();
|
||||
|
||||
assert.equal(model.getLineContent(1), 'hi my name is Carlos Matos waso waso');
|
||||
@@ -259,28 +259,28 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'hola',
|
||||
'world',
|
||||
'hello world',
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let deleteAllLeftAction = new DeleteAllLeftAction();
|
||||
|
||||
editor.setSelections([new Selection(1, 2, 1, 2), new Selection(1, 4, 1, 4)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'lo', '001');
|
||||
|
||||
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(2, 4, 2, 5)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), 'ord', '002');
|
||||
|
||||
editor.setSelections([new Selection(3, 2, 3, 5), new Selection(3, 7, 3, 7)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(3), 'world', '003');
|
||||
|
||||
editor.setSelections([new Selection(4, 3, 4, 3), new Selection(4, 5, 5, 4)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(4), 'lljour', '004');
|
||||
|
||||
editor.setSelections([new Selection(5, 3, 6, 3), new Selection(6, 5, 7, 5), new Selection(7, 7, 7, 7)]);
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(5), 'horlworld', '005');
|
||||
});
|
||||
});
|
||||
@@ -291,8 +291,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'one',
|
||||
'two',
|
||||
'three'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let deleteAllLeftAction = new DeleteAllLeftAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
@@ -301,7 +301,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
assert.equal(model.getLineContent(1), 'Typing some text here on line one');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31));
|
||||
|
||||
deleteAllLeftAction.run(null, editor);
|
||||
deleteAllLeftAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'one');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1));
|
||||
|
||||
@@ -327,34 +327,34 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'',
|
||||
'',
|
||||
'hello world'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let joinLinesAction = new JoinLinesAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 2, 1, 2));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello world', '001');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 6, 1, 6).toString(), '002');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 6, 1, 6).toString(), '002');
|
||||
|
||||
editor.setSelection(new Selection(2, 2, 2, 2));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), 'hello world', '003');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 7, 2, 7).toString(), '004');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 7, 2, 7).toString(), '004');
|
||||
|
||||
editor.setSelection(new Selection(3, 2, 3, 2));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(3), 'hello world', '005');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 7, 3, 7).toString(), '006');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(3, 7, 3, 7).toString(), '006');
|
||||
|
||||
editor.setSelection(new Selection(4, 2, 5, 3));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(4), 'hello world', '007');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 8).toString(), '008');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 2, 4, 8).toString(), '008');
|
||||
|
||||
editor.setSelection(new Selection(5, 1, 7, 3));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(5), 'hello world', '009');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(5, 1, 5, 3).toString(), '010');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(5, 1, 5, 3).toString(), '010');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -363,15 +363,15 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
[
|
||||
'hello',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let joinLinesAction = new JoinLinesAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 1));
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello', '001');
|
||||
assert.equal(model.getLineContent(2), 'world', '002');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 6, 2, 6).toString(), '003');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 6, 2, 6).toString(), '003');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -389,8 +389,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'',
|
||||
'',
|
||||
'hello world'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let joinLinesAction = new JoinLinesAction();
|
||||
|
||||
editor.setSelections([
|
||||
@@ -403,9 +403,9 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
new Selection(10, 1, 10, 1)
|
||||
]);
|
||||
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world', '001');
|
||||
assert.deepEqual(editor.getSelections().toString(), [
|
||||
assert.deepEqual(editor.getSelections()!.toString(), [
|
||||
/** primary cursor */
|
||||
new Selection(3, 4, 3, 8),
|
||||
new Selection(1, 6, 1, 6),
|
||||
@@ -415,7 +415,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
].toString(), '002');
|
||||
|
||||
/** primary cursor */
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 4, 3, 8).toString(), '003');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(3, 4, 3, 8).toString(), '003');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -424,8 +424,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
[
|
||||
'hello',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let joinLinesAction = new JoinLinesAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 6, 1, 6));
|
||||
@@ -434,7 +434,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
assert.equal(model.getLineContent(1), 'hello my dear');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
|
||||
|
||||
joinLinesAction.run(null, editor);
|
||||
joinLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello my dear world');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
|
||||
|
||||
@@ -452,34 +452,34 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'',
|
||||
'',
|
||||
' ',
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let transposeAction = new TransposeAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello world', '001');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 2, 1, 2).toString(), '002');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 2, 1, 2).toString(), '002');
|
||||
|
||||
editor.setSelection(new Selection(1, 6, 1, 6));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hell oworld', '003');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 7, 1, 7).toString(), '004');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 7, 1, 7).toString(), '004');
|
||||
|
||||
editor.setSelection(new Selection(1, 12, 1, 12));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hell oworl', '005');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '006');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '006');
|
||||
|
||||
editor.setSelection(new Selection(3, 1, 3, 1));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(3), '', '007');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 1, 4, 1).toString(), '008');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 1, 4, 1).toString(), '008');
|
||||
|
||||
editor.setSelection(new Selection(4, 2, 4, 2));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(4), ' ', '009');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 3, 4, 3).toString(), '010');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 3, 4, 3).toString(), '010');
|
||||
}
|
||||
);
|
||||
|
||||
@@ -494,29 +494,29 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'hello world',
|
||||
'',
|
||||
'hello world'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let transposeAction = new TransposeAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), '', '011');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 1).toString(), '012');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 1).toString(), '012');
|
||||
|
||||
editor.setSelection(new Selection(3, 6, 3, 6));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(4), 'oworld', '013');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 2).toString(), '014');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 2, 4, 2).toString(), '014');
|
||||
|
||||
editor.setSelection(new Selection(6, 12, 6, 12));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(7), 'd', '015');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(7, 2, 7, 2).toString(), '016');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(7, 2, 7, 2).toString(), '016');
|
||||
|
||||
editor.setSelection(new Selection(8, 12, 8, 12));
|
||||
transposeAction.run(null, editor);
|
||||
transposeAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(8), 'hello world', '019');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(8, 12, 8, 12).toString(), '020');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(8, 12, 8, 12).toString(), '020');
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -526,40 +526,40 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
[
|
||||
'hello world',
|
||||
'öçşğü'
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let uppercaseAction = new UpperCaseAction();
|
||||
let lowercaseAction = new LowerCaseAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 12));
|
||||
uppercaseAction.run(null, editor);
|
||||
uppercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'HELLO WORLD', '001');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '002');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 12).toString(), '002');
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 12));
|
||||
lowercaseAction.run(null, editor);
|
||||
lowercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello world', '003');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '004');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 12).toString(), '004');
|
||||
|
||||
editor.setSelection(new Selection(1, 3, 1, 3));
|
||||
uppercaseAction.run(null, editor);
|
||||
uppercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'HELLO world', '005');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 3, 1, 3).toString(), '006');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 3, 1, 3).toString(), '006');
|
||||
|
||||
editor.setSelection(new Selection(1, 4, 1, 4));
|
||||
lowercaseAction.run(null, editor);
|
||||
lowercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), 'hello world', '007');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 4, 1, 4).toString(), '008');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 4, 1, 4).toString(), '008');
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 6));
|
||||
uppercaseAction.run(null, editor);
|
||||
uppercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), 'ÖÇŞĞÜ', '009');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '010');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 6).toString(), '010');
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 6));
|
||||
lowercaseAction.run(null, editor);
|
||||
lowercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), 'öçşğü', '011');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '012');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 6).toString(), '012');
|
||||
}
|
||||
);
|
||||
|
||||
@@ -567,51 +567,51 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
[
|
||||
'',
|
||||
' '
|
||||
], {}, (editor, cursor) => {
|
||||
let model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
let model = editor.getModel()!;
|
||||
let uppercaseAction = new UpperCaseAction();
|
||||
let lowercaseAction = new LowerCaseAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
uppercaseAction.run(null, editor);
|
||||
uppercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), '', '013');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '014');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 1).toString(), '014');
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
lowercaseAction.run(null, editor);
|
||||
lowercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), '', '015');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '016');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 1).toString(), '016');
|
||||
|
||||
editor.setSelection(new Selection(2, 2, 2, 2));
|
||||
uppercaseAction.run(null, editor);
|
||||
uppercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), ' ', '017');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '018');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '018');
|
||||
|
||||
editor.setSelection(new Selection(2, 2, 2, 2));
|
||||
lowercaseAction.run(null, editor);
|
||||
lowercaseAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(2), ' ', '019');
|
||||
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '020');
|
||||
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '020');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
suite('DeleteAllRightAction', () => {
|
||||
test('should be noop on empty', () => {
|
||||
withTestCodeEditor([''], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
withTestCodeEditor([''], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
|
||||
|
||||
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1)]);
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
|
||||
});
|
||||
@@ -621,22 +621,22 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
withTestCodeEditor([
|
||||
'hello',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 2, 1, 5));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['ho', 'world']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 2, 1, 2)]);
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 2, 4));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['ld']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 3));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
|
||||
});
|
||||
@@ -646,17 +646,17 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
withTestCodeEditor([
|
||||
'hello',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 3, 1, 3));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['he', 'world']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 3, 1, 3)]);
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 1));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['he', '']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(2, 1, 2, 1)]);
|
||||
});
|
||||
@@ -666,22 +666,22 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
withTestCodeEditor([
|
||||
'hello',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 6, 1, 6));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['helloworld']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
|
||||
|
||||
editor.setSelection(new Selection(1, 6, 1, 6));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['hello']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
|
||||
|
||||
editor.setSelection(new Selection(1, 6, 1, 6));
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['hello']);
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
|
||||
});
|
||||
@@ -692,8 +692,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'hello',
|
||||
'there',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
editor.setSelections([
|
||||
@@ -701,34 +701,34 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
new Selection(1, 6, 1, 6),
|
||||
new Selection(3, 4, 3, 4),
|
||||
]);
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3),
|
||||
new Selection(2, 4, 2, 4)
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['he', 'wor']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3),
|
||||
new Selection(2, 4, 2, 4)
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['hewor']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3),
|
||||
new Selection(1, 6, 1, 6)
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['he']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3)
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['he']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3)
|
||||
@@ -741,8 +741,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'hello',
|
||||
'there',
|
||||
'world'
|
||||
], {}, (editor, cursor) => {
|
||||
const model = editor.getModel();
|
||||
], {}, (editor) => {
|
||||
const model = editor.getModel()!;
|
||||
const action = new DeleteAllRightAction();
|
||||
|
||||
editor.setSelections([
|
||||
@@ -750,7 +750,7 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
new Selection(1, 6, 1, 6),
|
||||
new Selection(3, 4, 3, 4),
|
||||
]);
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 3, 1, 3),
|
||||
@@ -783,8 +783,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
editor.setPosition(new Position(lineNumber, column));
|
||||
let insertLineBeforeAction = new InsertLineBeforeAction();
|
||||
|
||||
insertLineBeforeAction.run(null, editor);
|
||||
callback(editor.getModel(), cursor);
|
||||
insertLineBeforeAction.run(null!, editor);
|
||||
callback(editor.getModel()!, cursor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -824,8 +824,8 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
editor.setPosition(new Position(lineNumber, column));
|
||||
let insertLineAfterAction = new InsertLineAfterAction();
|
||||
|
||||
insertLineAfterAction.run(null, editor);
|
||||
callback(editor.getModel(), cursor);
|
||||
insertLineAfterAction.run(null!, editor);
|
||||
callback(editor.getModel()!, cursor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -865,11 +865,11 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
}
|
||||
);
|
||||
|
||||
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
|
||||
withTestCodeEditor(null, { model: model }, (editor) => {
|
||||
let indentLinesAction = new IndentLinesAction();
|
||||
editor.setPosition(new Position(1, 2));
|
||||
|
||||
indentLinesAction.run(null, editor);
|
||||
indentLinesAction.run(null!, editor);
|
||||
assert.equal(model.getLineContent(1), '\tfunction baz() {');
|
||||
assert.deepEqual(editor.getSelection(), new Selection(1, 3, 1, 3));
|
||||
|
||||
@@ -887,14 +887,14 @@ suite('Editor Contrib - Line Operations', () => {
|
||||
'too',
|
||||
'c',
|
||||
];
|
||||
withTestCodeEditor(TEXT, {}, (editor, cursor) => {
|
||||
withTestCodeEditor(TEXT, {}, (editor) => {
|
||||
editor.setSelections([
|
||||
new Selection(2, 4, 2, 4),
|
||||
new Selection(2, 8, 2, 8),
|
||||
new Selection(3, 4, 3, 4),
|
||||
]);
|
||||
const deleteLinesAction = new DeleteLinesAction();
|
||||
deleteLinesAction.run(null, editor);
|
||||
deleteLinesAction.run(null!, editor);
|
||||
|
||||
assert.equal(editor.getValue(), 'a\nc');
|
||||
});
|
||||
|
||||
@@ -330,7 +330,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
|
||||
|
||||
test('move line should still work as before if there is no indentation rules', () => {
|
||||
testMoveLinesUpWithIndentCommand(
|
||||
null,
|
||||
null!,
|
||||
[
|
||||
'if (true) {',
|
||||
' var task = new Task(() => {',
|
||||
|
||||
@@ -37,7 +37,7 @@ export class Link implements ILink {
|
||||
return this._link.url;
|
||||
}
|
||||
|
||||
resolve(token: CancellationToken): Thenable<URI> {
|
||||
resolve(token: CancellationToken): Promise<URI> {
|
||||
if (this._link.url) {
|
||||
try {
|
||||
return Promise.resolve(URI.parse(this._link.url));
|
||||
|
||||
@@ -270,8 +270,8 @@ class LinkDetector implements editorCommon.IEditorContribution {
|
||||
let newDecorations: IModelDeltaDecoration[] = [];
|
||||
if (links) {
|
||||
// Not sure why this is sometimes null
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
newDecorations.push(LinkOccurrence.decoration(links[i], useMetaKey));
|
||||
for (const link of links) {
|
||||
newDecorations.push(LinkOccurrence.decoration(link, useMetaKey));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,8 +361,7 @@ class LinkDetector implements editorCommon.IEditorContribution {
|
||||
endColumn: position.column
|
||||
}, 0, true);
|
||||
|
||||
for (let i = 0; i < decorations.length; i++) {
|
||||
const decoration = decorations[i];
|
||||
for (const decoration of decorations) {
|
||||
const currentOccurrence = this.currentOccurrences[decoration.id];
|
||||
if (currentOccurrence) {
|
||||
return currentOccurrence;
|
||||
|
||||
@@ -77,7 +77,7 @@ export class MarkdownRenderer {
|
||||
};
|
||||
}
|
||||
|
||||
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
||||
render(markdown: IMarkdownString | undefined): IMarkdownRenderResult {
|
||||
let disposeables: IDisposable[] = [];
|
||||
|
||||
let element: HTMLElement;
|
||||
|
||||
@@ -54,6 +54,10 @@ export class InsertCursorAbove extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const useLogicalLine = (args && args.logicalLine === true);
|
||||
const cursors = editor._getCursors();
|
||||
const context = cursors.context;
|
||||
@@ -99,6 +103,10 @@ export class InsertCursorBelow extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const useLogicalLine = (args && args.logicalLine === true);
|
||||
const cursors = editor._getCursors();
|
||||
const context = cursors.context;
|
||||
@@ -154,6 +162,10 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
const selections = editor.getSelections();
|
||||
let newSelections: Selection[] = [];
|
||||
@@ -177,10 +189,14 @@ class InsertCursorAtEndOfLineSelected extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selections = editor.getSelections();
|
||||
const lineCount = editor.getModel().getLineCount();
|
||||
|
||||
let newSelections = [];
|
||||
let newSelections: Selection[] = [];
|
||||
for (let i = selections[0].startLineNumber; i <= lineCount; i++) {
|
||||
newSelections.push(new Selection(i, selections[0].startColumn, i, selections[0].endColumn));
|
||||
}
|
||||
@@ -203,9 +219,13 @@ class InsertCursorAtTopOfLineSelected extends EditorAction {
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selections = editor.getSelections();
|
||||
|
||||
let newSelections = [];
|
||||
let newSelections: Selection[] = [];
|
||||
for (let i = selections[0].startLineNumber; i >= 1; i--) {
|
||||
newSelections.push(new Selection(i, selections[0].startColumn, i, selections[0].endColumn));
|
||||
}
|
||||
@@ -226,7 +246,10 @@ export class MultiCursorSessionResult {
|
||||
|
||||
export class MultiCursorSession {
|
||||
|
||||
public static create(editor: ICodeEditor, findController: CommonFindController): MultiCursorSession {
|
||||
public static create(editor: ICodeEditor, findController: CommonFindController): MultiCursorSession | null {
|
||||
if (!editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
const findState = findController.getState();
|
||||
|
||||
// Find widget owns entirely what we search for if:
|
||||
@@ -281,10 +304,14 @@ export class MultiCursorSession {
|
||||
public readonly searchText: string,
|
||||
public readonly wholeWord: boolean,
|
||||
public readonly matchCase: boolean,
|
||||
public currentMatch: Selection
|
||||
public currentMatch: Selection | null
|
||||
) { }
|
||||
|
||||
public addSelectionToNextFindMatch(): MultiCursorSessionResult {
|
||||
public addSelectionToNextFindMatch(): MultiCursorSessionResult | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextMatch = this._getNextMatch();
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
@@ -294,7 +321,11 @@ export class MultiCursorSession {
|
||||
return new MultiCursorSessionResult(allSelections.concat(nextMatch), nextMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
public moveSelectionToNextFindMatch(): MultiCursorSessionResult {
|
||||
public moveSelectionToNextFindMatch(): MultiCursorSessionResult | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextMatch = this._getNextMatch();
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
@@ -304,7 +335,11 @@ export class MultiCursorSession {
|
||||
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(nextMatch), nextMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
private _getNextMatch(): Selection {
|
||||
private _getNextMatch(): Selection | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.currentMatch) {
|
||||
const result = this.currentMatch;
|
||||
this.currentMatch = null;
|
||||
@@ -323,7 +358,11 @@ export class MultiCursorSession {
|
||||
return new Selection(nextMatch.range.startLineNumber, nextMatch.range.startColumn, nextMatch.range.endLineNumber, nextMatch.range.endColumn);
|
||||
}
|
||||
|
||||
public addSelectionToPreviousFindMatch(): MultiCursorSessionResult {
|
||||
public addSelectionToPreviousFindMatch(): MultiCursorSessionResult | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousMatch = this._getPreviousMatch();
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
@@ -333,7 +372,11 @@ export class MultiCursorSession {
|
||||
return new MultiCursorSessionResult(allSelections.concat(previousMatch), previousMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
public moveSelectionToPreviousFindMatch(): MultiCursorSessionResult {
|
||||
public moveSelectionToPreviousFindMatch(): MultiCursorSessionResult | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousMatch = this._getPreviousMatch();
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
@@ -343,7 +386,11 @@ export class MultiCursorSession {
|
||||
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(previousMatch), previousMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
private _getPreviousMatch(): Selection {
|
||||
private _getPreviousMatch(): Selection | null {
|
||||
if (!this._editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.currentMatch) {
|
||||
const result = this.currentMatch;
|
||||
this.currentMatch = null;
|
||||
@@ -363,6 +410,10 @@ export class MultiCursorSession {
|
||||
}
|
||||
|
||||
public selectAll(): FindMatch[] {
|
||||
if (!this._editor.hasModel()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.findController.highlightFindOptions();
|
||||
|
||||
return this._editor.getModel().findMatches(this.searchText, true, false, this.matchCase, this.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
@@ -375,7 +426,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private _ignoreSelectionChange: boolean;
|
||||
private _session: MultiCursorSession;
|
||||
private _session: MultiCursorSession | null;
|
||||
private _sessionDispose: IDisposable[];
|
||||
|
||||
public static get(editor: ICodeEditor): MultiCursorSelectionController {
|
||||
@@ -466,7 +517,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
return new Selection(selection.startLineNumber, word.startColumn, selection.startLineNumber, word.endColumn);
|
||||
}
|
||||
|
||||
private _applySessionResult(result: MultiCursorSessionResult): void {
|
||||
private _applySessionResult(result: MultiCursorSessionResult | null): void {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@@ -476,11 +527,14 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
}
|
||||
}
|
||||
|
||||
public getSession(findController: CommonFindController): MultiCursorSession {
|
||||
public getSession(findController: CommonFindController): MultiCursorSession | null {
|
||||
return this._session;
|
||||
}
|
||||
|
||||
public addSelectionToNextFindMatch(findController: CommonFindController): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
if (!this._session) {
|
||||
// If there are multiple cursors, handle the case where they do not all select the same text.
|
||||
const allSelections = this._editor.getSelections();
|
||||
@@ -527,6 +581,10 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
}
|
||||
|
||||
public selectAll(findController: CommonFindController): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let matches: FindMatch[] | null = null;
|
||||
|
||||
const findState = findController.getState();
|
||||
@@ -578,7 +636,7 @@ export abstract class MultiCursorSelectionControllerAction extends EditorAction
|
||||
}
|
||||
const findController = CommonFindController.get(editor);
|
||||
if (!findController) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
this._run(multiCursorController, findController);
|
||||
}
|
||||
@@ -715,9 +773,9 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction {
|
||||
class SelectionHighlighterState {
|
||||
public readonly searchText: string;
|
||||
public readonly matchCase: boolean;
|
||||
public readonly wordSeparators: string;
|
||||
public readonly wordSeparators: string | null;
|
||||
|
||||
constructor(searchText: string, matchCase: boolean, wordSeparators: string) {
|
||||
constructor(searchText: string, matchCase: boolean, wordSeparators: string | null) {
|
||||
this.searchText = searchText;
|
||||
this.matchCase = matchCase;
|
||||
this.wordSeparators = wordSeparators;
|
||||
@@ -726,7 +784,7 @@ class SelectionHighlighterState {
|
||||
/**
|
||||
* Everything equals except for `lastWordUnderCursor`
|
||||
*/
|
||||
public static softEquals(a: SelectionHighlighterState, b: SelectionHighlighterState): boolean {
|
||||
public static softEquals(a: SelectionHighlighterState | null, b: SelectionHighlighterState | null): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
@@ -748,7 +806,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
|
||||
private _isEnabled: boolean;
|
||||
private decorations: string[];
|
||||
private updateSoon: RunOnceScheduler;
|
||||
private state: SelectionHighlighterState;
|
||||
private state: SelectionHighlighterState | null;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
@@ -800,12 +858,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
|
||||
this._setState(SelectionHighlighter._createState(this._isEnabled, this.editor));
|
||||
}
|
||||
|
||||
private static _createState(isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState {
|
||||
private static _createState(isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState | null {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
if (!editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
const s = editor.getSelection();
|
||||
@@ -877,7 +934,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
|
||||
return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null);
|
||||
}
|
||||
|
||||
private _setState(state: SelectionHighlighterState): void {
|
||||
private _setState(state: SelectionHighlighterState | null): void {
|
||||
if (SelectionHighlighterState.softEquals(this.state, state)) {
|
||||
this.state = state;
|
||||
return;
|
||||
@@ -889,6 +946,10 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (model.isTooLargeForTokenization()) {
|
||||
// the file is too large, so searching word under cursor in the whole document takes is blocking the UI.
|
||||
|
||||
@@ -24,7 +24,7 @@ suite('Multicursor', () => {
|
||||
let addCursorUpAction = new InsertCursorAbove();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 2, 1));
|
||||
addCursorUpAction.run(null, editor, {});
|
||||
addCursorUpAction.run(null!, editor, {});
|
||||
assert.equal(cursor.getSelections().length, 2);
|
||||
|
||||
editor.trigger('test', Handler.Paste, {
|
||||
@@ -35,8 +35,8 @@ suite('Multicursor', () => {
|
||||
]
|
||||
});
|
||||
// cursorCommand(cursor, H.Paste, { text: '1\n2' });
|
||||
assert.equal(editor.getModel().getLineContent(1), '1abc');
|
||||
assert.equal(editor.getModel().getLineContent(2), '2def');
|
||||
assert.equal(editor.getModel()!.getLineContent(1), '1abc');
|
||||
assert.equal(editor.getModel()!.getLineContent(2), '2def');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ suite('Multicursor', () => {
|
||||
'abc'
|
||||
], {}, (editor, cursor) => {
|
||||
let addCursorDownAction = new InsertCursorBelow();
|
||||
addCursorDownAction.run(null, editor, {});
|
||||
addCursorDownAction.run(null!, editor, {});
|
||||
assert.equal(cursor.getSelections().length, 1);
|
||||
});
|
||||
});
|
||||
@@ -65,9 +65,9 @@ suite('Multicursor selection', () => {
|
||||
onWillSaveState: Event.None,
|
||||
get: (key: string) => queryState[key],
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
getInteger: (key: string) => undefined,
|
||||
getInteger: (key: string) => undefined!,
|
||||
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
|
||||
remove: (key) => void 0
|
||||
remove: (key) => undefined
|
||||
} as IStorageService);
|
||||
|
||||
test('issue #8817: Cursor position changes when you cancel multicursor', () => {
|
||||
@@ -83,8 +83,8 @@ suite('Multicursor selection', () => {
|
||||
|
||||
editor.setSelection(new Selection(2, 9, 2, 16));
|
||||
|
||||
selectHighlightsAction.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
selectHighlightsAction.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[2, 9, 2, 16],
|
||||
[1, 9, 1, 16],
|
||||
[3, 9, 3, 16],
|
||||
@@ -92,7 +92,7 @@ suite('Multicursor selection', () => {
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 9, 2, 16]);
|
||||
assert.deepEqual(fromRange(editor.getSelection()!), [2, 9, 2, 16]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
@@ -114,8 +114,8 @@ suite('Multicursor selection', () => {
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
findController.getState().change({ searchString: 'some+thing', isRegex: true, isRevealed: true }, false);
|
||||
|
||||
selectHighlightsAction.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
selectHighlightsAction.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[1, 1, 1, 10],
|
||||
[2, 1, 2, 11],
|
||||
[3, 1, 3, 12],
|
||||
@@ -147,15 +147,15 @@ suite('Multicursor selection', () => {
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 3, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[2, 1, 3, 4],
|
||||
[8, 1, 9, 4]
|
||||
]);
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
|
||||
assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
@@ -175,16 +175,16 @@ suite('Multicursor selection', () => {
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[1, 1, 1, 4],
|
||||
[1, 4, 1, 7]
|
||||
]);
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[1, 1, 1, 4],
|
||||
[1, 4, 1, 7],
|
||||
[2, 1, 2, 4],
|
||||
@@ -193,7 +193,7 @@ suite('Multicursor selection', () => {
|
||||
]);
|
||||
|
||||
editor.trigger('test', Handler.Type, { text: 'z' });
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[1, 2, 1, 2],
|
||||
[1, 3, 1, 3],
|
||||
[2, 2, 2, 2],
|
||||
@@ -224,7 +224,7 @@ suite('Multicursor selection', () => {
|
||||
'rty'
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
editor.getModel().setEOL(EndOfLineSequence.CRLF);
|
||||
editor.getModel()!.setEOL(EndOfLineSequence.CRLF);
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
@@ -232,15 +232,15 @@ suite('Multicursor selection', () => {
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 3, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
addSelectionToNextFindMatch.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections()!.map(fromRange), [
|
||||
[2, 1, 3, 4],
|
||||
[8, 1, 9, 4]
|
||||
]);
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
|
||||
assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
@@ -277,25 +277,25 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
@@ -316,20 +316,20 @@ suite('Multicursor selection', () => {
|
||||
new Selection(2, 2, 2, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
@@ -350,20 +350,20 @@ suite('Multicursor selection', () => {
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
@@ -385,14 +385,14 @@ suite('Multicursor selection', () => {
|
||||
new Selection(3, 1, 3, 1),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
@@ -414,14 +414,14 @@ suite('Multicursor selection', () => {
|
||||
new Selection(3, 6, 3, 6),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 5, 1, 10),
|
||||
new Selection(2, 5, 2, 10),
|
||||
new Selection(3, 5, 3, 8),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 5, 1, 10),
|
||||
new Selection(2, 5, 2, 10),
|
||||
@@ -443,20 +443,20 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 1, 1, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
new Selection(3, 1, 3, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
@@ -464,7 +464,7 @@ suite('Multicursor selection', () => {
|
||||
new Selection(4, 1, 4, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
@@ -473,7 +473,7 @@ suite('Multicursor selection', () => {
|
||||
new Selection(5, 1, 5, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
@@ -501,18 +501,18 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
@@ -527,12 +527,12 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
@@ -543,7 +543,7 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
@@ -558,14 +558,14 @@ suite('Multicursor selection', () => {
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
new Selection(6, 2, 6, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
action.run(null!, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
|
||||
@@ -12,10 +12,11 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ParameterHintsWidget, TriggerContext } from './parameterHintsWidget';
|
||||
import { ParameterHintsWidget } from './parameterHintsWidget';
|
||||
import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
|
||||
|
||||
class ParameterHintsController implements IEditorContribution {
|
||||
|
||||
@@ -97,7 +98,7 @@ registerEditorCommand(new ParameterHintsCommand({
|
||||
handler: x => x.cancel(),
|
||||
kbOpts: {
|
||||
weight: weight,
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape]
|
||||
}
|
||||
@@ -108,7 +109,7 @@ registerEditorCommand(new ParameterHintsCommand({
|
||||
handler: x => x.previous(),
|
||||
kbOpts: {
|
||||
weight: weight,
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.UpArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.UpArrow],
|
||||
mac: { primary: KeyCode.UpArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow, KeyMod.WinCtrl | KeyCode.KEY_P] }
|
||||
@@ -120,7 +121,7 @@ registerEditorCommand(new ParameterHintsCommand({
|
||||
handler: x => x.next(),
|
||||
kbOpts: {
|
||||
weight: weight,
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.DownArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.DownArrow],
|
||||
mac: { primary: KeyCode.DownArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow, KeyMod.WinCtrl | KeyCode.KEY_N] }
|
||||
|
||||
269
src/vs/editor/contrib/parameterHints/parameterHintsModel.ts
Normal file
269
src/vs/editor/contrib/parameterHints/parameterHintsModel.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
|
||||
|
||||
export interface TriggerContext {
|
||||
readonly triggerKind: modes.SignatureHelpTriggerKind;
|
||||
readonly triggerCharacter?: string;
|
||||
}
|
||||
|
||||
namespace ParameterHintState {
|
||||
export const enum Type {
|
||||
Default,
|
||||
Active,
|
||||
Pending,
|
||||
}
|
||||
|
||||
export const Default = new class { readonly type = Type.Default; };
|
||||
export const Pending = new class { readonly type = Type.Pending; };
|
||||
|
||||
export class Active {
|
||||
readonly type = Type.Active;
|
||||
constructor(
|
||||
readonly hints: modes.SignatureHelp
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State = typeof Default | typeof Pending | Active;
|
||||
}
|
||||
|
||||
export class ParameterHintsModel extends Disposable {
|
||||
|
||||
private static readonly DEFAULT_DELAY = 120; // ms
|
||||
|
||||
private readonly _onChangedHints = this._register(new Emitter<modes.SignatureHelp | undefined>());
|
||||
public readonly onChangedHints = this._onChangedHints.event;
|
||||
|
||||
private editor: ICodeEditor;
|
||||
private enabled: boolean;
|
||||
private state: ParameterHintState.State = ParameterHintState.Default;
|
||||
private triggerChars = new CharacterSet();
|
||||
private retriggerChars = new CharacterSet();
|
||||
|
||||
private throttledDelayer: Delayer<boolean>;
|
||||
private provideSignatureHelpRequest?: CancelablePromise<any>;
|
||||
private triggerId = 0;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
delay: number = ParameterHintsModel.DEFAULT_DELAY
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editor = editor;
|
||||
this.enabled = false;
|
||||
|
||||
this.throttledDelayer = new Delayer(delay);
|
||||
|
||||
this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
|
||||
this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
|
||||
this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
|
||||
this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
|
||||
this._register(this.editor.onDidType(text => this.onDidType(text)));
|
||||
|
||||
this.onEditorConfigurationChange();
|
||||
this.onModelChanged();
|
||||
}
|
||||
|
||||
cancel(silent: boolean = false): void {
|
||||
this.state = ParameterHintState.Default;
|
||||
|
||||
this.throttledDelayer.cancel();
|
||||
|
||||
if (!silent) {
|
||||
this._onChangedHints.fire(undefined);
|
||||
}
|
||||
|
||||
if (this.provideSignatureHelpRequest) {
|
||||
this.provideSignatureHelpRequest.cancel();
|
||||
this.provideSignatureHelpRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
trigger(context: TriggerContext, delay?: number): void {
|
||||
const model = this.editor.getModel();
|
||||
if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const triggerId = ++this.triggerId;
|
||||
this.throttledDelayer.trigger(
|
||||
() => this.doTrigger({
|
||||
triggerKind: context.triggerKind,
|
||||
triggerCharacter: context.triggerCharacter,
|
||||
isRetrigger: this.state.type === ParameterHintState.Type.Active || this.state.type === ParameterHintState.Type.Pending,
|
||||
activeSignatureHelp: this.state.type === ParameterHintState.Type.Active ? this.state.hints : undefined
|
||||
}, triggerId), delay).then(undefined, onUnexpectedError);
|
||||
}
|
||||
|
||||
public next(): void {
|
||||
if (this.state.type !== ParameterHintState.Type.Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const length = this.state.hints.signatures.length;
|
||||
const activeSignature = this.state.hints.activeSignature;
|
||||
const last = (activeSignature % length) === (length - 1);
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
// If there is only one signature, or we're on last signature of list
|
||||
if ((length < 2 || last) && !cycle) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateActiveSignature(last && cycle ? 0 : activeSignature + 1);
|
||||
}
|
||||
|
||||
public previous(): void {
|
||||
if (this.state.type !== ParameterHintState.Type.Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const length = this.state.hints.signatures.length;
|
||||
const activeSignature = this.state.hints.activeSignature;
|
||||
const first = activeSignature === 0;
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
// If there is only one signature, or we're on first signature of list
|
||||
if ((length < 2 || first) && !cycle) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateActiveSignature(first && cycle ? length - 1 : activeSignature - 1);
|
||||
}
|
||||
|
||||
private updateActiveSignature(activeSignature: number) {
|
||||
if (this.state.type !== ParameterHintState.Type.Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = new ParameterHintState.Active({ ...this.state.hints, activeSignature });
|
||||
this._onChangedHints.fire(this.state.hints);
|
||||
}
|
||||
|
||||
private doTrigger(triggerContext: modes.SignatureHelpContext, triggerId: number): Promise<boolean> {
|
||||
this.cancel(true);
|
||||
|
||||
if (!this.editor.hasModel()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
const position = this.editor.getPosition();
|
||||
|
||||
this.state = ParameterHintState.Pending;
|
||||
|
||||
this.provideSignatureHelpRequest = createCancelablePromise(token =>
|
||||
provideSignatureHelp(model, position, triggerContext, token));
|
||||
|
||||
return this.provideSignatureHelpRequest.then(result => {
|
||||
// Check that we are still resolving the correct signature help
|
||||
if (triggerId !== this.triggerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result || !result.signatures || result.signatures.length === 0) {
|
||||
this.cancel();
|
||||
return false;
|
||||
} else {
|
||||
this.state = new ParameterHintState.Active(result);
|
||||
this._onChangedHints.fire(this.state.hints);
|
||||
return true;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.state = ParameterHintState.Default;
|
||||
onUnexpectedError(error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private get isTriggered(): boolean {
|
||||
return this.state.type === ParameterHintState.Type.Active
|
||||
|| this.state.type === ParameterHintState.Type.Pending
|
||||
|| this.throttledDelayer.isTriggered();
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
this.cancel();
|
||||
|
||||
// Update trigger characters
|
||||
this.triggerChars = new CharacterSet();
|
||||
this.retriggerChars = new CharacterSet();
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
|
||||
for (const ch of support.signatureHelpTriggerCharacters || []) {
|
||||
this.triggerChars.add(ch.charCodeAt(0));
|
||||
|
||||
// All trigger characters are also considered retrigger characters
|
||||
this.retriggerChars.add(ch.charCodeAt(0));
|
||||
}
|
||||
|
||||
for (const ch of support.signatureHelpRetriggerCharacters || []) {
|
||||
this.retriggerChars.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidType(text: string) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastCharIndex = text.length - 1;
|
||||
const triggerCharCode = text.charCodeAt(lastCharIndex);
|
||||
|
||||
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
|
||||
this.trigger({
|
||||
triggerKind: modes.SignatureHelpTriggerKind.TriggerCharacter,
|
||||
triggerCharacter: text.charAt(lastCharIndex),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onCursorChange(e: ICursorSelectionChangedEvent): void {
|
||||
if (e.source === 'mouse') {
|
||||
this.cancel();
|
||||
} else if (this.isTriggered) {
|
||||
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onModelContentChange(): void {
|
||||
if (this.isTriggered) {
|
||||
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorConfigurationChange(): void {
|
||||
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
|
||||
|
||||
if (!this.enabled) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel(true);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,232 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./parameterHints';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event, Emitter, chain } from 'vs/base/common/event';
|
||||
import { domEvent, stop } from 'vs/base/browser/event';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Context, provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./parameterHints';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export interface TriggerContext {
|
||||
readonly triggerKind: modes.SignatureHelpTriggerKind;
|
||||
readonly triggerCharacter?: string;
|
||||
}
|
||||
|
||||
export interface IHintEvent {
|
||||
hints: modes.SignatureHelp;
|
||||
}
|
||||
|
||||
export class ParameterHintsModel extends Disposable {
|
||||
|
||||
private static readonly DEFAULT_DELAY = 120; // ms
|
||||
|
||||
private readonly _onHint = this._register(new Emitter<IHintEvent>());
|
||||
public readonly onHint: Event<IHintEvent> = this._onHint.event;
|
||||
|
||||
private readonly _onCancel = this._register(new Emitter<void>());
|
||||
public readonly onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
private editor: ICodeEditor;
|
||||
private enabled: boolean;
|
||||
private triggerCharactersListeners: IDisposable[];
|
||||
private active: boolean = false;
|
||||
private pending: boolean = false;
|
||||
private triggerChars = new CharacterSet();
|
||||
private retriggerChars = new CharacterSet();
|
||||
|
||||
private throttledDelayer: Delayer<boolean>;
|
||||
private provideSignatureHelpRequest?: CancelablePromise<modes.SignatureHelp | null | undefined>;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
delay: number = ParameterHintsModel.DEFAULT_DELAY
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editor = editor;
|
||||
this.enabled = false;
|
||||
this.triggerCharactersListeners = [];
|
||||
|
||||
this.throttledDelayer = new Delayer(delay);
|
||||
|
||||
this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
|
||||
this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
|
||||
this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
|
||||
this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
|
||||
this._register(this.editor.onDidType(text => this.onDidType(text)));
|
||||
|
||||
this.onEditorConfigurationChange();
|
||||
this.onModelChanged();
|
||||
}
|
||||
|
||||
cancel(silent: boolean = false): void {
|
||||
this.active = false;
|
||||
this.pending = false;
|
||||
|
||||
this.throttledDelayer.cancel();
|
||||
|
||||
if (!silent) {
|
||||
this._onCancel.fire(void 0);
|
||||
}
|
||||
|
||||
if (this.provideSignatureHelpRequest) {
|
||||
this.provideSignatureHelpRequest.cancel();
|
||||
this.provideSignatureHelpRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
trigger(context: TriggerContext, delay?: number): void {
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.throttledDelayer.trigger(
|
||||
() => this.doTrigger({
|
||||
triggerKind: context.triggerKind,
|
||||
triggerCharacter: context.triggerCharacter,
|
||||
isRetrigger: this.isTriggered,
|
||||
}), delay).then(undefined, onUnexpectedError);
|
||||
}
|
||||
|
||||
private doTrigger(triggerContext: modes.SignatureHelpContext): Promise<boolean> {
|
||||
this.cancel(true);
|
||||
|
||||
if (!this.editor.hasModel()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
const position = this.editor.getPosition();
|
||||
|
||||
this.pending = true;
|
||||
|
||||
this.provideSignatureHelpRequest = createCancelablePromise(token =>
|
||||
provideSignatureHelp(model!, position!, triggerContext, token));
|
||||
|
||||
return this.provideSignatureHelpRequest.then(result => {
|
||||
this.pending = false;
|
||||
|
||||
if (!result || !result.signatures || result.signatures.length === 0) {
|
||||
this.cancel();
|
||||
this._onCancel.fire(void 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.active = true;
|
||||
const event: IHintEvent = { hints: result };
|
||||
this._onHint.fire(event);
|
||||
return true;
|
||||
|
||||
}).catch(error => {
|
||||
this.pending = false;
|
||||
onUnexpectedError(error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private get isTriggered(): boolean {
|
||||
return this.active || this.pending || this.throttledDelayer.isTriggered();
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
this.cancel();
|
||||
|
||||
// Update trigger characters
|
||||
this.triggerChars = new CharacterSet();
|
||||
this.retriggerChars = new CharacterSet();
|
||||
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
|
||||
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
|
||||
for (const ch of support.signatureHelpTriggerCharacters) {
|
||||
this.triggerChars.add(ch.charCodeAt(0));
|
||||
|
||||
// All trigger characters are also considered retrigger characters
|
||||
this.retriggerChars.add(ch.charCodeAt(0));
|
||||
|
||||
}
|
||||
}
|
||||
if (Array.isArray(support.signatureHelpRetriggerCharacters)) {
|
||||
for (const ch of support.signatureHelpRetriggerCharacters) {
|
||||
this.retriggerChars.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidType(text: string) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastCharIndex = text.length - 1;
|
||||
const triggerCharCode = text.charCodeAt(lastCharIndex);
|
||||
|
||||
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
|
||||
this.trigger({
|
||||
triggerKind: modes.SignatureHelpTriggerKind.TriggerCharacter,
|
||||
triggerCharacter: text.charAt(lastCharIndex),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onCursorChange(e: ICursorSelectionChangedEvent): void {
|
||||
if (e.source === 'mouse') {
|
||||
this.cancel();
|
||||
} else if (this.isTriggered) {
|
||||
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onModelContentChange(): void {
|
||||
if (this.isTriggered) {
|
||||
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorConfigurationChange(): void {
|
||||
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
|
||||
|
||||
if (!this.enabled) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.cancel(true);
|
||||
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
private static readonly ID = 'editor.widget.parameterHintsWidget';
|
||||
@@ -242,9 +38,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
private signature: HTMLElement;
|
||||
private docs: HTMLElement;
|
||||
private overloads: HTMLElement;
|
||||
private currentSignature: number;
|
||||
private visible: boolean;
|
||||
private hints: modes.SignatureHelp | null;
|
||||
private announcedLabel: string | null;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private disposables: IDisposable[];
|
||||
@@ -265,15 +59,13 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
this.visible = false;
|
||||
this.disposables = [];
|
||||
|
||||
this.disposables.push(this.model.onHint(e => {
|
||||
this.show();
|
||||
this.hints = e.hints;
|
||||
this.currentSignature = e.hints.activeSignature;
|
||||
this.render();
|
||||
}));
|
||||
|
||||
this.disposables.push(this.model.onCancel(() => {
|
||||
this.hide();
|
||||
this.disposables.push(this.model.onChangedHints(newParameterHints => {
|
||||
if (newParameterHints) {
|
||||
this.show();
|
||||
this.render(newParameterHints);
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -302,8 +94,6 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
this.docs = dom.append(body, $('.docs'));
|
||||
|
||||
this.currentSignature = 0;
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.hide();
|
||||
|
||||
@@ -321,7 +111,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
updateFont();
|
||||
|
||||
chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
|
||||
.filter(e => e.fontInfo)
|
||||
.on(updateFont, null, this.disposables);
|
||||
|
||||
@@ -355,7 +145,6 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
this.keyVisible.reset();
|
||||
this.visible = false;
|
||||
this.hints = null;
|
||||
this.announcedLabel = null;
|
||||
dom.removeClass(this.element, 'visible');
|
||||
this.editor.layoutContentWidget(this);
|
||||
@@ -371,19 +160,15 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (!this.hints) {
|
||||
return;
|
||||
}
|
||||
|
||||
const multiple = this.hints.signatures.length > 1;
|
||||
private render(hints: modes.SignatureHelp): void {
|
||||
const multiple = hints.signatures.length > 1;
|
||||
dom.toggleClass(this.element, 'multiple', multiple);
|
||||
this.keyMultipleSignatures.set(multiple);
|
||||
|
||||
this.signature.innerHTML = '';
|
||||
this.docs.innerHTML = '';
|
||||
|
||||
const signature = this.hints.signatures[this.currentSignature];
|
||||
const signature = hints.signatures[hints.activeSignature];
|
||||
|
||||
if (!signature) {
|
||||
return;
|
||||
@@ -401,13 +186,13 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
label.textContent = signature.label;
|
||||
|
||||
} else {
|
||||
this.renderParameters(code, signature, this.hints.activeParameter);
|
||||
this.renderParameters(code, signature, hints.activeParameter);
|
||||
}
|
||||
|
||||
dispose(this.renderDisposeables);
|
||||
this.renderDisposeables = [];
|
||||
|
||||
const activeParameter = signature.parameters[this.hints.activeParameter];
|
||||
const activeParameter = signature.parameters[hints.activeParameter];
|
||||
|
||||
if (activeParameter && activeParameter.documentation) {
|
||||
const documentation = $('span.documentation');
|
||||
@@ -434,16 +219,16 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
dom.append(this.docs, renderedContents.element);
|
||||
}
|
||||
|
||||
let currentOverload = String(this.currentSignature + 1);
|
||||
let currentOverload = String(hints.activeSignature + 1);
|
||||
|
||||
if (this.hints.signatures.length < 10) {
|
||||
currentOverload += `/${this.hints.signatures.length}`;
|
||||
if (hints.signatures.length < 10) {
|
||||
currentOverload += `/${hints.signatures.length}`;
|
||||
}
|
||||
|
||||
this.overloads.textContent = currentOverload;
|
||||
|
||||
if (activeParameter) {
|
||||
const labelToAnnounce = this.getParameterLabel(signature, this.hints.activeParameter);
|
||||
const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter);
|
||||
// Select method gets called on every user type while parameter hints are visible.
|
||||
// We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
|
||||
|
||||
@@ -459,16 +244,16 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
|
||||
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
|
||||
|
||||
let [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
|
||||
const [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
|
||||
|
||||
let beforeSpan = document.createElement('span');
|
||||
const beforeSpan = document.createElement('span');
|
||||
beforeSpan.textContent = signature.label.substring(0, start);
|
||||
|
||||
let paramSpan = document.createElement('span');
|
||||
const paramSpan = document.createElement('span');
|
||||
paramSpan.textContent = signature.label.substring(start, end);
|
||||
paramSpan.className = 'parameter active';
|
||||
|
||||
let afterSpan = document.createElement('span');
|
||||
const afterSpan = document.createElement('span');
|
||||
afterSpan.textContent = signature.label.substring(end);
|
||||
|
||||
dom.append(parent, beforeSpan, paramSpan, afterSpan);
|
||||
@@ -497,85 +282,18 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
// private select(position: number): void {
|
||||
// const signature = this.signatureViews[position];
|
||||
|
||||
// if (!signature) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.signatures.style.height = `${ signature.height }px`;
|
||||
// this.signatures.scrollTop = signature.top;
|
||||
|
||||
// let overloads = '' + (position + 1);
|
||||
|
||||
// if (this.signatureViews.length < 10) {
|
||||
// overloads += '/' + this.signatureViews.length;
|
||||
// }
|
||||
|
||||
// this.overloads.textContent = overloads;
|
||||
|
||||
// if (this.hints && this.hints.signatures[position].parameters[this.hints.activeParameter]) {
|
||||
// const labelToAnnounce = this.hints.signatures[position].parameters[this.hints.activeParameter].label;
|
||||
// // Select method gets called on every user type while parameter hints are visible.
|
||||
// // We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
|
||||
// if (this.announcedLabel !== labelToAnnounce) {
|
||||
// aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
|
||||
// this.announcedLabel = labelToAnnounce;
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.editor.layoutContentWidget(this);
|
||||
// }
|
||||
|
||||
next(): boolean {
|
||||
if (!this.hints) {
|
||||
return false;
|
||||
next(): void {
|
||||
if (this.model) {
|
||||
this.editor.focus();
|
||||
this.model.next();
|
||||
}
|
||||
|
||||
const length = this.hints.signatures.length;
|
||||
const last = (this.currentSignature % length) === (length - 1);
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
// If there is only one signature, or we're on last signature of list
|
||||
if ((length < 2 || last) && !cycle) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (last && cycle) {
|
||||
this.currentSignature = 0;
|
||||
} else {
|
||||
this.currentSignature++;
|
||||
}
|
||||
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
previous(): boolean {
|
||||
if (!this.hints) {
|
||||
return false;
|
||||
previous(): void {
|
||||
if (this.model) {
|
||||
this.editor.focus();
|
||||
this.model.previous();
|
||||
}
|
||||
|
||||
const length = this.hints.signatures.length;
|
||||
const first = this.currentSignature === 0;
|
||||
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
|
||||
|
||||
// If there is only one signature, or we're on first signature of list
|
||||
if ((length < 2 || first) && !cycle) {
|
||||
this.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first && cycle) {
|
||||
this.currentSignature = length - 1;
|
||||
} else {
|
||||
this.currentSignature--;
|
||||
}
|
||||
|
||||
this.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
@@ -617,11 +335,10 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const border = theme.getColor(editorHoverBorder);
|
||||
if (border) {
|
||||
let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
|
||||
const borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget { border: ${borderWidth}px solid ${border}; }`);
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid ${border.transparent(0.5)}; }`);
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`);
|
||||
|
||||
}
|
||||
const background = theme.getColor(editorHoverBackground);
|
||||
if (background) {
|
||||
|
||||
@@ -26,8 +26,9 @@ export function provideSignatureHelp(model: ITextModel, position: Position, cont
|
||||
}));
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position) =>
|
||||
registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position, args) =>
|
||||
provideSignatureHelp(model, position, {
|
||||
triggerKind: modes.SignatureHelpTriggerKind.Invoke,
|
||||
isRetrigger: false
|
||||
isRetrigger: false,
|
||||
triggerCharacter: args['triggerCharacter']
|
||||
}, CancellationToken.None));
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
|
||||
import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ParameterHintsModel } from '../parameterHintsWidget';
|
||||
import { ParameterHintsModel } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
|
||||
|
||||
const mockFile = URI.parse('test:somefile.ttt');
|
||||
const mockFileSelector = { scheme: 'test' };
|
||||
@@ -62,7 +62,7 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext) {
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
assert.strictEqual(context.triggerCharacter, triggerChar);
|
||||
done();
|
||||
@@ -84,18 +84,23 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
|
||||
++invokeCount;
|
||||
if (invokeCount === 1) {
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
assert.strictEqual(context.triggerCharacter, triggerChar);
|
||||
assert.strictEqual(context.isRetrigger, false);
|
||||
assert.strictEqual(context.activeSignatureHelp, undefined);
|
||||
|
||||
// Retrigger
|
||||
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
|
||||
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50);
|
||||
} else {
|
||||
assert.strictEqual(invokeCount, 2);
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
assert.ok(context.isRetrigger);
|
||||
assert.strictEqual(context.isRetrigger, true);
|
||||
assert.strictEqual(context.triggerCharacter, triggerChar);
|
||||
assert.strictEqual(context.activeSignatureHelp, emptySigHelpResult);
|
||||
|
||||
done();
|
||||
}
|
||||
return emptySigHelpResult;
|
||||
@@ -117,11 +122,13 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
|
||||
++invokeCount;
|
||||
if (invokeCount === 1) {
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
assert.strictEqual(context.triggerCharacter, triggerChar);
|
||||
assert.strictEqual(context.isRetrigger, false);
|
||||
assert.strictEqual(context.activeSignatureHelp, undefined);
|
||||
|
||||
// Cancel and retrigger
|
||||
hintModel.cancel();
|
||||
@@ -130,6 +137,8 @@ suite('ParameterHintsModel', () => {
|
||||
assert.strictEqual(invokeCount, 2);
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
assert.strictEqual(context.triggerCharacter, triggerChar);
|
||||
assert.strictEqual(context.isRetrigger, false);
|
||||
assert.strictEqual(context.activeSignatureHelp, undefined);
|
||||
done();
|
||||
}
|
||||
return emptySigHelpResult;
|
||||
@@ -148,7 +157,7 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = ['a', 'b', 'c'];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext) {
|
||||
++invokeCount;
|
||||
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
@@ -179,7 +188,7 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = ['a', 'b'];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
|
||||
++invokeCount;
|
||||
if (invokeCount === 1) {
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
@@ -214,7 +223,7 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Promise<modes.SignatureHelp> {
|
||||
const count = invokeCount++;
|
||||
token.onCancellationRequested(() => { didRequestCancellationOf = count; });
|
||||
|
||||
@@ -244,10 +253,10 @@ suite('ParameterHintsModel', () => {
|
||||
assert.strictEqual(-1, didRequestCancellationOf);
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
hintsModel.onHint(e => {
|
||||
hintsModel.onChangedHints(newParamterHints => {
|
||||
try {
|
||||
assert.strictEqual(0, didRequestCancellationOf);
|
||||
assert.strictEqual('1', e.hints.signatures[0].label);
|
||||
assert.strictEqual('1', newParamterHints!.signatures[0].label);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@@ -267,7 +276,7 @@ suite('ParameterHintsModel', () => {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [retriggerChar];
|
||||
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
|
||||
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
|
||||
++invokeCount;
|
||||
if (invokeCount === 1) {
|
||||
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
|
||||
@@ -294,4 +303,82 @@ suite('ParameterHintsModel', () => {
|
||||
// But a trigger character should
|
||||
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
|
||||
});
|
||||
|
||||
test('should use first result from multiple providers', async () => {
|
||||
const triggerChar = 'a';
|
||||
const firstProviderId = 'firstProvider';
|
||||
const secondProviderId = 'secondProvider';
|
||||
const paramterLabel = 'parameter';
|
||||
|
||||
const editor = createMockEditor('');
|
||||
const model = new ParameterHintsModel(editor, 5);
|
||||
disposables.push(model);
|
||||
|
||||
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
async provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): Promise<modes.SignatureHelp | undefined> {
|
||||
if (!context.isRetrigger) {
|
||||
// retrigger after delay for widget to show up
|
||||
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50);
|
||||
|
||||
return {
|
||||
activeParameter: 0,
|
||||
activeSignature: 0,
|
||||
signatures: [{
|
||||
label: firstProviderId,
|
||||
parameters: [
|
||||
{ label: paramterLabel }
|
||||
]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
|
||||
signatureHelpTriggerCharacters = [triggerChar];
|
||||
signatureHelpRetriggerCharacters = [];
|
||||
|
||||
async provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): Promise<modes.SignatureHelp | undefined> {
|
||||
if (context.isRetrigger) {
|
||||
return {
|
||||
activeParameter: 0,
|
||||
activeSignature: context.activeSignatureHelp ? context.activeSignatureHelp.activeSignature + 1 : 0,
|
||||
signatures: [{
|
||||
label: secondProviderId,
|
||||
parameters: context.activeSignatureHelp ? context.activeSignatureHelp.signatures[0].parameters : []
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
|
||||
|
||||
const firstHint = await getNextHint(model);
|
||||
assert.strictEqual(firstHint!.signatures[0].label, firstProviderId);
|
||||
assert.strictEqual(firstHint!.activeSignature, 0);
|
||||
assert.strictEqual(firstHint!.signatures[0].parameters[0].label, paramterLabel);
|
||||
|
||||
const secondHint = await getNextHint(model);
|
||||
assert.strictEqual(secondHint!.signatures[0].label, secondProviderId);
|
||||
assert.strictEqual(secondHint!.activeSignature, 1);
|
||||
assert.strictEqual(secondHint!.signatures[0].parameters[0].label, paramterLabel);
|
||||
});
|
||||
});
|
||||
|
||||
function getNextHint(model: ParameterHintsModel) {
|
||||
return new Promise<modes.SignatureHelp | undefined>(resolve => {
|
||||
const sub = model.onChangedHints(e => {
|
||||
sub.dispose();
|
||||
return resolve(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Thenable<DocumentSymbol[]> {
|
||||
export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
|
||||
|
||||
let roots: DocumentSymbol[] = [];
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export namespace PeekContext {
|
||||
export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated();
|
||||
}
|
||||
|
||||
export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor {
|
||||
export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null {
|
||||
let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
|
||||
if (editor instanceof EmbeddedCodeEditorWidget) {
|
||||
return editor.getParentEditor();
|
||||
@@ -34,13 +34,12 @@ export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor {
|
||||
}
|
||||
|
||||
export interface IPeekViewStyles extends IStyles {
|
||||
headerBackgroundColor?: Color;
|
||||
primaryHeadingColor?: Color;
|
||||
secondaryHeadingColor?: Color;
|
||||
headerBackgroundColor?: Color | null;
|
||||
primaryHeadingColor?: Color | null;
|
||||
secondaryHeadingColor?: Color | null;
|
||||
}
|
||||
|
||||
export interface IPeekViewOptions extends IOptions, IPeekViewStyles {
|
||||
}
|
||||
export type IPeekViewOptions = IOptions & IPeekViewStyles;
|
||||
|
||||
const defaultOptions: IPeekViewOptions = {
|
||||
headerBackgroundColor: Color.white,
|
||||
@@ -92,16 +91,16 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
protected _applyStyles(): void {
|
||||
super._applyStyles();
|
||||
let options = <IPeekViewOptions>this.options;
|
||||
if (this._headElement) {
|
||||
if (this._headElement && options.headerBackgroundColor) {
|
||||
this._headElement.style.backgroundColor = options.headerBackgroundColor.toString();
|
||||
}
|
||||
if (this._primaryHeading) {
|
||||
if (this._primaryHeading && options.primaryHeadingColor) {
|
||||
this._primaryHeading.style.color = options.primaryHeadingColor.toString();
|
||||
}
|
||||
if (this._secondaryHeading) {
|
||||
if (this._secondaryHeading && options.secondaryHeadingColor) {
|
||||
this._secondaryHeading.style.color = options.secondaryHeadingColor.toString();
|
||||
}
|
||||
if (this._bodyElement) {
|
||||
if (this._bodyElement && options.frameColor) {
|
||||
this._bodyElement.style.borderColor = options.frameColor.toString();
|
||||
}
|
||||
}
|
||||
@@ -138,7 +137,7 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
|
||||
this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'close-peekview-action', true, () => {
|
||||
this.dispose();
|
||||
return null;
|
||||
return Promise.resolve();
|
||||
}), { label: false, icon: true });
|
||||
}
|
||||
|
||||
@@ -186,11 +185,11 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
}
|
||||
|
||||
protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void {
|
||||
this._headElement.style.height = strings.format('{0}px', heightInPixel);
|
||||
this._headElement.style.height = `${heightInPixel}px`;
|
||||
this._headElement.style.lineHeight = this._headElement.style.height;
|
||||
}
|
||||
|
||||
protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
|
||||
this._bodyElement.style.height = strings.format('{0}px', heightInPixel);
|
||||
this._bodyElement.style.height = `${heightInPixel}px`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const defaultReferenceSearchOptions: RequestOptions = {
|
||||
getMetaTitle(model) {
|
||||
return model.references.length > 1 && nls.localize('meta.titleReference', " – {0} references", model.references.length);
|
||||
return model.references.length > 1 ? nls.localize('meta.titleReference', " – {0} references", model.references.length) : '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,15 +78,17 @@ export class ReferenceAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = ReferencesController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
let range = editor.getSelection();
|
||||
let model = editor.getModel();
|
||||
let references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references)));
|
||||
controller.toggleWidget(range, references, defaultReferenceSearchOptions);
|
||||
if (editor.hasModel()) {
|
||||
const range = editor.getSelection();
|
||||
const model = editor.getModel();
|
||||
const references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references)));
|
||||
controller.toggleWidget(range, references, defaultReferenceSearchOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour
|
||||
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => {
|
||||
if (!isCodeEditor(control)) {
|
||||
if (!isCodeEditor(control) || !control.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -139,10 +141,11 @@ let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Promise.resolve(controller.toggleWidget(
|
||||
return controller.toggleWidget(
|
||||
new Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
||||
createCancelablePromise(_ => Promise.resolve(new ReferencesModel(references))),
|
||||
defaultReferenceSearchOptions)).then(() => true);
|
||||
defaultReferenceSearchOptions
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSe
|
||||
|
||||
export interface RequestOptions {
|
||||
getMetaTitle(model: ReferencesModel): string;
|
||||
onGoto?: (reference: Location) => Thenable<any>;
|
||||
onGoto?: (reference: Location) => Promise<any>;
|
||||
}
|
||||
|
||||
export abstract class ReferencesController implements editorCommon.IEditorContribution {
|
||||
@@ -192,7 +192,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
this._requestIdPool += 1; // Cancel pending requests
|
||||
}
|
||||
|
||||
private _gotoReference(ref: Location): Thenable<any> {
|
||||
private _gotoReference(ref: Location): Promise<any> {
|
||||
this._widget.hide();
|
||||
|
||||
this._ignoreModelChangeEvent = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { Location } from 'vs/editor/common/modes';
|
||||
import { Location, LocationLink } from 'vs/editor/common/modes';
|
||||
import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
@@ -170,7 +170,7 @@ export class ReferencesModel implements IDisposable {
|
||||
readonly _onDidChangeReferenceRange = new Emitter<OneReference>();
|
||||
readonly onDidChangeReferenceRange: Event<OneReference> = this._onDidChangeReferenceRange.event;
|
||||
|
||||
constructor(references: Location[]) {
|
||||
constructor(references: LocationLink[]) {
|
||||
this._disposables = [];
|
||||
// grouping and sorting
|
||||
references.sort(ReferencesModel._compareReferences);
|
||||
@@ -187,7 +187,7 @@ export class ReferencesModel implements IDisposable {
|
||||
if (current.children.length === 0
|
||||
|| !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) {
|
||||
|
||||
let oneRef = new OneReference(current, ref.range);
|
||||
let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range);
|
||||
this._disposables.push(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e)));
|
||||
this.references.push(oneRef);
|
||||
current.children.push(oneRef);
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
|
||||
|
||||
import { ReferencesModel, FileReferences, OneReference } from './referencesModel';
|
||||
import { IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
@@ -21,24 +20,22 @@ import { escape } from 'vs/base/common/strings';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters';
|
||||
|
||||
//#region data source
|
||||
|
||||
export type TreeElement = FileReferences | OneReference;
|
||||
|
||||
export class DataSource implements IDataSource<TreeElement> {
|
||||
export class DataSource implements IAsyncDataSource<ReferencesModel | FileReferences, TreeElement> {
|
||||
|
||||
root: ReferencesModel | FileReferences;
|
||||
constructor(@ITextModelService private readonly _resolverService: ITextModelService) { }
|
||||
|
||||
constructor(
|
||||
@ITextModelService private readonly _resolverService: ITextModelService,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
hasChildren(element: TreeElement): boolean {
|
||||
if (!element) {
|
||||
hasChildren(element: ReferencesModel | FileReferences | TreeElement): boolean {
|
||||
if (element instanceof ReferencesModel) {
|
||||
return true;
|
||||
}
|
||||
if (element instanceof FileReferences && !element.failure) {
|
||||
@@ -47,10 +44,11 @@ export class DataSource implements IDataSource<TreeElement> {
|
||||
return false;
|
||||
}
|
||||
|
||||
getChildren(element: TreeElement): Thenable<TreeElement[]> {
|
||||
if (!element && this.root instanceof FileReferences) {
|
||||
element = this.root;
|
||||
getChildren(element: ReferencesModel | FileReferences | TreeElement): TreeElement[] | Promise<TreeElement[]> {
|
||||
if (element instanceof ReferencesModel) {
|
||||
return element.groups;
|
||||
}
|
||||
|
||||
if (element instanceof FileReferences) {
|
||||
return element.resolve(this._resolverService).then(val => {
|
||||
// if (element.failure) {
|
||||
@@ -61,9 +59,7 @@ export class DataSource implements IDataSource<TreeElement> {
|
||||
return val.children;
|
||||
});
|
||||
}
|
||||
if (this.root instanceof ReferencesModel) {
|
||||
return Promise.resolve(this.root.groups);
|
||||
}
|
||||
|
||||
throw new Error('bad tree');
|
||||
}
|
||||
}
|
||||
@@ -83,6 +79,28 @@ export class Delegate implements IListVirtualDelegate<TreeElement> {
|
||||
}
|
||||
}
|
||||
|
||||
export class StringRepresentationProvider implements IKeyboardNavigationLabelProvider<TreeElement> {
|
||||
|
||||
constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { }
|
||||
|
||||
getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } {
|
||||
// todo@joao `OneReference` elements are lazy and their "real" label
|
||||
// isn't known yet
|
||||
return basename(element.uri.path);
|
||||
}
|
||||
|
||||
mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
|
||||
return this._keybindingService.mightProducePrintableCharacter(event);
|
||||
}
|
||||
}
|
||||
|
||||
export class IdentityProvider implements IIdentityProvider<TreeElement> {
|
||||
|
||||
getId(element: TreeElement): { toString(): string; } {
|
||||
return element.id;
|
||||
}
|
||||
}
|
||||
|
||||
//#region render: File
|
||||
|
||||
class FileReferencesTemplate extends Disposable {
|
||||
@@ -98,7 +116,7 @@ class FileReferencesTemplate extends Disposable {
|
||||
super();
|
||||
const parent = document.createElement('div');
|
||||
dom.addClass(parent, 'reference-file');
|
||||
this.file = this._register(new IconLabel(parent));
|
||||
this.file = this._register(new IconLabel(parent, { supportHighlights: true }));
|
||||
|
||||
this.badge = new CountBadge(dom.append(parent, dom.$('.count')));
|
||||
this._register(attachBadgeStyler(this.badge, themeService));
|
||||
@@ -106,9 +124,9 @@ class FileReferencesTemplate extends Disposable {
|
||||
container.appendChild(parent);
|
||||
}
|
||||
|
||||
set(element: FileReferences) {
|
||||
set(element: FileReferences, matches: IMatch[]) {
|
||||
let parent = dirname(element.uri);
|
||||
this.file.setValue(getBaseLabel(element.uri), parent ? this._uriLabel.getUriLabel(parent, { relative: true }) : undefined, { title: this._uriLabel.getUriLabel(element.uri) });
|
||||
this.file.setLabel(getBaseLabel(element.uri), parent ? this._uriLabel.getUriLabel(parent, { relative: true }) : undefined, { title: this._uriLabel.getUriLabel(element.uri), matches });
|
||||
const len = element.children.length;
|
||||
this.badge.setCount(len);
|
||||
if (element.failure) {
|
||||
@@ -121,7 +139,7 @@ class FileReferencesTemplate extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class FileReferencesRenderer implements ITreeRenderer<FileReferences, void, FileReferencesTemplate> {
|
||||
export class FileReferencesRenderer implements ITreeRenderer<FileReferences, FuzzyScore, FileReferencesTemplate> {
|
||||
|
||||
static readonly id = 'FileReferencesRenderer';
|
||||
|
||||
@@ -132,11 +150,8 @@ export class FileReferencesRenderer implements ITreeRenderer<FileReferences, voi
|
||||
renderTemplate(container: HTMLElement): FileReferencesTemplate {
|
||||
return this._instantiationService.createInstance(FileReferencesTemplate, container);
|
||||
}
|
||||
renderElement(node: ITreeNode<FileReferences, void>, index: number, template: FileReferencesTemplate): void {
|
||||
template.set(node.element);
|
||||
}
|
||||
disposeElement(element: ITreeNode<FileReferences, void>, index: number, templateData: FileReferencesTemplate): void {
|
||||
//
|
||||
renderElement(node: ITreeNode<FileReferences, FuzzyScore>, index: number, template: FileReferencesTemplate): void {
|
||||
template.set(node.element, createMatches(node.filterData));
|
||||
}
|
||||
disposeTemplate(templateData: FileReferencesTemplate): void {
|
||||
templateData.dispose();
|
||||
@@ -177,7 +192,7 @@ class OneReferenceTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
export class OneReferenceRenderer implements ITreeRenderer<OneReference, void, OneReferenceTemplate> {
|
||||
export class OneReferenceRenderer implements ITreeRenderer<OneReference, FuzzyScore, OneReferenceTemplate> {
|
||||
|
||||
static readonly id = 'OneReferenceRenderer';
|
||||
|
||||
@@ -186,12 +201,9 @@ export class OneReferenceRenderer implements ITreeRenderer<OneReference, void, O
|
||||
renderTemplate(container: HTMLElement): OneReferenceTemplate {
|
||||
return new OneReferenceTemplate(container);
|
||||
}
|
||||
renderElement(element: ITreeNode<OneReference, void>, index: number, templateData: OneReferenceTemplate): void {
|
||||
renderElement(element: ITreeNode<OneReference, FuzzyScore>, index: number, templateData: OneReferenceTemplate): void {
|
||||
templateData.set(element.element);
|
||||
}
|
||||
disposeElement(): void {
|
||||
//
|
||||
}
|
||||
disposeTemplate(): void {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/
|
||||
import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Location } from 'vs/editor/common/modes';
|
||||
import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement } from 'vs/editor/contrib/referenceSearch/referencesTree';
|
||||
import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/referenceSearch/referencesTree';
|
||||
import * as nls from 'vs/nls';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -31,6 +31,10 @@ import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform
|
||||
import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { PeekViewWidget } from './peekViewWidget';
|
||||
import { FileReferences, OneReference, ReferencesModel } from './referencesModel';
|
||||
import { ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
|
||||
class DecorationsManager implements IDisposable {
|
||||
|
||||
@@ -69,6 +73,9 @@ class DecorationsManager implements IDisposable {
|
||||
}
|
||||
|
||||
private _addDecorations(reference: FileReferences): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged()));
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [];
|
||||
@@ -95,8 +102,13 @@ class DecorationsManager implements IDisposable {
|
||||
private _onDecorationChanged(): void {
|
||||
const toRemove: string[] = [];
|
||||
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._decorations.forEach((reference, decorationId) => {
|
||||
const newRange = this._editor.getModel().getDecorationRange(decorationId);
|
||||
const newRange = model.getDecorationRange(decorationId);
|
||||
|
||||
if (!newRange) {
|
||||
return;
|
||||
@@ -217,7 +229,7 @@ export interface LayoutData {
|
||||
export interface SelectionEvent {
|
||||
kind: 'goto' | 'show' | 'side' | 'open';
|
||||
source: 'editor' | 'tree' | 'title';
|
||||
element: Location;
|
||||
element?: Location;
|
||||
}
|
||||
|
||||
export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey<boolean>('referenceSearchTreeFocused', true);
|
||||
@@ -227,15 +239,14 @@ export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey<boolean>('r
|
||||
*/
|
||||
export class ReferenceWidget extends PeekViewWidget {
|
||||
|
||||
private _model: ReferencesModel;
|
||||
private _model: ReferencesModel | undefined;
|
||||
private _decorationsManager: DecorationsManager;
|
||||
|
||||
private _disposeOnNewModel: IDisposable[] = [];
|
||||
private _callOnDispose: IDisposable[] = [];
|
||||
private _onDidSelectReference = new Emitter<SelectionEvent>();
|
||||
|
||||
private _treeDataSource: DataSource;
|
||||
private _tree: WorkbenchAsyncDataTree<TreeElement>;
|
||||
private _tree: WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>;
|
||||
private _treeContainer: HTMLElement;
|
||||
private _sash: VSash;
|
||||
private _preview: ICodeEditor;
|
||||
@@ -243,6 +254,8 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
private _previewNotAvailableMessage: TextModel;
|
||||
private _previewContainer: HTMLElement;
|
||||
private _messageContainer: HTMLElement;
|
||||
private height: number | undefined;
|
||||
private width: number | undefined;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@@ -272,7 +285,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.setModel(null);
|
||||
this.setModel(undefined);
|
||||
this._callOnDispose = dispose(this._callOnDispose);
|
||||
dispose<IDisposable>(this._preview, this._previewNotAvailableMessage, this._tree, this._sash, this._previewModelReference);
|
||||
super.dispose();
|
||||
@@ -330,38 +343,42 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
this._previewNotAvailableMessage = TextModel.createFromString(nls.localize('missingPreviewMessage', "no preview available"));
|
||||
|
||||
// sash
|
||||
this._sash = new VSash(containerElement, this.layoutData.ratio || .8);
|
||||
this._sash = new VSash(containerElement, this.layoutData.ratio || 0.8);
|
||||
this._sash.onDidChangePercentages(() => {
|
||||
let [left, right] = this._sash.percentages;
|
||||
this._previewContainer.style.width = left;
|
||||
this._treeContainer.style.width = right;
|
||||
this._preview.layout();
|
||||
this._tree.layout();
|
||||
this._tree.layout(this.height, this.width && this.width * (1 - this._sash.ratio));
|
||||
this.layoutData.ratio = this._sash.ratio;
|
||||
});
|
||||
|
||||
// tree
|
||||
this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline'));
|
||||
|
||||
const renderer = [
|
||||
const renderers = [
|
||||
this._instantiationService.createInstance(FileReferencesRenderer),
|
||||
this._instantiationService.createInstance(OneReferenceRenderer),
|
||||
];
|
||||
|
||||
const treeOptions = {
|
||||
const treeOptions: IAsyncDataTreeOptions<TreeElement, FuzzyScore> = {
|
||||
ariaLabel: nls.localize('treeAriaLabel', "References"),
|
||||
keyboardSupport: this._defaultTreeKeyboardSupport,
|
||||
accessibilityProvider: new AriaProvider()
|
||||
accessibilityProvider: new AriaProvider(),
|
||||
keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider),
|
||||
identityProvider: new IdentityProvider()
|
||||
};
|
||||
|
||||
this._treeDataSource = this._instantiationService.createInstance(DataSource);
|
||||
const treeDataSource = this._instantiationService.createInstance(DataSource);
|
||||
|
||||
this._tree = this._instantiationService.createInstance(
|
||||
WorkbenchAsyncDataTree, this._treeContainer, new Delegate(),
|
||||
renderer as any,
|
||||
this._treeDataSource,
|
||||
this._tree = this._instantiationService.createInstance<HTMLElement, IListVirtualDelegate<TreeElement>, ITreeRenderer<any, FuzzyScore, any>[], IAsyncDataSource<ReferencesModel | FileReferences, TreeElement>, IAsyncDataTreeOptions<TreeElement, FuzzyScore>, WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>>(
|
||||
WorkbenchAsyncDataTree,
|
||||
this._treeContainer,
|
||||
new Delegate(),
|
||||
renderers,
|
||||
treeDataSource,
|
||||
treeOptions
|
||||
) as any as WorkbenchAsyncDataTree<TreeElement>;
|
||||
);
|
||||
|
||||
ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService);
|
||||
|
||||
@@ -385,7 +402,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
goto = true;
|
||||
|
||||
} else if (e.browserEvent instanceof MouseEvent) {
|
||||
aside = e.browserEvent.metaKey || e.browserEvent.metaKey || e.browserEvent.altKey;
|
||||
aside = e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey;
|
||||
goto = e.browserEvent.detail === 2;
|
||||
}
|
||||
if (aside) {
|
||||
@@ -403,6 +420,9 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
|
||||
super._doLayoutBody(heightInPixel, widthInPixel);
|
||||
|
||||
this.height = heightInPixel;
|
||||
this.width = widthInPixel;
|
||||
|
||||
const height = heightInPixel + 'px';
|
||||
this._sash.height = heightInPixel;
|
||||
this._sash.width = widthInPixel;
|
||||
@@ -414,12 +434,12 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
this._treeContainer.style.height = height;
|
||||
this._treeContainer.style.width = right;
|
||||
// forward
|
||||
this._tree.layout(heightInPixel);
|
||||
this._tree.layout(heightInPixel, widthInPixel * (1 - this._sash.ratio));
|
||||
this._preview.layout();
|
||||
|
||||
// store layout data
|
||||
this.layoutData = {
|
||||
heightInLines: this._viewZone.heightInLines,
|
||||
heightInLines: this._viewZone ? this._viewZone.heightInLines : 0,
|
||||
ratio: this._sash.ratio
|
||||
};
|
||||
}
|
||||
@@ -441,7 +461,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
});
|
||||
}
|
||||
|
||||
public setModel(newModel: ReferencesModel): Thenable<any> {
|
||||
public setModel(newModel: ReferencesModel | undefined): Promise<any> | undefined {
|
||||
// clean up
|
||||
this._disposeOnNewModel = dispose(this._disposeOnNewModel);
|
||||
this._model = newModel;
|
||||
@@ -451,13 +471,16 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _onNewModel(): Thenable<any> {
|
||||
private _onNewModel(): Promise<any> {
|
||||
if (!this._model) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (this._model.empty) {
|
||||
this.setTitle('');
|
||||
this._messageContainer.innerHTML = nls.localize('noResults', "No results");
|
||||
dom.show(this._messageContainer);
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
dom.hide(this._messageContainer);
|
||||
@@ -478,7 +501,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
return;
|
||||
}
|
||||
this._onDidSelectReference.fire({
|
||||
element: { uri: element.uri, range: target.range },
|
||||
element: { uri: element.uri, range: target.range! },
|
||||
kind: (event.ctrlKey || event.metaKey || event.altKey) ? 'side' : 'open',
|
||||
source: 'editor'
|
||||
});
|
||||
@@ -493,11 +516,10 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
this.focus();
|
||||
|
||||
// pick input and a reference to begin with
|
||||
this._treeDataSource.root = this._model.groups.length === 1 ? this._model.groups[0] : this._model;
|
||||
return this._tree.refresh(null);
|
||||
return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model);
|
||||
}
|
||||
|
||||
private _getFocusedReference(): OneReference {
|
||||
private _getFocusedReference(): OneReference | undefined {
|
||||
const [element] = this._tree.getFocus();
|
||||
if (element instanceof OneReference) {
|
||||
return element;
|
||||
@@ -521,14 +543,14 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
|
||||
// Update widget header
|
||||
if (reference.uri.scheme !== Schemas.inMemory) {
|
||||
this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri)));
|
||||
this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri)!));
|
||||
} else {
|
||||
this.setTitle(nls.localize('peekView.alternateTitle', "References"));
|
||||
}
|
||||
|
||||
const promise = this._textModelResolverService.createModelReference(reference.uri);
|
||||
|
||||
if (this._treeDataSource.root === reference.parent) {
|
||||
if (this._tree.getInput() === reference.parent) {
|
||||
this._tree.reveal(reference);
|
||||
} else {
|
||||
if (revealParent) {
|
||||
|
||||
@@ -23,16 +23,16 @@ suite('references', function () {
|
||||
}]);
|
||||
|
||||
let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1));
|
||||
assert.equal(ref.uri.path, '/src/can');
|
||||
assert.equal(ref!.uri.path, '/src/can');
|
||||
|
||||
ref = model.nearestReference(URI.file('/src/someOtherFileInSrc'), new Position(1, 1));
|
||||
assert.equal(ref.uri.path, '/src/can');
|
||||
assert.equal(ref!.uri.path, '/src/can');
|
||||
|
||||
ref = model.nearestReference(URI.file('/out/someOtherFile'), new Position(1, 1));
|
||||
assert.equal(ref.uri.path, '/out/obj/can');
|
||||
assert.equal(ref!.uri.path, '/out/obj/can');
|
||||
|
||||
ref = model.nearestReference(URI.file('/out/obj/can2222'), new Position(1, 1));
|
||||
assert.equal(ref.uri.path, '/out/obj/can2');
|
||||
assert.equal(ref!.uri.path, '/out/obj/can2');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
import * as nls from 'vs/nls';
|
||||
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import RenameInputField from './renameInputField';
|
||||
import { RenameInputField, CONTEXT_RENAME_INPUT_VISIBLE } from './renameInputField';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation, Rejection } from 'vs/editor/common/modes';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
@@ -27,35 +27,39 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
|
||||
class RenameSkeleton {
|
||||
|
||||
private _provider: RenameProvider[];
|
||||
private readonly _providers: RenameProvider[];
|
||||
|
||||
constructor(
|
||||
readonly model: ITextModel,
|
||||
readonly position: Position
|
||||
private readonly model: ITextModel,
|
||||
private readonly position: Position
|
||||
) {
|
||||
this._provider = RenameProviderRegistry.ordered(model);
|
||||
this._providers = RenameProviderRegistry.ordered(model);
|
||||
}
|
||||
|
||||
hasProvider() {
|
||||
return this._provider.length > 0;
|
||||
return this._providers.length > 0;
|
||||
}
|
||||
|
||||
async resolveRenameLocation(token: CancellationToken): Promise<RenameLocation & Rejection | null | undefined> {
|
||||
const firstProvider = this._providers[0];
|
||||
if (!firstProvider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let [provider] = this._provider;
|
||||
let res: RenameLocation & Rejection | null | undefined;
|
||||
|
||||
if (provider.resolveRenameLocation) {
|
||||
res = await provider.resolveRenameLocation(this.model, this.position, token);
|
||||
if (firstProvider.resolveRenameLocation) {
|
||||
res = await firstProvider.resolveRenameLocation(this.model, this.position, token);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
let word = this.model.getWordAtPosition(this.position);
|
||||
const word = this.model.getWordAtPosition(this.position);
|
||||
if (word) {
|
||||
res = {
|
||||
return {
|
||||
range: new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn),
|
||||
text: word.word
|
||||
};
|
||||
@@ -65,17 +69,16 @@ class RenameSkeleton {
|
||||
return res;
|
||||
}
|
||||
|
||||
async provideRenameEdits(newName: string, i: number = 0, rejects: string[] = [], token: CancellationToken): Promise<WorkspaceEdit & Rejection> {
|
||||
|
||||
if (i >= this._provider.length) {
|
||||
async provideRenameEdits(newName: string, i: number, rejects: string[], token: CancellationToken): Promise<WorkspaceEdit & Rejection> {
|
||||
const provider = this._providers[i];
|
||||
if (!provider) {
|
||||
return {
|
||||
edits: undefined,
|
||||
edits: [],
|
||||
rejectReason: rejects.join('\n')
|
||||
};
|
||||
}
|
||||
|
||||
let provider = this._provider[i];
|
||||
let result = await provider.provideRenameEdits(this.model, this.position, newName, token);
|
||||
const result = await provider.provideRenameEdits(this.model, this.position, newName, token);
|
||||
if (!result) {
|
||||
return this.provideRenameEdits(newName, i + 1, rejects.concat(nls.localize('no result', "No result.")), token);
|
||||
} else if (result.rejectReason) {
|
||||
@@ -86,15 +89,13 @@ class RenameSkeleton {
|
||||
}
|
||||
|
||||
export async function rename(model: ITextModel, position: Position, newName: string): Promise<WorkspaceEdit & Rejection> {
|
||||
return new RenameSkeleton(model, position).provideRenameEdits(newName, undefined, undefined, CancellationToken.None);
|
||||
return new RenameSkeleton(model, position).provideRenameEdits(newName, 0, [], CancellationToken.None);
|
||||
}
|
||||
|
||||
// --- register actions and commands
|
||||
|
||||
const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export class RenameController implements IEditorContribution {
|
||||
export class RenameController extends Disposable implements IEditorContribution {
|
||||
|
||||
private static readonly ID = 'editor.contrib.renameController';
|
||||
|
||||
@@ -102,31 +103,53 @@ export class RenameController implements IEditorContribution {
|
||||
return editor.getContribution<RenameController>(RenameController.ID);
|
||||
}
|
||||
|
||||
private _renameInputField: RenameInputField;
|
||||
private _renameInputVisible: IContextKey<boolean>;
|
||||
private _renameInputField?: RenameInputField;
|
||||
private _renameOperationIdPool = 1;
|
||||
|
||||
private _activeRename?: {
|
||||
readonly id: number;
|
||||
readonly operation: CancelablePromise<void>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
private readonly editor: ICodeEditor,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@IProgressService private readonly _progressService: IProgressService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
) {
|
||||
this._renameInputField = new RenameInputField(editor, themeService);
|
||||
this._renameInputVisible = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
|
||||
super();
|
||||
this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeModelLanguage(() => this.onModelChanged()));
|
||||
this._register(this.editor.onDidChangeCursorSelection(() => this.onModelChanged()));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._renameInputField.dispose();
|
||||
private get renameInputField(): RenameInputField {
|
||||
if (!this._renameInputField) {
|
||||
this._renameInputField = this._register(new RenameInputField(this.editor, this._themeService, this._contextKeyService));
|
||||
}
|
||||
return this._renameInputField;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return RenameController.ID;
|
||||
}
|
||||
|
||||
async run(token: CancellationToken): Promise<void> {
|
||||
async run(): Promise<void> {
|
||||
if (this._activeRename) {
|
||||
this._activeRename.operation.cancel();
|
||||
}
|
||||
|
||||
const id = this._renameOperationIdPool++;
|
||||
this._activeRename = {
|
||||
id,
|
||||
operation: createCancelablePromise(token => this.doRename(token, id))
|
||||
};
|
||||
return this._activeRename.operation;
|
||||
}
|
||||
|
||||
private async doRename(token: CancellationToken, id: number): Promise<void> {
|
||||
if (!this.editor.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -140,7 +163,9 @@ export class RenameController implements IEditorContribution {
|
||||
|
||||
let loc: RenameLocation & Rejection | null | undefined;
|
||||
try {
|
||||
loc = await skeleton.resolveRenameLocation(token);
|
||||
const resolveLocationOperation = skeleton.resolveRenameLocation(token);
|
||||
this._progressService.showWhile(resolveLocationOperation, 250);
|
||||
loc = await resolveLocationOperation;
|
||||
} catch (e) {
|
||||
MessageController.get(this.editor).showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position);
|
||||
return undefined;
|
||||
@@ -155,6 +180,10 @@ export class RenameController implements IEditorContribution {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._activeRename || this._activeRename.id !== id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let selection = this.editor.getSelection();
|
||||
let selectionStart = 0;
|
||||
let selectionEnd = loc.text.length;
|
||||
@@ -164,9 +193,7 @@ export class RenameController implements IEditorContribution {
|
||||
selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
|
||||
}
|
||||
|
||||
this._renameInputVisible.set(true);
|
||||
return this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => {
|
||||
this._renameInputVisible.reset();
|
||||
return this.renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => {
|
||||
|
||||
if (typeof newNameOrFocusFlag === 'boolean') {
|
||||
if (newNameOrFocusFlag) {
|
||||
@@ -209,18 +236,26 @@ export class RenameController implements IEditorContribution {
|
||||
this._progressService.showWhile(renameOperation, 250);
|
||||
return renameOperation;
|
||||
|
||||
}, err => {
|
||||
this._renameInputVisible.reset();
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
public acceptRenameInput(): void {
|
||||
this._renameInputField.acceptInput();
|
||||
if (this._renameInputField) {
|
||||
this._renameInputField.acceptInput();
|
||||
}
|
||||
}
|
||||
|
||||
public cancelRenameInput(): void {
|
||||
this._renameInputField.cancelInput(true);
|
||||
if (this._renameInputField) {
|
||||
this._renameInputField.cancelInput(true);
|
||||
}
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
if (this._activeRename) {
|
||||
this._activeRename.operation.cancel();
|
||||
this._activeRename = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +281,7 @@ export class RenameAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Thenable<void> {
|
||||
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise<void> {
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const [uri, pos] = args || [undefined, undefined];
|
||||
|
||||
@@ -267,9 +302,9 @@ export class RenameAction extends EditorAction {
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
let controller = RenameController.get(editor);
|
||||
const controller = RenameController.get(editor);
|
||||
if (controller) {
|
||||
return Promise.resolve(controller.run(CancellationToken.None));
|
||||
return controller.run();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -3,29 +3,39 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./renameInputField';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export default class RenameInputField implements IContentWidget, IDisposable {
|
||||
export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
|
||||
|
||||
export class RenameInputField implements IContentWidget, IDisposable {
|
||||
|
||||
private _editor: ICodeEditor;
|
||||
private _position: Position;
|
||||
private _domNode: HTMLElement;
|
||||
private _inputField: HTMLInputElement;
|
||||
private _visible: boolean;
|
||||
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
// Editor.IContentWidget.allowEditorOverflow
|
||||
public allowEditorOverflow: boolean = true;
|
||||
|
||||
constructor(editor: ICodeEditor, @IThemeService private themeService: IThemeService) {
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private readonly themeService: IThemeService,
|
||||
contextKeyService: IContextKeyService,
|
||||
) {
|
||||
this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
|
||||
|
||||
this._editor = editor;
|
||||
this._editor.addContentWidget(this);
|
||||
|
||||
@@ -128,10 +138,8 @@ export default class RenameInputField implements IContentWidget, IDisposable {
|
||||
this._inputField.setAttribute('selectionEnd', selectionEnd.toString());
|
||||
this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20);
|
||||
|
||||
let disposeOnDone: IDisposable[] = [],
|
||||
always: Function;
|
||||
|
||||
always = () => {
|
||||
const disposeOnDone: IDisposable[] = [];
|
||||
const always = () => {
|
||||
dispose(disposeOnDone);
|
||||
this._hide();
|
||||
};
|
||||
@@ -181,6 +189,7 @@ export default class RenameInputField implements IContentWidget, IDisposable {
|
||||
private _show(): void {
|
||||
this._editor.revealLineInCenterIfOutsideViewport(this._position.lineNumber, ScrollType.Smooth);
|
||||
this._visible = true;
|
||||
this._visibleContextKey.set(true);
|
||||
this._editor.layoutContentWidget(this);
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -193,6 +202,7 @@ export default class RenameInputField implements IContentWidget, IDisposable {
|
||||
|
||||
private _hide(): void {
|
||||
this._visible = false;
|
||||
this._visibleContextKey.reset();
|
||||
this._editor.layoutContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,47 +3,146 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
import { SelectionRangeProvider, SelectionRange } from 'vs/editor/common/modes';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
|
||||
export class BracketSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Promise<SelectionRange[]> {
|
||||
const bucket: SelectionRange[] = [];
|
||||
const ranges = new Map<string, LinkedList<Range>>();
|
||||
return new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges))
|
||||
.then(() => new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket)))
|
||||
.then(() => bucket);
|
||||
}
|
||||
|
||||
let result: Range[] = [];
|
||||
let last: Range | undefined;
|
||||
let pos = position;
|
||||
let i = 0;
|
||||
for (; i < 1750; i++) {
|
||||
private static readonly _maxDuration = 30;
|
||||
private static readonly _maxRounds = 2;
|
||||
|
||||
private static _bracketsRightYield(resolve: () => void, round: number, model: ITextModel, pos: Position, ranges: Map<string, LinkedList<Range>>): void {
|
||||
const counts = new Map<string, number>();
|
||||
const t1 = Date.now();
|
||||
while (true) {
|
||||
if (round >= BracketSelectionRangeProvider._maxRounds) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
if (!pos) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
let bracket = model.findNextBracket(pos);
|
||||
if (!bracket) {
|
||||
// no more brackets
|
||||
resolve();
|
||||
break;
|
||||
} else if (bracket.isOpen) {
|
||||
// skip past the closing bracket
|
||||
let matching = model.matchBracket(bracket.range.getEndPosition());
|
||||
if (!matching) {
|
||||
break;
|
||||
}
|
||||
pos = model.getPositionAt(model.getOffsetAt(matching[1].getEndPosition()) + 1);
|
||||
|
||||
}
|
||||
let d = Date.now() - t1;
|
||||
if (d > BracketSelectionRangeProvider._maxDuration) {
|
||||
setTimeout(() => BracketSelectionRangeProvider._bracketsRightYield(resolve, round + 1, model, pos, ranges));
|
||||
break;
|
||||
}
|
||||
const key = bracket.close;
|
||||
if (bracket.isOpen) {
|
||||
// wait for closing
|
||||
let val = counts.has(key) ? counts.get(key)! : 0;
|
||||
counts.set(key, val + 1);
|
||||
} else {
|
||||
// find matching, opening bracket
|
||||
let range = model.findMatchingBracketUp(bracket.close, bracket.range.getStartPosition());
|
||||
if (!range) {
|
||||
break;
|
||||
// process closing
|
||||
let val = counts.has(key) ? counts.get(key)! : 0;
|
||||
val -= 1;
|
||||
counts.set(key, Math.max(0, val));
|
||||
if (val < 0) {
|
||||
let list = ranges.get(key);
|
||||
if (!list) {
|
||||
list = new LinkedList();
|
||||
ranges.set(key, list);
|
||||
}
|
||||
list.push(bracket.range);
|
||||
}
|
||||
if (!last || range.getStartPosition().isBefore(last.getStartPosition())) {
|
||||
const inner = Range.fromPositions(range.getStartPosition(), bracket.range.getEndPosition());
|
||||
const outer = Range.fromPositions(range.getEndPosition(), bracket.range.getStartPosition());
|
||||
result.push(inner, outer);
|
||||
last = outer;
|
||||
}
|
||||
pos = bracket.range.getEndPosition();
|
||||
}
|
||||
}
|
||||
|
||||
private static _bracketsLeftYield(resolve: () => void, round: number, model: ITextModel, pos: Position, ranges: Map<string, LinkedList<Range>>, bucket: SelectionRange[]): void {
|
||||
const counts = new Map<string, number>();
|
||||
const t1 = Date.now();
|
||||
while (true) {
|
||||
if (round >= BracketSelectionRangeProvider._maxRounds && ranges.size === 0) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
if (!pos) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
let bracket = model.findPrevBracket(pos);
|
||||
if (!bracket) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
let d = Date.now() - t1;
|
||||
if (d > BracketSelectionRangeProvider._maxDuration) {
|
||||
setTimeout(() => BracketSelectionRangeProvider._bracketsLeftYield(resolve, round + 1, model, pos, ranges, bucket));
|
||||
break;
|
||||
}
|
||||
const key = bracket.close;
|
||||
if (!bracket.isOpen) {
|
||||
// wait for opening
|
||||
let val = counts.has(key) ? counts.get(key)! : 0;
|
||||
counts.set(key, val + 1);
|
||||
} else {
|
||||
// opening
|
||||
let val = counts.has(key) ? counts.get(key)! : 0;
|
||||
val -= 1;
|
||||
counts.set(key, Math.max(0, val));
|
||||
if (val < 0) {
|
||||
let list = ranges.get(key);
|
||||
if (list) {
|
||||
let closing = list.shift();
|
||||
if (list.size === 0) {
|
||||
ranges.delete(key);
|
||||
}
|
||||
const innerBracket = Range.fromPositions(bracket.range.getEndPosition(), closing!.getStartPosition());
|
||||
const outerBracket = Range.fromPositions(bracket.range.getStartPosition(), closing!.getEndPosition());
|
||||
bucket.push({ range: innerBracket, kind: 'statement.brackets' });
|
||||
bucket.push({ range: outerBracket, kind: 'statement.brackets.full' });
|
||||
BracketSelectionRangeProvider._addBracketLeading(model, outerBracket, bucket);
|
||||
}
|
||||
}
|
||||
pos = model.getPositionAt(model.getOffsetAt(bracket.range.getEndPosition()) + 1);
|
||||
}
|
||||
pos = bracket.range.getStartPosition();
|
||||
}
|
||||
}
|
||||
|
||||
private static _addBracketLeading(model: ITextModel, bracket: Range, bucket: SelectionRange[]): void {
|
||||
if (bracket.startLineNumber === bracket.endLineNumber) {
|
||||
return;
|
||||
}
|
||||
// xxxxxxxx {
|
||||
//
|
||||
// }
|
||||
const startLine = bracket.startLineNumber;
|
||||
const column = model.getLineFirstNonWhitespaceColumn(startLine);
|
||||
if (column !== 0 && column !== bracket.startColumn) {
|
||||
bucket.push({ range: Range.fromPositions(new Position(startLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' });
|
||||
bucket.push({ range: Range.fromPositions(new Position(startLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' });
|
||||
}
|
||||
|
||||
// xxxxxxxx
|
||||
// {
|
||||
//
|
||||
// }
|
||||
const aboveLine = startLine - 1;
|
||||
if (aboveLine > 0) {
|
||||
const column = model.getLineFirstNonWhitespaceColumn(aboveLine);
|
||||
if (column === bracket.startColumn && column !== model.getLineLastNonWhitespaceColumn(aboveLine)) {
|
||||
bucket.push({ range: Range.fromPositions(new Position(aboveLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' });
|
||||
bucket.push({ range: Range.fromPositions(new Position(aboveLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import * as nls from 'vs/nls';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
|
||||
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
|
||||
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
class SelectionRanges {
|
||||
|
||||
@@ -82,10 +82,10 @@ class SmartSelectController implements IEditorContribution {
|
||||
}
|
||||
|
||||
|
||||
let promise: Promise<void> = Promise.resolve(void 0);
|
||||
let promise: Promise<void> = Promise.resolve(undefined);
|
||||
|
||||
if (!this._state) {
|
||||
promise = provideSelectionRanges(model, selection.getStartPosition(), CancellationToken.None).then(ranges => {
|
||||
promise = provideSelectionRanges(model, selection.getPosition(), CancellationToken.None).then(ranges => {
|
||||
if (!arrays.isNonEmptyArray(ranges)) {
|
||||
// invalid result
|
||||
return;
|
||||
@@ -154,9 +154,9 @@ abstract class AbstractSmartSelect extends EditorAction {
|
||||
class GrowSelectionAction extends AbstractSmartSelect {
|
||||
constructor() {
|
||||
super(true, {
|
||||
id: 'editor.action.smartSelect.grow',
|
||||
label: nls.localize('smartSelect.grow', "Expand Select"),
|
||||
alias: 'Expand Select',
|
||||
id: 'editor.action.smartSelect.expand',
|
||||
label: nls.localize('smartSelect.expand', "Expand Selection"),
|
||||
alias: 'Expand Selection',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
@@ -174,12 +174,15 @@ class GrowSelectionAction extends AbstractSmartSelect {
|
||||
}
|
||||
}
|
||||
|
||||
// renamed command id
|
||||
CommandsRegistry.registerCommandAlias('editor.action.smartSelect.grow', 'editor.action.smartSelect.expand');
|
||||
|
||||
class ShrinkSelectionAction extends AbstractSmartSelect {
|
||||
constructor() {
|
||||
super(false, {
|
||||
id: 'editor.action.smartSelect.shrink',
|
||||
label: nls.localize('smartSelect.shrink', "Shrink Select"),
|
||||
alias: 'Shrink Select',
|
||||
label: nls.localize('smartSelect.shrink', "Shrink Selection"),
|
||||
alias: 'Shrink Selection',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
@@ -201,14 +204,18 @@ registerEditorContribution(SmartSelectController);
|
||||
registerEditorAction(GrowSelectionAction);
|
||||
registerEditorAction(ShrinkSelectionAction);
|
||||
|
||||
// word selection
|
||||
modes.SelectionRangeRegistry.register('*', new WordSelectionRangeProvider());
|
||||
modes.SelectionRangeRegistry.register('*', new BracketSelectionRangeProvider());
|
||||
modes.SelectionRangeRegistry.register('*', new TokenTreeSelectionRangeProvider());
|
||||
|
||||
export function provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<Range[] | undefined | null> {
|
||||
|
||||
const provider = modes.SelectionRangeRegistry.orderedGroups(model);
|
||||
|
||||
if (provider.length === 1) {
|
||||
// add word selection and bracket selection when no provider exists
|
||||
provider.unshift([new BracketSelectionRangeProvider()]);
|
||||
}
|
||||
|
||||
interface RankedRange {
|
||||
rank: number;
|
||||
range: Range;
|
||||
@@ -221,11 +228,11 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
|
||||
for (const group of provider) {
|
||||
rank += 1;
|
||||
for (const prov of group) {
|
||||
work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(res => {
|
||||
if (arrays.isNonEmptyArray(res)) {
|
||||
for (const range of res) {
|
||||
if (Range.isIRange(range) && Range.containsPosition(range, position)) {
|
||||
ranges.push({ range: Range.lift(range), rank });
|
||||
work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(selectionRanges => {
|
||||
if (arrays.isNonEmptyArray(selectionRanges)) {
|
||||
for (const sel of selectionRanges) {
|
||||
if (Range.isIRange(sel.range) && Range.containsPosition(sel.range, position)) {
|
||||
ranges.push({ range: Range.lift(sel.range), rank });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,6 +241,11 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
|
||||
}
|
||||
|
||||
return Promise.all(work).then(() => {
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
ranges.sort((a, b) => {
|
||||
if (Position.isBefore(a.range.getStartPosition(), b.range.getStartPosition())) {
|
||||
return 1;
|
||||
@@ -248,16 +260,35 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
|
||||
}
|
||||
});
|
||||
|
||||
// ranges.sort((a, b) => Range.compareRangesUsingStarts(b.range, a.range));
|
||||
let result: Range[] = [];
|
||||
let last: Range | undefined;
|
||||
for (const { range } of ranges) {
|
||||
if (!last || Range.containsRange(range, last)) {
|
||||
if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) {
|
||||
result.push(range);
|
||||
last = range;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
let result2: Range[] = [result[0]];
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
const prev = result[i - 1];
|
||||
const cur = result[i];
|
||||
if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) {
|
||||
// add line/block range without leading/failing whitespace
|
||||
const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber));
|
||||
if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) {
|
||||
result2.push(rangeNoWhitespace);
|
||||
}
|
||||
// add line/block range
|
||||
const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber));
|
||||
if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) {
|
||||
result2.push(rangeFull);
|
||||
}
|
||||
}
|
||||
result2.push(cur);
|
||||
}
|
||||
|
||||
return result2;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
321
src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts
Normal file
321
src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LanguageIdentifier, SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
|
||||
import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
|
||||
|
||||
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
}
|
||||
|
||||
getEOL(resource: URI | undefined): string {
|
||||
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
|
||||
if (filesConfiguration && filesConfiguration.eol) {
|
||||
if (filesConfiguration.eol !== 'auto') {
|
||||
return filesConfiguration.eol;
|
||||
}
|
||||
}
|
||||
return (isLinux || isMacintosh) ? '\n' : '\r\n';
|
||||
}
|
||||
}
|
||||
|
||||
class MockJSMode extends MockMode {
|
||||
|
||||
private static readonly _id = new LanguageIdentifier('mockJSMode', 3);
|
||||
|
||||
constructor() {
|
||||
super(MockJSMode._id);
|
||||
|
||||
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
|
||||
brackets: [
|
||||
['(', ')'],
|
||||
['{', '}'],
|
||||
['[', ']']
|
||||
],
|
||||
|
||||
onEnterRules: javascriptOnEnterRules,
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\;\:\'\"\,\.\<\>\/\?\s]+)/g
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
suite('SmartSelect', () => {
|
||||
|
||||
let modelService: ModelServiceImpl;
|
||||
let mode: MockJSMode;
|
||||
|
||||
setup(() => {
|
||||
const configurationService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService));
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
modelService.dispose();
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): Promise<void> {
|
||||
let uri = URI.file('test.js');
|
||||
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
|
||||
let actual = await provideSelectionRanges(model, new Position(lineNumber, column), CancellationToken.None);
|
||||
let actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
|
||||
let desiredStr = ranges.reverse().map(r => String(r));
|
||||
|
||||
assert.deepEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`);
|
||||
modelService.destroyModel(uri);
|
||||
}
|
||||
|
||||
test('getRangesToPosition #1', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'\t\treturn (bar + (2 * foo))',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 20, [
|
||||
new Range(1, 1, 5, 2), // all
|
||||
new Range(1, 21, 5, 2), // {} outside
|
||||
new Range(1, 22, 5, 1), // {} inside
|
||||
new Range(2, 1, 4, 3), // block
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 2, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(2, 12, 4, 2),
|
||||
new Range(3, 1, 3, 27), // line w/ triva
|
||||
new Range(3, 3, 3, 27), // line w/o triva
|
||||
new Range(3, 10, 3, 27), // () outside
|
||||
new Range(3, 11, 3, 26), // () inside
|
||||
new Range(3, 17, 3, 26), // () outside
|
||||
new Range(3, 18, 3, 25), // () inside
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(1, 22, 5, 1),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 2, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(2, 12, 4, 2),
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
' ',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2), // all
|
||||
new Range(1, 21, 5, 2), // {} outside
|
||||
new Range(1, 22, 5, 1), // {} inside
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 2, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(2, 12, 4, 2),
|
||||
new Range(3, 1, 3, 2), // block
|
||||
new Range(3, 1, 3, 2) // empty line
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [ ]',
|
||||
' { } ',
|
||||
'( ) '
|
||||
], 2, 3, [
|
||||
new Range(1, 1, 3, 5),
|
||||
new Range(2, 1, 2, 6), // line w/ triava
|
||||
new Range(2, 2, 2, 5), // {} inside, line w/o triva
|
||||
new Range(2, 3, 2, 4) // {} inside
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
' ( ) '
|
||||
], 1, 3, [
|
||||
new Range(1, 1, 3, 7), // all
|
||||
new Range(1, 1, 1, 5), // line w/ trival
|
||||
new Range(1, 2, 1, 4), // [] outside, line w/o trival
|
||||
new Range(1, 3, 1, 3), // [] inside
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
'selectthis( ) '
|
||||
], 3, 11, [
|
||||
new Range(1, 1, 3, 15), // all
|
||||
new Range(3, 1, 3, 15), // line w/ trivia
|
||||
new Range(3, 1, 3, 14), // line w/o trivia
|
||||
new Range(3, 1, 3, 11) // word
|
||||
]);
|
||||
});
|
||||
|
||||
// -- bracket selections
|
||||
|
||||
async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise<void> {
|
||||
|
||||
let model = modelService.createModel(value, new StaticLanguageSelector(mode.getLanguageIdentifier()), URI.parse('fake:lang'));
|
||||
let pos = model.getPositionAt(value.indexOf('|'));
|
||||
let ranges = await provider.provideSelectionRanges(model, pos, CancellationToken.None);
|
||||
modelService.destroyModel(model.uri);
|
||||
|
||||
assert.equal(expected.length, ranges!.length);
|
||||
for (const range of ranges!) {
|
||||
let exp = expected.shift() || null;
|
||||
assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`);
|
||||
}
|
||||
}
|
||||
|
||||
test('bracket selection', async () => {
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '(|)',
|
||||
new Range(1, 2, 1, 3), new Range(1, 1, 1, 4)
|
||||
);
|
||||
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]',
|
||||
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8), // ()
|
||||
new Range(1, 3, 1, 8), new Range(1, 2, 1, 9), // [[]()]
|
||||
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10), // [[[]()]]
|
||||
);
|
||||
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]',
|
||||
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8),
|
||||
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10),
|
||||
);
|
||||
|
||||
// no bracket
|
||||
await assertRanges(new BracketSelectionRangeProvider(), 'fofof|fofo');
|
||||
|
||||
// empty
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]]|');
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]');
|
||||
|
||||
// edge
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
|
||||
|
||||
await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 16), new Range(1, 12, 1, 17));
|
||||
await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 17), new Range(1, 13, 1, 18), new Range(1, 2, 1, 26), new Range(1, 1, 1, 27));
|
||||
});
|
||||
|
||||
test('bracket with leading/trailing', async () => {
|
||||
|
||||
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}',
|
||||
new Range(2, 7, 2, 8), new Range(2, 6, 2, 9),
|
||||
new Range(1, 13, 3, 1), new Range(1, 12, 3, 2),
|
||||
new Range(1, 1, 3, 2), new Range(1, 1, 3, 2),
|
||||
);
|
||||
|
||||
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}',
|
||||
new Range(3, 7, 3, 8), new Range(3, 6, 3, 9),
|
||||
new Range(2, 2, 4, 1), new Range(2, 1, 4, 2),
|
||||
new Range(1, 1, 4, 2), new Range(1, 1, 4, 2),
|
||||
);
|
||||
});
|
||||
|
||||
test('in-word ranges', async () => {
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar',
|
||||
new Range(1, 1, 1, 5), // foo
|
||||
new Range(1, 1, 1, 8), // fooBar
|
||||
new Range(1, 1, 1, 8), // doc
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba',
|
||||
new Range(1, 1, 1, 5),
|
||||
new Range(1, 1, 1, 8),
|
||||
new Range(1, 1, 1, 8),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba',
|
||||
new Range(1, 1, 1, 5),
|
||||
new Range(1, 1, 1, 8),
|
||||
new Range(1, 1, 1, 8),
|
||||
);
|
||||
});
|
||||
|
||||
test('Default selection should select current word/hump first in camelCase #67493', async function () {
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect',
|
||||
new Range(1, 1, 1, 10),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect',
|
||||
new Range(1, 9, 1, 15),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect',
|
||||
new Range(1, 9, 1, 15),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect',
|
||||
new Range(1, 9, 1, 15),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect',
|
||||
new Range(1, 9, 1, 15),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
|
||||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect',
|
||||
new Range(1, 9, 1, 15),
|
||||
new Range(1, 1, 1, 21),
|
||||
new Range(1, 1, 1, 21),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,188 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
|
||||
import { MarkerService } from 'vs/platform/markers/common/markerService';
|
||||
|
||||
class MockJSMode extends MockMode {
|
||||
|
||||
private static readonly _id = new LanguageIdentifier('mockJSMode', 3);
|
||||
|
||||
constructor() {
|
||||
super(MockJSMode._id);
|
||||
|
||||
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
|
||||
brackets: [
|
||||
['(', ')'],
|
||||
['{', '}'],
|
||||
['[', ']']
|
||||
],
|
||||
|
||||
onEnterRules: javascriptOnEnterRules
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
suite('TokenSelectionSupport', () => {
|
||||
|
||||
let modelService: ModelServiceImpl;
|
||||
let mode: MockJSMode;
|
||||
|
||||
setup(() => {
|
||||
const configurationService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(new MarkerService(), configurationService, new TestTextResourcePropertiesService(configurationService));
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
modelService.dispose();
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): void {
|
||||
let uri = URI.file('test.js');
|
||||
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
|
||||
|
||||
let actual = new TokenTreeSelectionRangeProvider().provideSelectionRanges(model, new Position(lineNumber, column));
|
||||
|
||||
|
||||
let actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
|
||||
let desiredStr = ranges.reverse().map(r => String(r));
|
||||
|
||||
assert.deepEqual(actualStr, desiredStr);
|
||||
|
||||
modelService.destroyModel(uri);
|
||||
}
|
||||
|
||||
test('getRangesToPosition #1', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'\t\treturn (bar + (2 * foo))',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 20, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(3, 1, 4, 2),
|
||||
new Range(3, 1, 3, 27),
|
||||
new Range(3, 10, 3, 27),
|
||||
new Range(3, 11, 3, 26),
|
||||
new Range(3, 17, 3, 26),
|
||||
new Range(3, 18, 3, 25),
|
||||
// new Range(3, 19, 3, 20)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
'',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 11, 4, 3)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
'function a(bar, foo){',
|
||||
'\tif (bar) {',
|
||||
' ',
|
||||
'\t}',
|
||||
'}'
|
||||
], 3, 1, [
|
||||
new Range(1, 1, 5, 2),
|
||||
new Range(1, 21, 5, 2),
|
||||
new Range(2, 1, 4, 3),
|
||||
new Range(2, 11, 4, 3),
|
||||
new Range(3, 1, 4, 2),
|
||||
new Range(3, 1, 3, 2)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [ ]',
|
||||
' { } ',
|
||||
'( ) '
|
||||
], 2, 3, [
|
||||
new Range(1, 1, 3, 5),
|
||||
new Range(2, 1, 2, 6),
|
||||
new Range(2, 2, 2, 5),
|
||||
new Range(2, 3, 2, 4)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
' ( ) '
|
||||
], 1, 3, [
|
||||
new Range(1, 1, 3, 7),
|
||||
new Range(1, 1, 1, 5),
|
||||
new Range(1, 2, 1, 4)
|
||||
]);
|
||||
});
|
||||
|
||||
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
|
||||
|
||||
return assertGetRangesToPosition([
|
||||
' [] ',
|
||||
' { } ',
|
||||
'selectthis( ) '
|
||||
], 3, 11, [
|
||||
new Range(1, 1, 3, 15),
|
||||
new Range(3, 1, 3, 15),
|
||||
new Range(3, 1, 3, 11)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
) {
|
||||
}
|
||||
|
||||
getEOL(resource: URI): string {
|
||||
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
|
||||
if (filesConfiguration && filesConfiguration.eol) {
|
||||
if (filesConfiguration.eol !== 'auto') {
|
||||
return filesConfiguration.eol;
|
||||
}
|
||||
}
|
||||
return (isLinux || isMacintosh) ? '\n' : '\r\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,452 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports';
|
||||
import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { LanguageId, StandardTokenType, SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
|
||||
export class TokenTreeSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
let tree = build(model);
|
||||
let node = find(tree, position);
|
||||
let ranges: Range[] = [];
|
||||
let lastRange: Range | undefined;
|
||||
while (node) {
|
||||
if (!lastRange || !Range.equalsRange(lastRange, node.range)) {
|
||||
ranges.push(node.range);
|
||||
}
|
||||
lastRange = node.range;
|
||||
node = node.parent;
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum TokenTreeBracket {
|
||||
None = 0,
|
||||
Open = 1,
|
||||
Close = -1
|
||||
}
|
||||
|
||||
export class Node {
|
||||
|
||||
start: Position;
|
||||
|
||||
end: Position;
|
||||
|
||||
get range(): Range {
|
||||
return new Range(
|
||||
this.start.lineNumber,
|
||||
this.start.column,
|
||||
this.end.lineNumber,
|
||||
this.end.column
|
||||
);
|
||||
}
|
||||
|
||||
parent: Node;
|
||||
}
|
||||
|
||||
export class NodeList extends Node {
|
||||
|
||||
children: Node[];
|
||||
|
||||
get start(): Position {
|
||||
return this.hasChildren
|
||||
? this.children[0].start
|
||||
: this.parent.start;
|
||||
}
|
||||
|
||||
get end(): Position {
|
||||
return this.hasChildren
|
||||
? this.children[this.children.length - 1].end
|
||||
: this.parent.end;
|
||||
}
|
||||
|
||||
get hasChildren() {
|
||||
return this.children && this.children.length > 0;
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return !this.hasChildren && !this.parent;
|
||||
}
|
||||
|
||||
public append(node: Node | null): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
node.parent = this;
|
||||
if (!this.children) {
|
||||
this.children = [];
|
||||
}
|
||||
if (node instanceof NodeList) {
|
||||
if (node.children) {
|
||||
this.children.push.apply(this.children, node.children);
|
||||
}
|
||||
} else {
|
||||
this.children.push(node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Block extends Node {
|
||||
|
||||
open: Node;
|
||||
close: Node;
|
||||
elements: NodeList;
|
||||
|
||||
get start(): Position {
|
||||
return this.open.start;
|
||||
}
|
||||
|
||||
get end(): Position {
|
||||
return this.close.end;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.elements = new NodeList();
|
||||
this.elements.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
class Token {
|
||||
_tokenBrand: void;
|
||||
|
||||
readonly range: Range;
|
||||
readonly bracket: TokenTreeBracket;
|
||||
readonly bracketType: string | null;
|
||||
|
||||
constructor(range: Range, bracket: TokenTreeBracket, bracketType: string | null) {
|
||||
this.range = range;
|
||||
this.bracket = bracket;
|
||||
this.bracketType = bracketType;
|
||||
}
|
||||
}
|
||||
|
||||
function newNode(token: Token): Node {
|
||||
let node = new Node();
|
||||
node.start = token.range.getStartPosition();
|
||||
node.end = token.range.getEndPosition();
|
||||
return node;
|
||||
}
|
||||
|
||||
class RawToken {
|
||||
_basicTokenBrand: void;
|
||||
|
||||
public lineNumber: number;
|
||||
public lineText: string;
|
||||
public startOffset: number;
|
||||
public endOffset: number;
|
||||
public type: StandardTokenType;
|
||||
public languageId: LanguageId;
|
||||
|
||||
constructor(source: LineTokens, tokenIndex: number, lineNumber: number) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.lineText = source.getLineContent();
|
||||
this.startOffset = source.getStartOffset(tokenIndex);
|
||||
this.endOffset = source.getEndOffset(tokenIndex);
|
||||
this.type = source.getStandardTokenType(tokenIndex);
|
||||
this.languageId = source.getLanguageId(tokenIndex);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelRawTokenScanner {
|
||||
|
||||
private _model: ITextModel;
|
||||
private _lineCount: number;
|
||||
private _versionId: number;
|
||||
private _lineNumber: number;
|
||||
private _tokenIndex: number;
|
||||
private _lineTokens: LineTokens | null;
|
||||
|
||||
constructor(model: ITextModel) {
|
||||
this._model = model;
|
||||
this._lineCount = this._model.getLineCount();
|
||||
this._versionId = this._model.getVersionId();
|
||||
this._lineNumber = 0;
|
||||
this._tokenIndex = 0;
|
||||
this._lineTokens = null;
|
||||
this._advance();
|
||||
}
|
||||
|
||||
private _advance(): void {
|
||||
if (this._lineTokens) {
|
||||
this._tokenIndex++;
|
||||
if (this._tokenIndex >= this._lineTokens.getCount()) {
|
||||
this._lineTokens = null;
|
||||
}
|
||||
}
|
||||
|
||||
while (this._lineNumber < this._lineCount && !this._lineTokens) {
|
||||
this._lineNumber++;
|
||||
this._model.forceTokenization(this._lineNumber);
|
||||
this._lineTokens = this._model.getLineTokens(this._lineNumber);
|
||||
this._tokenIndex = 0;
|
||||
if (this._lineTokens.getLineContent().length === 0) {
|
||||
// Skip empty lines
|
||||
this._lineTokens = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public next(): RawToken | null {
|
||||
if (!this._lineTokens) {
|
||||
return null;
|
||||
}
|
||||
if (this._model.getVersionId() !== this._versionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = new RawToken(this._lineTokens, this._tokenIndex, this._lineNumber);
|
||||
this._advance();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TokenScanner {
|
||||
|
||||
private _rawTokenScanner: ModelRawTokenScanner;
|
||||
private _nextBuff: Token[];
|
||||
|
||||
private _cachedLanguageBrackets: RichEditBrackets | null;
|
||||
private _cachedLanguageId: LanguageId;
|
||||
|
||||
constructor(model: ITextModel) {
|
||||
this._rawTokenScanner = new ModelRawTokenScanner(model);
|
||||
this._nextBuff = [];
|
||||
this._cachedLanguageBrackets = null;
|
||||
this._cachedLanguageId = -1;
|
||||
}
|
||||
|
||||
next(): Token | null {
|
||||
if (this._nextBuff.length > 0) {
|
||||
return this._nextBuff.shift()!;
|
||||
}
|
||||
|
||||
const token = this._rawTokenScanner.next();
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const lineNumber = token.lineNumber;
|
||||
const lineText = token.lineText;
|
||||
const tokenType = token.type;
|
||||
let startOffset = token.startOffset;
|
||||
const endOffset = token.endOffset;
|
||||
|
||||
if (this._cachedLanguageId !== token.languageId) {
|
||||
this._cachedLanguageId = token.languageId;
|
||||
this._cachedLanguageBrackets = LanguageConfigurationRegistry.getBracketsSupport(this._cachedLanguageId);
|
||||
}
|
||||
const modeBrackets = this._cachedLanguageBrackets;
|
||||
|
||||
if (!modeBrackets || ignoreBracketsInToken(tokenType)) {
|
||||
return new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
let foundBracket: Range | null;
|
||||
do {
|
||||
foundBracket = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, startOffset, endOffset);
|
||||
if (foundBracket) {
|
||||
const foundBracketStartOffset = foundBracket.startColumn - 1;
|
||||
const foundBracketEndOffset = foundBracket.endColumn - 1;
|
||||
|
||||
if (startOffset < foundBracketStartOffset) {
|
||||
// there is some text before this bracket in this token
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, foundBracketStartOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
let bracketText = lineText.substring(foundBracketStartOffset, foundBracketEndOffset);
|
||||
bracketText = bracketText.toLowerCase();
|
||||
|
||||
const bracketData = modeBrackets.textIsBracket[bracketText];
|
||||
const bracketIsOpen = modeBrackets.textIsOpenBracket[bracketText];
|
||||
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, foundBracketStartOffset + 1, lineNumber, foundBracketEndOffset + 1),
|
||||
bracketIsOpen ? TokenTreeBracket.Open : TokenTreeBracket.Close,
|
||||
`${bracketData.languageIdentifier.language};${bracketData.open};${bracketData.close}`
|
||||
));
|
||||
|
||||
startOffset = foundBracketEndOffset;
|
||||
}
|
||||
} while (foundBracket);
|
||||
|
||||
if (startOffset < endOffset) {
|
||||
// there is some remaining none-bracket text in this token
|
||||
this._nextBuff.push(new Token(
|
||||
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
|
||||
TokenTreeBracket.None,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
return this._nextBuff.shift() || null;
|
||||
}
|
||||
}
|
||||
|
||||
class TokenTreeBuilder {
|
||||
|
||||
private _scanner: TokenScanner;
|
||||
private _stack: Token[] = [];
|
||||
private _currentToken: Token;
|
||||
|
||||
constructor(model: ITextModel) {
|
||||
this._scanner = new TokenScanner(model);
|
||||
}
|
||||
|
||||
public build(): Node {
|
||||
let node = new NodeList();
|
||||
while (node.append(this._line() || this._any())) {
|
||||
// accept all
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private _accept(condt: (info: Token) => boolean): boolean {
|
||||
let token = this._stack.pop() || this._scanner.next();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
let accepted = condt(token);
|
||||
if (!accepted) {
|
||||
this._stack.push(token);
|
||||
// this._currentToken = null;
|
||||
} else {
|
||||
this._currentToken = token;
|
||||
// console.log('accepted: ' + token.__debugContent);
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private _peek(condt: (info: Token) => boolean): boolean {
|
||||
let ret = false;
|
||||
this._accept(info => {
|
||||
ret = condt(info);
|
||||
return false;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
private _line(): Node | null {
|
||||
let node = new NodeList();
|
||||
let lineNumber: number;
|
||||
|
||||
// capture current linenumber
|
||||
this._peek(info => {
|
||||
lineNumber = info.range.startLineNumber;
|
||||
return false;
|
||||
});
|
||||
|
||||
while (this._peek(info => info.range.startLineNumber === lineNumber)
|
||||
&& node.append(this._token() || this._block())) {
|
||||
|
||||
// all children that started on this line
|
||||
}
|
||||
|
||||
if (!node.children || node.children.length === 0) {
|
||||
return null;
|
||||
} else if (node.children.length === 1) {
|
||||
return node.children[0];
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private _token(): Node | null {
|
||||
if (!this._accept(token => token.bracket === TokenTreeBracket.None)) {
|
||||
return null;
|
||||
}
|
||||
return newNode(this._currentToken);
|
||||
}
|
||||
|
||||
private _block(): Node | null {
|
||||
|
||||
let bracketType: string | null;
|
||||
let accepted: boolean;
|
||||
|
||||
accepted = this._accept(token => {
|
||||
bracketType = token.bracketType;
|
||||
return token.bracket === TokenTreeBracket.Open;
|
||||
});
|
||||
if (!accepted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let bracket = new Block();
|
||||
bracket.open = newNode(this._currentToken);
|
||||
while (bracket.elements.append(this._line())) {
|
||||
// inside brackets
|
||||
}
|
||||
|
||||
if (!this._accept(token => token.bracket === TokenTreeBracket.Close && token.bracketType === bracketType)) {
|
||||
// missing closing bracket -> return just a node list
|
||||
let nodelist = new NodeList();
|
||||
nodelist.append(bracket.open);
|
||||
nodelist.append(bracket.elements);
|
||||
return nodelist;
|
||||
}
|
||||
|
||||
bracket.close = newNode(this._currentToken);
|
||||
return bracket;
|
||||
}
|
||||
|
||||
private _any(): Node | null {
|
||||
if (!this._accept(_ => true)) {
|
||||
return null;
|
||||
}
|
||||
return newNode(this._currentToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses this grammar:
|
||||
* grammer = { line }
|
||||
* line = { block | "token" }
|
||||
* block = "open_bracket" { line } "close_bracket"
|
||||
*/
|
||||
export function build(model: ITextModel): Node {
|
||||
let node = new TokenTreeBuilder(model).build();
|
||||
return node;
|
||||
}
|
||||
|
||||
export function find(node: Node, position: Position): Node | null {
|
||||
if (node instanceof NodeList && node.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Range.containsPosition(node.range, position)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: Node | null = null;
|
||||
|
||||
if (node instanceof NodeList) {
|
||||
if (node.hasChildren) {
|
||||
for (let i = 0, len = node.children.length; i < len && !result; i++) {
|
||||
result = find(node.children[i], position);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (node instanceof Block) {
|
||||
result = find(node.elements, position) || find(node.open, position) || find(node.close, position);
|
||||
}
|
||||
|
||||
return result || node;
|
||||
}
|
||||
@@ -3,29 +3,81 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SelectionRangeProvider } from 'vs/editor/common/modes';
|
||||
import { SelectionRangeProvider, SelectionRange } from 'vs/editor/common/modes';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { isUpperAsciiLetter, isLowerAsciiLetter } from 'vs/base/common/strings';
|
||||
|
||||
export class WordSelectionRangeProvider implements SelectionRangeProvider {
|
||||
|
||||
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
|
||||
let result: Range[] = [];
|
||||
provideSelectionRanges(model: ITextModel, position: Position): SelectionRange[] {
|
||||
let result: SelectionRange[] = [];
|
||||
this._addInWordRanges(result, model, position);
|
||||
this._addWordRanges(result, model, position);
|
||||
this._addLineRanges(result, model, position);
|
||||
this._addWhitespaceLine(result, model, position);
|
||||
result.push({ range: model.getFullModelRange(), kind: 'statement.all' });
|
||||
return result;
|
||||
}
|
||||
|
||||
private _addWordRanges(bucket: Range[], model: ITextModel, pos: Position): void {
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (word) {
|
||||
bucket.push(new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn));
|
||||
private _addInWordRanges(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
|
||||
const obj = model.getWordAtPosition(pos);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { word, startColumn } = obj;
|
||||
let offset = pos.column - startColumn;
|
||||
let start = offset;
|
||||
let end = offset;
|
||||
let lastCh: number = 0;
|
||||
|
||||
// LEFT anchor (start)
|
||||
for (; start >= 0; start--) {
|
||||
let ch = word.charCodeAt(start);
|
||||
if (ch === CharCode.Underline || ch === CharCode.Dash) {
|
||||
// foo-bar OR foo_bar
|
||||
break;
|
||||
} else if (isLowerAsciiLetter(ch) && isUpperAsciiLetter(lastCh)) {
|
||||
// fooBar
|
||||
break;
|
||||
}
|
||||
lastCh = ch;
|
||||
}
|
||||
start += 1;
|
||||
|
||||
// RIGHT anchor (end)
|
||||
for (; end < word.length; end++) {
|
||||
let ch = word.charCodeAt(end);
|
||||
if (isUpperAsciiLetter(ch) && isLowerAsciiLetter(lastCh)) {
|
||||
// fooBar
|
||||
break;
|
||||
} else if (ch === CharCode.Underline || ch === CharCode.Dash) {
|
||||
// foo-bar OR foo_bar
|
||||
break;
|
||||
}
|
||||
lastCh = ch;
|
||||
}
|
||||
|
||||
if (start < end) {
|
||||
bucket.push({ range: new Range(pos.lineNumber, startColumn + start, pos.lineNumber, startColumn + end), kind: 'statement.word.part' });
|
||||
}
|
||||
}
|
||||
|
||||
private _addLineRanges(bucket: Range[], model: ITextModel, pos: Position): void {
|
||||
bucket.push(new Range(pos.lineNumber, model.getLineFirstNonWhitespaceColumn(pos.lineNumber), pos.lineNumber, model.getLineLastNonWhitespaceColumn(pos.lineNumber)));
|
||||
bucket.push(new Range(pos.lineNumber, model.getLineMinColumn(pos.lineNumber), pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)));
|
||||
private _addWordRanges(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
|
||||
const word = model.getWordAtPosition(pos);
|
||||
if (word) {
|
||||
bucket.push({ range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn), kind: 'statement.word' });
|
||||
}
|
||||
}
|
||||
|
||||
private _addWhitespaceLine(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
|
||||
if (model.getLineLength(pos.lineNumber) > 0
|
||||
&& model.getLineFirstNonWhitespaceColumn(pos.lineNumber) === 0
|
||||
&& model.getLineLastNonWhitespaceColumn(pos.lineNumber) === 0
|
||||
) {
|
||||
bucket.push({ range: new Range(pos.lineNumber, 1, pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)), kind: 'statement.line' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ export class SnippetController2 implements IEditorContribution {
|
||||
private readonly _hasNextTabstop: IContextKey<boolean>;
|
||||
private readonly _hasPrevTabstop: IContextKey<boolean>;
|
||||
|
||||
private _session: SnippetSession;
|
||||
private _session?: SnippetSession;
|
||||
private _snippetListener: IDisposable[] = [];
|
||||
private _modelVersionId: number;
|
||||
private _currentChoice: Choice;
|
||||
private _currentChoice?: Choice;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@@ -87,6 +87,9 @@ export class SnippetController2 implements IEditorContribution {
|
||||
undoStopBefore: boolean = true, undoStopAfter: boolean = true,
|
||||
adjustWhitespace: boolean = true,
|
||||
): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't listen while inserting the snippet
|
||||
// as that is the inflight state causing cancelation
|
||||
@@ -118,7 +121,7 @@ export class SnippetController2 implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _updateState(): void {
|
||||
if (!this._session) {
|
||||
if (!this._session || !this._editor.hasModel()) {
|
||||
// canceled in the meanwhile
|
||||
return;
|
||||
}
|
||||
@@ -147,6 +150,11 @@ export class SnippetController2 implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _handleChoice(): void {
|
||||
if (!this._session || !this._editor.hasModel()) {
|
||||
this._currentChoice = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const { choice } = this._session;
|
||||
if (!choice) {
|
||||
this._currentChoice = undefined;
|
||||
@@ -173,7 +181,7 @@ export class SnippetController2 implements IEditorContribution {
|
||||
// insertText: `\${1|${after.concat(before).join(',')}|}$0`,
|
||||
// snippetType: 'textmate',
|
||||
sortText: repeat('a', i),
|
||||
range: Range.fromPositions(this._editor.getPosition(), this._editor.getPosition().delta(0, first.value.length))
|
||||
range: Range.fromPositions(this._editor.getPosition()!, this._editor.getPosition()!.delta(0, first.value.length))
|
||||
};
|
||||
}));
|
||||
}
|
||||
@@ -196,20 +204,24 @@ export class SnippetController2 implements IEditorContribution {
|
||||
}
|
||||
|
||||
prev(): void {
|
||||
this._session.prev();
|
||||
if (this._session) {
|
||||
this._session.prev();
|
||||
}
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
next(): void {
|
||||
this._session.next();
|
||||
if (this._session) {
|
||||
this._session.next();
|
||||
}
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
isInSnippet(): boolean {
|
||||
return this._inSnippet.get();
|
||||
return Boolean(this._inSnippet.get());
|
||||
}
|
||||
|
||||
getSessionEnclosingRange(): Range {
|
||||
getSessionEnclosingRange(): Range | undefined {
|
||||
if (this._session) {
|
||||
return this._session.getEnclosingRange();
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ export class FormatString extends Marker {
|
||||
super();
|
||||
}
|
||||
|
||||
resolve(value: string): string {
|
||||
resolve(value?: string): string {
|
||||
if (this.shorthandName === 'upcase') {
|
||||
return !value ? '' : value.toLocaleUpperCase();
|
||||
} else if (this.shorthandName === 'downcase') {
|
||||
@@ -626,10 +626,11 @@ export class SnippetParser {
|
||||
return true;
|
||||
});
|
||||
for (const placeholder of incompletePlaceholders) {
|
||||
if (placeholderDefaultValues.has(placeholder.index)) {
|
||||
const defaultValues = placeholderDefaultValues.get(placeholder.index);
|
||||
if (defaultValues) {
|
||||
const clone = new Placeholder(placeholder.index);
|
||||
clone.transform = placeholder.transform;
|
||||
for (const child of placeholderDefaultValues.get(placeholder.index)) {
|
||||
for (const child of defaultValues) {
|
||||
clone.appendChild(child.clone());
|
||||
}
|
||||
snippet.replace(placeholder, [clone]);
|
||||
@@ -929,7 +930,7 @@ export class SnippetParser {
|
||||
|
||||
let escaped: string;
|
||||
if (escaped = this._accept(TokenType.Backslash, true)) {
|
||||
escaped = this._accept(TokenType.Forwardslash, true) || escaped;
|
||||
escaped = this._accept(TokenType.Backslash, true) || this._accept(TokenType.Forwardslash, true) || escaped;
|
||||
transform.appendChild(new Text(escaped));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { groupBy } from 'vs/base/common/arrays';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { getLeadingWhitespace } from 'vs/base/common/strings';
|
||||
import 'vs/css!./snippetSession';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -34,7 +34,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
|
||||
export class OneSnippet {
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private readonly _snippet: TextmateSnippet;
|
||||
private readonly _offset: number;
|
||||
|
||||
@@ -50,7 +50,7 @@ export class OneSnippet {
|
||||
inactiveFinal: ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'finish-snippet-placeholder' }),
|
||||
};
|
||||
|
||||
constructor(editor: ICodeEditor, snippet: TextmateSnippet, offset: number) {
|
||||
constructor(editor: IActiveCodeEditor, snippet: TextmateSnippet, offset: number) {
|
||||
this._editor = editor;
|
||||
this._snippet = snippet;
|
||||
this._offset = offset;
|
||||
@@ -95,6 +95,9 @@ export class OneSnippet {
|
||||
}
|
||||
|
||||
move(fwd: boolean | undefined): Selection[] {
|
||||
if (!this._editor.hasModel()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this._initDecorations();
|
||||
|
||||
@@ -105,8 +108,8 @@ export class OneSnippet {
|
||||
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
|
||||
// Check if the placeholder has a transformation
|
||||
if (placeholder.transform) {
|
||||
const id = this._placeholderDecorations.get(placeholder);
|
||||
const range = this._editor.getModel().getDecorationRange(id);
|
||||
const id = this._placeholderDecorations.get(placeholder)!;
|
||||
const range = this._editor.getModel().getDecorationRange(id)!;
|
||||
const currentValue = this._editor.getModel().getValueInRange(range);
|
||||
|
||||
operations.push(EditOperation.replaceMove(range, placeholder.transform.resolve(currentValue)));
|
||||
@@ -142,8 +145,8 @@ export class OneSnippet {
|
||||
// Special case #2: placeholders enclosing active placeholders
|
||||
const selections: Selection[] = [];
|
||||
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
|
||||
const id = this._placeholderDecorations.get(placeholder);
|
||||
const range = this._editor.getModel().getDecorationRange(id);
|
||||
const id = this._placeholderDecorations.get(placeholder)!;
|
||||
const range = this._editor.getModel().getDecorationRange(id)!;
|
||||
selections.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn));
|
||||
|
||||
// consider to skip this placeholder index when the decoration
|
||||
@@ -155,7 +158,7 @@ export class OneSnippet {
|
||||
activePlaceholders.add(placeholder);
|
||||
|
||||
for (const enclosingPlaceholder of this._snippet.enclosingPlaceholders(placeholder)) {
|
||||
const id = this._placeholderDecorations.get(enclosingPlaceholder);
|
||||
const id = this._placeholderDecorations.get(enclosingPlaceholder)!;
|
||||
accessor.changeDecorationOptions(id, enclosingPlaceholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active);
|
||||
activePlaceholders.add(enclosingPlaceholder);
|
||||
}
|
||||
@@ -170,7 +173,7 @@ export class OneSnippet {
|
||||
});
|
||||
|
||||
return selections;
|
||||
});
|
||||
})!;
|
||||
|
||||
return !skipThisPlaceholder ? newSelections : this.move(fwd);
|
||||
}
|
||||
@@ -190,7 +193,7 @@ export class OneSnippet {
|
||||
computePossibleSelections() {
|
||||
const result = new Map<number, Range[]>();
|
||||
for (const placeholdersWithEqualIndex of this._placeholderGroups) {
|
||||
let ranges: Range[];
|
||||
let ranges: Range[] | undefined;
|
||||
|
||||
for (const placeholder of placeholdersWithEqualIndex) {
|
||||
if (placeholder.isFinalTabstop) {
|
||||
@@ -203,7 +206,7 @@ export class OneSnippet {
|
||||
result.set(placeholder.index, ranges);
|
||||
}
|
||||
|
||||
const id = this._placeholderDecorations.get(placeholder);
|
||||
const id = this._placeholderDecorations.get(placeholder)!;
|
||||
const range = this._editor.getModel().getDecorationRange(id);
|
||||
if (!range) {
|
||||
// one of the placeholder lost its decoration and
|
||||
@@ -219,7 +222,7 @@ export class OneSnippet {
|
||||
return result;
|
||||
}
|
||||
|
||||
get choice(): Choice {
|
||||
get choice(): Choice | undefined {
|
||||
return this._placeholderGroups[this._placeholderGroupsIdx][0].choice;
|
||||
}
|
||||
|
||||
@@ -235,13 +238,13 @@ export class OneSnippet {
|
||||
// everything is sorted by editor selection we can simply remove
|
||||
// elements from the beginning of the array
|
||||
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
|
||||
const nested = others.shift();
|
||||
const nested = others.shift()!;
|
||||
console.assert(!nested._placeholderDecorations);
|
||||
|
||||
// Massage placeholder-indicies of the nested snippet to be
|
||||
// sorted right after the insertion point. This ensures we move
|
||||
// through the placeholders in the correct order
|
||||
const indexLastPlaceholder = nested._snippet.placeholderInfo.last.index;
|
||||
const indexLastPlaceholder = nested._snippet.placeholderInfo.last!.index;
|
||||
|
||||
for (const nestedPlaceholder of nested._snippet.placeholderInfo.all) {
|
||||
if (nestedPlaceholder.isFinalTabstop) {
|
||||
@@ -254,7 +257,7 @@ export class OneSnippet {
|
||||
|
||||
// Remove the placeholder at which position are inserting
|
||||
// the snippet and also remove its decoration.
|
||||
const id = this._placeholderDecorations.get(placeholder);
|
||||
const id = this._placeholderDecorations.get(placeholder)!;
|
||||
accessor.removeDecoration(id);
|
||||
this._placeholderDecorations.delete(placeholder);
|
||||
|
||||
@@ -277,15 +280,15 @@ export class OneSnippet {
|
||||
});
|
||||
}
|
||||
|
||||
public getEnclosingRange(): Range {
|
||||
let result: Range;
|
||||
public getEnclosingRange(): Range | undefined {
|
||||
let result: Range | undefined;
|
||||
const model = this._editor.getModel();
|
||||
this._placeholderDecorations.forEach((decorationId) => {
|
||||
const placeholderRange = model.getDecorationRange(decorationId);
|
||||
const placeholderRange = model.getDecorationRange(decorationId) || undefined;
|
||||
if (!result) {
|
||||
result = placeholderRange;
|
||||
} else {
|
||||
result = result.plusRange(placeholderRange);
|
||||
result = result.plusRange(placeholderRange!);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
@@ -340,12 +343,15 @@ export class SnippetSession {
|
||||
return selection;
|
||||
}
|
||||
|
||||
static createEditsAndSnippets(editor: ICodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
|
||||
|
||||
const model = editor.getModel();
|
||||
static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
|
||||
const edits: IIdentifiedSingleEditOperation[] = [];
|
||||
const snippets: OneSnippet[] = [];
|
||||
|
||||
if (!editor.hasModel()) {
|
||||
return { edits, snippets };
|
||||
}
|
||||
const model = editor.getModel();
|
||||
|
||||
const modelBasedVariableResolver = new ModelBasedVariableResolver(model);
|
||||
const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional));
|
||||
|
||||
@@ -419,7 +425,7 @@ export class SnippetSession {
|
||||
return { edits, snippets };
|
||||
}
|
||||
|
||||
private readonly _editor: ICodeEditor;
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private readonly _template: string;
|
||||
private readonly _templateMerges: [number, number, string][] = [];
|
||||
private readonly _overwriteBefore: number;
|
||||
@@ -427,7 +433,7 @@ export class SnippetSession {
|
||||
private readonly _adjustWhitespace: boolean;
|
||||
private _snippets: OneSnippet[] = [];
|
||||
|
||||
constructor(editor: ICodeEditor, template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true) {
|
||||
constructor(editor: IActiveCodeEditor, template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true) {
|
||||
this._editor = editor;
|
||||
this._template = template;
|
||||
this._overwriteBefore = overwriteBefore;
|
||||
@@ -444,6 +450,9 @@ export class SnippetSession {
|
||||
}
|
||||
|
||||
insert(): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
|
||||
@@ -457,12 +466,15 @@ export class SnippetSession {
|
||||
} else {
|
||||
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
|
||||
}
|
||||
});
|
||||
})!;
|
||||
this._editor.setSelections(selections);
|
||||
this._editor.revealRange(selections[0]);
|
||||
}
|
||||
|
||||
merge(template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
|
||||
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, overwriteBefore, overwriteAfter, true, adjustWhitespace);
|
||||
|
||||
@@ -478,7 +490,7 @@ export class SnippetSession {
|
||||
} else {
|
||||
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
|
||||
}
|
||||
}));
|
||||
})!);
|
||||
}
|
||||
|
||||
next(): void {
|
||||
@@ -514,7 +526,7 @@ export class SnippetSession {
|
||||
return this._snippets[0].hasPlaceholder;
|
||||
}
|
||||
|
||||
get choice(): Choice {
|
||||
get choice(): Choice | undefined {
|
||||
return this._snippets[0].choice;
|
||||
}
|
||||
|
||||
@@ -532,7 +544,7 @@ export class SnippetSession {
|
||||
return false;
|
||||
}
|
||||
|
||||
let allPossibleSelections: Map<number, Range[]>;
|
||||
let allPossibleSelections = new Map<number, Range[]>();
|
||||
for (const snippet of this._snippets) {
|
||||
|
||||
const possibleSelections = snippet.computePossibleSelections();
|
||||
@@ -540,8 +552,7 @@ export class SnippetSession {
|
||||
// for the first snippet find the placeholder (and its ranges)
|
||||
// that contain at least one selection. for all remaining snippets
|
||||
// the same placeholder (and their ranges) must be used.
|
||||
if (!allPossibleSelections) {
|
||||
allPossibleSelections = new Map<number, Range[]>();
|
||||
if (allPossibleSelections.size === 0) {
|
||||
possibleSelections.forEach((ranges, index) => {
|
||||
|
||||
ranges.sort(Range.compareRangesUsingStarts);
|
||||
@@ -563,7 +574,7 @@ export class SnippetSession {
|
||||
// add selections from 'this' snippet so that we know all
|
||||
// selections for this placeholder
|
||||
allPossibleSelections.forEach((array, index) => {
|
||||
array.push(...possibleSelections.get(index));
|
||||
array.push(...possibleSelections.get(index)!);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -595,14 +606,14 @@ export class SnippetSession {
|
||||
return allPossibleSelections.size > 0;
|
||||
}
|
||||
|
||||
public getEnclosingRange(): Range {
|
||||
let result: Range;
|
||||
public getEnclosingRange(): Range | undefined {
|
||||
let result: Range | undefined;
|
||||
for (const snippet of this._snippets) {
|
||||
const snippetRange = snippet.getEnclosingRange();
|
||||
if (!result) {
|
||||
result = snippetRange;
|
||||
} else {
|
||||
result = result.plusRange(snippetRange);
|
||||
result = result.plusRange(snippetRange!);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -49,7 +49,7 @@ export class CompositeSnippetVariableResolver implements VariableResolver {
|
||||
resolve(variable: Variable): string | undefined {
|
||||
for (const delegate of this._delegates) {
|
||||
let value = delegate.resolve(variable);
|
||||
if (value !== void 0) {
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class TestSnippetController extends SnippetController2 {
|
||||
}
|
||||
|
||||
isInSnippetMode(): boolean {
|
||||
return SnippetController2.InSnippetMode.getValue(this._contextKeyService);
|
||||
return SnippetController2.InSnippetMode.getValue(this._contextKeyService)!;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ suite('SnippetController', () => {
|
||||
}
|
||||
|
||||
withTestCodeEditor(lines, {}, (editor, cursor) => {
|
||||
editor.getModel().updateOptions({
|
||||
editor.getModel()!.updateOptions({
|
||||
insertSpaces: false
|
||||
});
|
||||
let snippetController = editor.registerAndInstantiateContribution<TestSnippetController>(TestSnippetController);
|
||||
@@ -63,30 +63,30 @@ suite('SnippetController', () => {
|
||||
editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
|
||||
snippetController.insert(template, 0, 0);
|
||||
assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {');
|
||||
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];');
|
||||
assert.equal(editor.getModel().getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel().getLineContent(7), '\t}');
|
||||
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
|
||||
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
|
||||
|
||||
editor.trigger('test', 'type', { text: 'i' });
|
||||
assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < array.length; i++) {');
|
||||
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[i];');
|
||||
assert.equal(editor.getModel().getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel().getLineContent(7), '\t}');
|
||||
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {');
|
||||
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
|
||||
|
||||
snippetController.next();
|
||||
editor.trigger('test', 'type', { text: 'arr' });
|
||||
assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
|
||||
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[i];');
|
||||
assert.equal(editor.getModel().getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel().getLineContent(7), '\t}');
|
||||
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
|
||||
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
|
||||
|
||||
snippetController.prev();
|
||||
editor.trigger('test', 'type', { text: 'j' });
|
||||
assert.equal(editor.getModel().getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
|
||||
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[j];');
|
||||
assert.equal(editor.getModel().getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel().getLineContent(7), '\t}');
|
||||
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
|
||||
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
|
||||
|
||||
snippetController.next();
|
||||
snippetController.next();
|
||||
@@ -99,10 +99,10 @@ suite('SnippetController', () => {
|
||||
editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
|
||||
snippetController.insert(template, 0, 0);
|
||||
assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {');
|
||||
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];');
|
||||
assert.equal(editor.getModel().getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel().getLineContent(7), '\t}');
|
||||
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
|
||||
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
|
||||
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
|
||||
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
|
||||
|
||||
snippetController.cancel();
|
||||
assert.deepEqual(editor.getPosition(), new Position(4, 16));
|
||||
@@ -114,7 +114,7 @@ suite('SnippetController', () => {
|
||||
// editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
// snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
// editor.getModel().applyEdits([{
|
||||
// editor.getModel()!.applyEdits([{
|
||||
// forceMoveMarkers: false,
|
||||
// identifier: null,
|
||||
// isAutoWhitespaceEdit: false,
|
||||
@@ -131,7 +131,7 @@ suite('SnippetController', () => {
|
||||
// editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
// snippetController.run(codeSnippet, 0, 0);
|
||||
|
||||
// editor.getModel().applyEdits([{
|
||||
// editor.getModel()!.applyEdits([{
|
||||
// forceMoveMarkers: false,
|
||||
// identifier: null,
|
||||
// isAutoWhitespaceEdit: false,
|
||||
@@ -148,7 +148,7 @@ suite('SnippetController', () => {
|
||||
// editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
// snippetController.run(codeSnippet, 0, 0);
|
||||
|
||||
// editor.getModel().applyEdits([{
|
||||
// editor.getModel()!.applyEdits([{
|
||||
// forceMoveMarkers: false,
|
||||
// identifier: null,
|
||||
// isAutoWhitespaceEdit: false,
|
||||
@@ -165,7 +165,7 @@ suite('SnippetController', () => {
|
||||
// editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
// snippetController.run(codeSnippet, 0, 0);
|
||||
|
||||
// editor.getModel().applyEdits([{
|
||||
// editor.getModel()!.applyEdits([{
|
||||
// forceMoveMarkers: false,
|
||||
// identifier: null,
|
||||
// isAutoWhitespaceEdit: false,
|
||||
@@ -182,7 +182,7 @@ suite('SnippetController', () => {
|
||||
editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
editor.getModel().setValue('goodbye');
|
||||
editor.getModel()!.setValue('goodbye');
|
||||
|
||||
assert.equal(snippetController.isInSnippetMode(), false);
|
||||
});
|
||||
@@ -193,7 +193,7 @@ suite('SnippetController', () => {
|
||||
editor.setPosition({ lineNumber: 4, column: 2 });
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
editor.getModel().undo();
|
||||
editor.getModel()!.undo();
|
||||
|
||||
assert.equal(snippetController.isInSnippetMode(), false);
|
||||
});
|
||||
@@ -242,8 +242,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'foo$0';
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
|
||||
});
|
||||
@@ -257,8 +257,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'foo$0bar';
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
|
||||
});
|
||||
@@ -272,8 +272,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'foo$0bar';
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString());
|
||||
});
|
||||
@@ -287,8 +287,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'foo\n$0\nbar';
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
|
||||
});
|
||||
@@ -302,8 +302,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'foo\n$0\nbar';
|
||||
snippetController.insert(codeSnippet, 0, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
|
||||
});
|
||||
@@ -316,8 +316,8 @@ suite('SnippetController', () => {
|
||||
codeSnippet = 'xo$0r';
|
||||
snippetController.insert(codeSnippet, 1, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -329,9 +329,9 @@ suite('SnippetController', () => {
|
||||
codeSnippet = '{{% url_**$1** %}}';
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
|
||||
assert.equal(editor.getModel().getValue(), 'example example {{% url_**** %}}');
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
|
||||
assert.equal(editor.getModel()!.getValue(), 'example example {{% url_**** %}}');
|
||||
|
||||
}, ['example example sc']);
|
||||
|
||||
@@ -347,9 +347,9 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection().toString());
|
||||
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection()!.toString());
|
||||
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
|
||||
|
||||
}, ['af']);
|
||||
|
||||
@@ -365,9 +365,9 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection().toString());
|
||||
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection()!.toString());
|
||||
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
|
||||
|
||||
}, ['af']);
|
||||
|
||||
@@ -381,9 +381,9 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 8, 0);
|
||||
|
||||
assert.equal(editor.getModel().getValue(), 'after');
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection().toString());
|
||||
assert.equal(editor.getModel()!.getValue(), 'after');
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection()!.toString());
|
||||
|
||||
}, ['afterone']);
|
||||
});
|
||||
@@ -405,8 +405,8 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 2);
|
||||
const [first, second] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 2);
|
||||
const [first, second] = editor.getSelections()!;
|
||||
|
||||
assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString());
|
||||
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), second.toString());
|
||||
@@ -430,8 +430,8 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.equal(editor.getSelections().length, 1);
|
||||
const [first] = editor.getSelections();
|
||||
assert.equal(editor.getSelections()!.length, 1);
|
||||
const [first] = editor.getSelections()!;
|
||||
|
||||
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString());
|
||||
|
||||
@@ -450,7 +450,7 @@ suite('SnippetController', () => {
|
||||
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
|
||||
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 }));
|
||||
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 }));
|
||||
|
||||
}, ['af', '\taf']);
|
||||
});
|
||||
@@ -466,7 +466,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = '_foo';
|
||||
controller.insert(codeSnippet, 1, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo');
|
||||
|
||||
}, ['this._', 'abc']);
|
||||
|
||||
@@ -479,7 +479,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = 'XX';
|
||||
controller.insert(codeSnippet, 1, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this.XX\nabcXX');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this.XX\nabcXX');
|
||||
|
||||
}, ['this._', 'abc']);
|
||||
|
||||
@@ -493,7 +493,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = '_foo';
|
||||
controller.insert(codeSnippet, 1, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo\ndef_foo');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo');
|
||||
|
||||
}, ['this._', 'abc', 'def_']);
|
||||
|
||||
@@ -507,7 +507,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = '._foo';
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this._foo\nabc._foo\ndef._foo');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
|
||||
|
||||
}, ['this._', 'abc', 'def._']);
|
||||
|
||||
@@ -521,7 +521,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = '._foo';
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this._foo\nabc._foo\ndef._foo');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
|
||||
|
||||
}, ['this._', 'abc', 'def._']);
|
||||
|
||||
@@ -535,7 +535,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = '._foo';
|
||||
controller.insert(codeSnippet, 2, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'this._._foo\na._foo\ndef._._foo');
|
||||
assert.equal(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo');
|
||||
|
||||
}, ['this._', 'abc', 'def._']);
|
||||
|
||||
@@ -551,7 +551,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = 'document';
|
||||
controller.insert(codeSnippet, 3, 0);
|
||||
assert.equal(editor.getModel().getValue(), '{document}\n{document && true}');
|
||||
assert.equal(editor.getModel()!.getValue(), '{document}\n{document && true}');
|
||||
|
||||
}, ['{foo}', '{foo && true}']);
|
||||
});
|
||||
@@ -566,7 +566,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = 'for (var ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
|
||||
controller.insert(codeSnippet, 0, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
|
||||
assert.equal(editor.getModel()!.getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
|
||||
|
||||
}, ['for (var i=0; i<len; i++) { }']);
|
||||
|
||||
@@ -579,7 +579,7 @@ suite('SnippetController', () => {
|
||||
|
||||
codeSnippet = 'for (let ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
|
||||
controller.insert(codeSnippet, 0, 0);
|
||||
assert.equal(editor.getModel().getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
|
||||
assert.equal(editor.getModel()!.getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
|
||||
|
||||
}, ['for (var i=0; i<len; i++) { }']);
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import { Handler } from 'vs/editor/common/editorCommon';
|
||||
suite('SnippetController2', function () {
|
||||
|
||||
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
|
||||
for (const selection of editor.getSelections()) {
|
||||
const actual = s.shift();
|
||||
for (const selection of editor.getSelections()!) {
|
||||
const actual = s.shift()!;
|
||||
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
|
||||
}
|
||||
assert.equal(s.length, 0);
|
||||
|
||||
@@ -106,7 +106,7 @@ suite('SnippetParser', () => {
|
||||
}
|
||||
while (marker.length > 0) {
|
||||
let m = marker.pop();
|
||||
let ctor = ctors.pop();
|
||||
let ctor = ctors.pop()!;
|
||||
assert.ok(m instanceof ctor);
|
||||
}
|
||||
assert.equal(marker.length, ctors.length);
|
||||
@@ -748,4 +748,10 @@ suite('SnippetParser', () => {
|
||||
let [, , clone] = snippet.children;
|
||||
assertParent(clone);
|
||||
});
|
||||
|
||||
test('Backspace can\'t be escaped in snippet variable transforms #65412', function () {
|
||||
|
||||
let snippet = new SnippetParser().parse('namespace ${TM_DIRECTORY/[\\/]/\\\\/g};');
|
||||
assertMarker(snippet, Text, Variable, Text);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -14,12 +14,12 @@ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
|
||||
suite('SnippetSession', function () {
|
||||
|
||||
let editor: ICodeEditor;
|
||||
let editor: IActiveCodeEditor;
|
||||
let model: TextModel;
|
||||
|
||||
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
|
||||
function assertSelections(editor: IActiveCodeEditor, ...s: Selection[]) {
|
||||
for (const selection of editor.getSelections()) {
|
||||
const actual = s.shift();
|
||||
const actual = s.shift()!;
|
||||
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
|
||||
}
|
||||
assert.equal(s.length, 0);
|
||||
@@ -27,7 +27,7 @@ suite('SnippetSession', function () {
|
||||
|
||||
setup(function () {
|
||||
model = TextModel.createFromString('function foo() {\n console.log(a);\n}');
|
||||
editor = createTestCodeEditor({ model: model });
|
||||
editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor;
|
||||
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]);
|
||||
assert.equal(model.getEOL(), '\n');
|
||||
});
|
||||
@@ -72,7 +72,7 @@ suite('SnippetSession', function () {
|
||||
test('text edits & selection', function () {
|
||||
const session = new SnippetSession(editor, 'foo${1:bar}foo$0');
|
||||
session.insert();
|
||||
assert.equal(editor.getModel().getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
|
||||
assert.equal(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
|
||||
|
||||
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
|
||||
session.next();
|
||||
@@ -115,7 +115,7 @@ suite('SnippetSession', function () {
|
||||
const session = new SnippetSession(editor, 'foo\n\t${1:bar}\n$0');
|
||||
session.insert();
|
||||
|
||||
assert.equal(editor.getModel().getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
|
||||
assert.equal(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
|
||||
|
||||
assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(5, 9, 5, 12));
|
||||
|
||||
@@ -128,7 +128,7 @@ suite('SnippetSession', function () {
|
||||
editor.setSelection(new Selection(2, 5, 2, 5));
|
||||
const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', 0, 0, false);
|
||||
session.insert();
|
||||
assert.equal(editor.getModel().getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
|
||||
assert.equal(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
|
||||
});
|
||||
|
||||
test('snippets, selections -> next/prev', () => {
|
||||
@@ -446,7 +446,7 @@ suite('SnippetSession', function () {
|
||||
});
|
||||
|
||||
test('snippets, transform', function () {
|
||||
editor.getModel().setValue('');
|
||||
editor.getModel()!.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
const session = new SnippetSession(editor, '${1/foo/bar/}$0');
|
||||
session.insert();
|
||||
@@ -461,7 +461,7 @@ suite('SnippetSession', function () {
|
||||
});
|
||||
|
||||
test('snippets, multi placeholder same index one transform', function () {
|
||||
editor.getModel().setValue('');
|
||||
editor.getModel()!.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
const session = new SnippetSession(editor, '$1 baz ${1/foo/bar/}$0');
|
||||
session.insert();
|
||||
@@ -476,7 +476,7 @@ suite('SnippetSession', function () {
|
||||
});
|
||||
|
||||
test('snippets, transform example', function () {
|
||||
editor.getModel().setValue('');
|
||||
editor.getModel()!.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
const session = new SnippetSession(editor, '${1:name} : ${2:type}${3/\\s:=(.*)/${1:+ :=}${1}/};\n$0');
|
||||
session.insert();
|
||||
@@ -516,8 +516,8 @@ suite('SnippetSession', function () {
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
editor.getModel().setValue(base);
|
||||
editor.getModel().updateOptions({ insertSpaces: false });
|
||||
editor.getModel()!.setValue(base);
|
||||
editor.getModel()!.updateOptions({ insertSpaces: false });
|
||||
editor.setSelection(new Selection(2, 2, 2, 2));
|
||||
|
||||
const session = new SnippetSession(editor, snippet);
|
||||
@@ -538,7 +538,7 @@ suite('SnippetSession', function () {
|
||||
});
|
||||
|
||||
test('snippets, transform example hit if', function () {
|
||||
editor.getModel().setValue('');
|
||||
editor.getModel()!.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
const session = new SnippetSession(editor, '${1:name} : ${2:type}${3/\\s:=(.*)/${1:+ :=}${1}/};\n$0');
|
||||
session.insert();
|
||||
@@ -561,43 +561,43 @@ suite('SnippetSession', function () {
|
||||
});
|
||||
|
||||
test('Snippet placeholder index incorrect after using 2+ snippets in a row that each end with a placeholder, #30769', function () {
|
||||
editor.getModel().setValue('');
|
||||
editor.getModel()!.setValue('');
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
const session = new SnippetSession(editor, 'test ${1:replaceme}');
|
||||
session.insert();
|
||||
|
||||
editor.trigger('test', 'type', { text: '1' });
|
||||
editor.trigger('test', 'type', { text: '\n' });
|
||||
assert.equal(editor.getModel().getValue(), 'test 1\n');
|
||||
assert.equal(editor.getModel()!.getValue(), 'test 1\n');
|
||||
|
||||
session.merge('test ${1:replaceme}');
|
||||
editor.trigger('test', 'type', { text: '2' });
|
||||
editor.trigger('test', 'type', { text: '\n' });
|
||||
|
||||
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\n');
|
||||
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\n');
|
||||
|
||||
session.merge('test ${1:replaceme}');
|
||||
editor.trigger('test', 'type', { text: '3' });
|
||||
editor.trigger('test', 'type', { text: '\n' });
|
||||
|
||||
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\ntest 3\n');
|
||||
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n');
|
||||
|
||||
session.merge('test ${1:replaceme}');
|
||||
editor.trigger('test', 'type', { text: '4' });
|
||||
editor.trigger('test', 'type', { text: '\n' });
|
||||
|
||||
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
|
||||
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
|
||||
});
|
||||
|
||||
test('Snippet variable text isn\'t whitespace normalised, #31124', function () {
|
||||
editor.getModel().setValue([
|
||||
editor.getModel()!.setValue([
|
||||
'start',
|
||||
'\t\t-one',
|
||||
'\t\t-two',
|
||||
'end'
|
||||
].join('\n'));
|
||||
|
||||
editor.getModel().updateOptions({ insertSpaces: false });
|
||||
editor.getModel()!.updateOptions({ insertSpaces: false });
|
||||
editor.setSelection(new Selection(2, 2, 3, 7));
|
||||
|
||||
new SnippetSession(editor, '<div>\n\t$TM_SELECTED_TEXT\n</div>$0').insert();
|
||||
@@ -611,16 +611,16 @@ suite('SnippetSession', function () {
|
||||
'end'
|
||||
].join('\n');
|
||||
|
||||
assert.equal(editor.getModel().getValue(), expected);
|
||||
assert.equal(editor.getModel()!.getValue(), expected);
|
||||
|
||||
editor.getModel().setValue([
|
||||
editor.getModel()!.setValue([
|
||||
'start',
|
||||
'\t\t-one',
|
||||
'\t-two',
|
||||
'end'
|
||||
].join('\n'));
|
||||
|
||||
editor.getModel().updateOptions({ insertSpaces: false });
|
||||
editor.getModel()!.updateOptions({ insertSpaces: false });
|
||||
editor.setSelection(new Selection(2, 2, 3, 7));
|
||||
|
||||
new SnippetSession(editor, '<div>\n\t$TM_SELECTED_TEXT\n</div>$0').insert();
|
||||
@@ -634,11 +634,11 @@ suite('SnippetSession', function () {
|
||||
'end'
|
||||
].join('\n');
|
||||
|
||||
assert.equal(editor.getModel().getValue(), expected);
|
||||
assert.equal(editor.getModel()!.getValue(), expected);
|
||||
});
|
||||
|
||||
test('Selecting text from left to right, and choosing item messes up code, #31199', function () {
|
||||
const model = editor.getModel();
|
||||
const model = editor.getModel()!;
|
||||
model.setValue('console.log');
|
||||
|
||||
let actual = SnippetSession.adjustSelection(model, new Selection(1, 12, 1, 9), 3, 0);
|
||||
|
||||
@@ -33,7 +33,7 @@ suite('Snippet Variables Resolver', function () {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
function assertVariableResolve(resolver: VariableResolver, varName: string, expected: string) {
|
||||
function assertVariableResolve(resolver: VariableResolver, varName: string, expected?: string) {
|
||||
const snippet = new SnippetParser().parse(`$${varName}`);
|
||||
const variable = <Variable>snippet.children[0];
|
||||
variable.resolve(resolver);
|
||||
@@ -208,10 +208,10 @@ suite('Snippet Variables Resolver', function () {
|
||||
|
||||
test('Add variable to insert value from clipboard to a snippet #40153', function () {
|
||||
|
||||
let readTextResult: string;
|
||||
let readTextResult: string | null | undefined;
|
||||
const clipboardService = new class implements IClipboardService {
|
||||
_serviceBrand: any;
|
||||
readText(): string { return readTextResult; }
|
||||
readText(): any { return readTextResult; }
|
||||
_throw = () => { throw new Error(); };
|
||||
writeText = this._throw;
|
||||
readFindText = this._throw;
|
||||
|
||||
@@ -3,21 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer } from 'vs/base/common/filters';
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { isDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { ISuggestionItem, ensureLowerCaseVariants } from './suggest';
|
||||
import { CompletionItem } from './suggest';
|
||||
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export interface ICompletionItem extends ISuggestionItem {
|
||||
matches?: number[];
|
||||
score?: number;
|
||||
idx?: number;
|
||||
distance?: number;
|
||||
word?: string;
|
||||
}
|
||||
type StrictCompletionItem = Required<CompletionItem>;
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"ICompletionStats" : {
|
||||
@@ -47,7 +41,7 @@ const enum Refilter {
|
||||
|
||||
export class CompletionModel {
|
||||
|
||||
private readonly _items: ICompletionItem[];
|
||||
private readonly _items: CompletionItem[];
|
||||
private readonly _column: number;
|
||||
private readonly _wordDistance: WordDistance;
|
||||
private readonly _options: InternalSuggestOptions;
|
||||
@@ -55,12 +49,12 @@ export class CompletionModel {
|
||||
|
||||
private _lineContext: LineContext;
|
||||
private _refilterKind: Refilter;
|
||||
private _filteredItems: ICompletionItem[];
|
||||
private _filteredItems: StrictCompletionItem[];
|
||||
private _isIncomplete: Set<CompletionItemProvider>;
|
||||
private _stats: ICompletionStats;
|
||||
|
||||
constructor(
|
||||
items: ISuggestionItem[],
|
||||
items: CompletionItem[],
|
||||
column: number,
|
||||
lineContext: LineContext,
|
||||
wordDistance: WordDistance,
|
||||
@@ -105,7 +99,7 @@ export class CompletionModel {
|
||||
}
|
||||
}
|
||||
|
||||
get items(): ICompletionItem[] {
|
||||
get items(): CompletionItem[] {
|
||||
this._ensureCachedState();
|
||||
return this._filteredItems;
|
||||
}
|
||||
@@ -115,10 +109,10 @@ export class CompletionModel {
|
||||
return this._isIncomplete;
|
||||
}
|
||||
|
||||
adopt(except: Set<CompletionItemProvider>): ISuggestionItem[] {
|
||||
let res = new Array<ISuggestionItem>();
|
||||
adopt(except: Set<CompletionItemProvider>): CompletionItem[] {
|
||||
let res = new Array<CompletionItem>();
|
||||
for (let i = 0; i < this._items.length;) {
|
||||
if (!except.has(this._items[i].support)) {
|
||||
if (!except.has(this._items[i].provider)) {
|
||||
res.push(this._items[i]);
|
||||
|
||||
// unordered removed
|
||||
@@ -155,7 +149,7 @@ export class CompletionModel {
|
||||
|
||||
// incrementally filter less
|
||||
const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems;
|
||||
const target: typeof source = [];
|
||||
const target: StrictCompletionItem[] = [];
|
||||
|
||||
// picks a score function based on the number of
|
||||
// items that we have to score/filter and based on the
|
||||
@@ -165,21 +159,17 @@ export class CompletionModel {
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
|
||||
const item = source[i];
|
||||
const { suggestion, container } = item;
|
||||
|
||||
// make sure _labelLow, _filterTextLow, _sortTextLow exist
|
||||
ensureLowerCaseVariants(suggestion);
|
||||
|
||||
// collect those supports that signaled having
|
||||
// an incomplete result
|
||||
if (container.incomplete) {
|
||||
this._isIncomplete.add(item.support);
|
||||
if (item.container.incomplete) {
|
||||
this._isIncomplete.add(item.provider);
|
||||
}
|
||||
|
||||
// 'word' is that remainder of the current line that we
|
||||
// filter and score against. In theory each suggestion uses a
|
||||
// different word, but in practice not - that's why we cache
|
||||
const overwriteBefore = item.position.column - suggestion.range.startColumn;
|
||||
const overwriteBefore = item.position.column - item.completion.range.startColumn;
|
||||
const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column);
|
||||
if (word.length !== wordLen) {
|
||||
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
|
||||
@@ -196,8 +186,7 @@ export class CompletionModel {
|
||||
// the fallback-sort using the initial sort order.
|
||||
// use a score of `-100` because that is out of the
|
||||
// bound of values `fuzzyScore` will return
|
||||
item.score = -100;
|
||||
item.matches = undefined;
|
||||
item.score = FuzzyScore.Default;
|
||||
|
||||
} else {
|
||||
// skip word characters that are whitespace until
|
||||
@@ -215,40 +204,37 @@ export class CompletionModel {
|
||||
if (wordPos >= wordLen) {
|
||||
// the wordPos at which scoring starts is the whole word
|
||||
// and therefore the same rules as not having a word apply
|
||||
item.score = -100;
|
||||
item.matches = [];
|
||||
item.score = FuzzyScore.Default;
|
||||
|
||||
} else if (typeof suggestion.filterText === 'string') {
|
||||
} else if (typeof item.completion.filterText === 'string') {
|
||||
// when there is a `filterText` it must match the `word`.
|
||||
// if it matches we check with the label to compute highlights
|
||||
// and if that doesn't yield a result we have no highlights,
|
||||
// despite having the match
|
||||
let match = scoreFn(word, wordLow, wordPos, suggestion.filterText, suggestion._filterTextLow, 0, false);
|
||||
let match = scoreFn(word, wordLow, wordPos, item.completion.filterText, item.filterTextLow!, 0, false);
|
||||
if (!match) {
|
||||
continue;
|
||||
continue; // NO match
|
||||
}
|
||||
item.score = match[0];
|
||||
item.matches = (fuzzyScore(word, wordLow, 0, suggestion.label, suggestion._labelLow, 0, true) || anyScore(word, suggestion.label))[1];
|
||||
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
|
||||
item.score[0] = match[0]; // use score from filterText
|
||||
|
||||
} else {
|
||||
// by default match `word` against the `label`
|
||||
let match = scoreFn(word, wordLow, wordPos, suggestion.label, suggestion._labelLow, 0, false);
|
||||
if (match) {
|
||||
item.score = match[0];
|
||||
item.matches = match[1];
|
||||
} else {
|
||||
continue;
|
||||
let match = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false);
|
||||
if (!match) {
|
||||
continue; // NO match
|
||||
}
|
||||
item.score = match;
|
||||
}
|
||||
}
|
||||
|
||||
item.idx = i;
|
||||
item.distance = this._wordDistance.distance(item.position, suggestion);
|
||||
target.push(item);
|
||||
item.distance = this._wordDistance.distance(item.position, item.completion);
|
||||
target.push(item as StrictCompletionItem);
|
||||
|
||||
// update stats
|
||||
this._stats.suggestionCount++;
|
||||
switch (suggestion.kind) {
|
||||
switch (item.completion.kind) {
|
||||
case CompletionItemKind.Snippet: this._stats.snippetCount++; break;
|
||||
case CompletionItemKind.Text: this._stats.textCount++; break;
|
||||
}
|
||||
@@ -258,10 +244,10 @@ export class CompletionModel {
|
||||
this._refilterKind = Refilter.Nothing;
|
||||
}
|
||||
|
||||
private static _compareCompletionItems(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.score > b.score) {
|
||||
private static _compareCompletionItems(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.score[0] > b.score[0]) {
|
||||
return -1;
|
||||
} else if (a.score < b.score) {
|
||||
} else if (a.score[0] < b.score[0]) {
|
||||
return 1;
|
||||
} else if (a.distance < b.distance) {
|
||||
return -1;
|
||||
@@ -276,22 +262,22 @@ export class CompletionModel {
|
||||
}
|
||||
}
|
||||
|
||||
private static _compareCompletionItemsSnippetsDown(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
private static _compareCompletionItemsSnippetsDown(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return CompletionModel._compareCompletionItems(a, b);
|
||||
}
|
||||
|
||||
private static _compareCompletionItemsSnippetsUp(a: ICompletionItem, b: ICompletionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
private static _compareCompletionItemsSnippetsUp(a: StrictCompletionItem, b: StrictCompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,14 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: -moz-none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/** Styles for each row in the list element **/
|
||||
|
||||
@@ -68,6 +75,8 @@
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px 2px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents {
|
||||
@@ -173,7 +182,7 @@
|
||||
background-image: url('Misc_16x.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-size: 75%;
|
||||
}
|
||||
|
||||
.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method::before,
|
||||
|
||||
@@ -10,12 +10,13 @@ import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionProviderRegistry, CompletionContext, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
|
||||
export const Context = {
|
||||
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
|
||||
@@ -24,23 +25,77 @@ export const Context = {
|
||||
AcceptSuggestionsOnEnter: new RawContextKey<boolean>('acceptSuggestionOnEnter', true)
|
||||
};
|
||||
|
||||
export interface ISuggestionItem {
|
||||
position: IPosition;
|
||||
suggestion: CompletionItem;
|
||||
container: CompletionList;
|
||||
support: CompletionItemProvider;
|
||||
resolve(token: CancellationToken): Thenable<void>;
|
||||
export class CompletionItem {
|
||||
|
||||
_brand: 'ISuggestionItem';
|
||||
|
||||
readonly resolve: (token: CancellationToken) => Promise<void>;
|
||||
|
||||
// perf
|
||||
readonly labelLow: string;
|
||||
readonly sortTextLow?: string;
|
||||
readonly filterTextLow?: string;
|
||||
|
||||
// sorting, filtering
|
||||
score: FuzzyScore = FuzzyScore.Default;
|
||||
distance: number = 0;
|
||||
idx?: number;
|
||||
word?: string;
|
||||
|
||||
constructor(
|
||||
readonly position: IPosition,
|
||||
readonly completion: modes.CompletionItem,
|
||||
readonly container: modes.CompletionList,
|
||||
readonly provider: modes.CompletionItemProvider,
|
||||
model: ITextModel
|
||||
) {
|
||||
// ensure lower-variants (perf)
|
||||
this.labelLow = completion.label.toLowerCase();
|
||||
this.sortTextLow = completion.sortText && completion.sortText.toLowerCase();
|
||||
this.filterTextLow = completion.filterText && completion.filterText.toLowerCase();
|
||||
|
||||
// create the suggestion resolver
|
||||
const { resolveCompletionItem } = provider;
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
this.resolve = () => Promise.resolve();
|
||||
} else {
|
||||
let cached: Promise<void> | undefined;
|
||||
this.resolve = (token) => {
|
||||
if (!cached) {
|
||||
let isDone = false;
|
||||
cached = Promise.resolve(resolveCompletionItem.call(provider, model, position, completion, token)).then(value => {
|
||||
assign(completion, value);
|
||||
isDone = true;
|
||||
}, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
// the IPC queue will reject the request with the
|
||||
// cancellation error -> reset cached
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
token.onCancellationRequested(() => {
|
||||
if (!isDone) {
|
||||
// cancellation after the request has been
|
||||
// dispatched -> reset cache
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none';
|
||||
|
||||
let _snippetSuggestSupport: CompletionItemProvider;
|
||||
let _snippetSuggestSupport: modes.CompletionItemProvider;
|
||||
|
||||
export function getSnippetSuggestSupport(): CompletionItemProvider {
|
||||
export function getSnippetSuggestSupport(): modes.CompletionItemProvider {
|
||||
return _snippetSuggestSupport;
|
||||
}
|
||||
|
||||
export function setSnippetSuggestSupport(support: CompletionItemProvider): CompletionItemProvider {
|
||||
export function setSnippetSuggestSupport(support: modes.CompletionItemProvider): modes.CompletionItemProvider {
|
||||
const old = _snippetSuggestSupport;
|
||||
_snippetSuggestSupport = support;
|
||||
return old;
|
||||
@@ -50,12 +105,12 @@ export function provideSuggestionItems(
|
||||
model: ITextModel,
|
||||
position: Position,
|
||||
snippetConfig: SnippetConfig = 'bottom',
|
||||
onlyFrom?: CompletionItemProvider[],
|
||||
context?: CompletionContext,
|
||||
onlyFrom?: modes.CompletionItemProvider[],
|
||||
context?: modes.CompletionContext,
|
||||
token: CancellationToken = CancellationToken.None
|
||||
): Promise<ISuggestionItem[]> {
|
||||
): Promise<CompletionItem[]> {
|
||||
|
||||
const allSuggestions: ISuggestionItem[] = [];
|
||||
const allSuggestions: CompletionItem[] = [];
|
||||
const acceptSuggestion = createSuggesionFilter(snippetConfig);
|
||||
|
||||
const wordUntil = model.getWordUntilPosition(position);
|
||||
@@ -64,27 +119,27 @@ export function provideSuggestionItems(
|
||||
position = position.clone();
|
||||
|
||||
// get provider groups, always add snippet suggestion provider
|
||||
const supports = CompletionProviderRegistry.orderedGroups(model);
|
||||
const supports = modes.CompletionProviderRegistry.orderedGroups(model);
|
||||
|
||||
// add snippets provider unless turned off
|
||||
if (snippetConfig !== 'none' && _snippetSuggestSupport) {
|
||||
supports.unshift([_snippetSuggestSupport]);
|
||||
}
|
||||
|
||||
const suggestConext = context || { triggerKind: CompletionTriggerKind.Invoke };
|
||||
const suggestConext = context || { triggerKind: modes.CompletionTriggerKind.Invoke };
|
||||
|
||||
// add suggestions from contributed providers - providers are ordered in groups of
|
||||
// equal score and once a group produces a result the process stops
|
||||
let hasResult = false;
|
||||
const factory = supports.map(supports => () => {
|
||||
// for each support in the group ask for suggestions
|
||||
return Promise.all(supports.map(support => {
|
||||
return Promise.all(supports.map(provider => {
|
||||
|
||||
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(support) < 0) {
|
||||
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(provider) < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Promise.resolve(support.provideCompletionItems(model, position, suggestConext, token)).then(container => {
|
||||
return Promise.resolve(provider.provideCompletionItems(model, position, suggestConext, token)).then(container => {
|
||||
|
||||
const len = allSuggestions.length;
|
||||
|
||||
@@ -97,21 +152,12 @@ export function provideSuggestionItems(
|
||||
suggestion.range = defaultRange;
|
||||
}
|
||||
|
||||
// fill in lower-case text
|
||||
ensureLowerCaseVariants(suggestion);
|
||||
|
||||
allSuggestions.push({
|
||||
position,
|
||||
container,
|
||||
suggestion,
|
||||
support,
|
||||
resolve: createSuggestionResolver(support, suggestion, model, position)
|
||||
});
|
||||
allSuggestions.push(new CompletionItem(position, suggestion, container, provider, model));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (len !== allSuggestions.length && support !== _snippetSuggestSupport) {
|
||||
if (len !== allSuggestions.length && provider !== _snippetSuggestSupport) {
|
||||
hasResult = true;
|
||||
}
|
||||
|
||||
@@ -139,101 +185,55 @@ export function provideSuggestionItems(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function ensureLowerCaseVariants(suggestion: CompletionItem) {
|
||||
if (!suggestion._labelLow) {
|
||||
suggestion._labelLow = suggestion.label.toLowerCase();
|
||||
}
|
||||
if (suggestion.sortText && !suggestion._sortTextLow) {
|
||||
suggestion._sortTextLow = suggestion.sortText.toLowerCase();
|
||||
}
|
||||
if (suggestion.filterText && !suggestion._filterTextLow) {
|
||||
suggestion._filterTextLow = suggestion.filterText.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function createSuggestionResolver(provider: CompletionItemProvider, suggestion: CompletionItem, model: ITextModel, position: Position): (token: CancellationToken) => Promise<void> {
|
||||
|
||||
const { resolveCompletionItem } = provider;
|
||||
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
return () => Promise.resolve();
|
||||
}
|
||||
|
||||
let cached: Promise<void> | undefined;
|
||||
return (token) => {
|
||||
if (!cached) {
|
||||
let isDone = false;
|
||||
cached = Promise.resolve(provider.resolveCompletionItem!(model, position, suggestion, token)).then(value => {
|
||||
assign(suggestion, value);
|
||||
isDone = true;
|
||||
}, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
// the IPC queue will reject the request with the
|
||||
// cancellation error -> reset cached
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
token.onCancellationRequested(() => {
|
||||
if (!isDone) {
|
||||
// cancellation after the request has been
|
||||
// dispatched -> reset cache
|
||||
cached = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return cached;
|
||||
};
|
||||
}
|
||||
|
||||
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: CompletionItem) => boolean {
|
||||
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: modes.CompletionItem) => boolean {
|
||||
if (snippetConfig === 'none') {
|
||||
return suggestion => suggestion.kind !== CompletionItemKind.Snippet;
|
||||
return suggestion => suggestion.kind !== modes.CompletionItemKind.Snippet;
|
||||
} else {
|
||||
return () => true;
|
||||
}
|
||||
}
|
||||
function defaultComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
function defaultComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
// check with 'sortText'
|
||||
if (a.suggestion._sortTextLow && b.suggestion._sortTextLow) {
|
||||
if (a.suggestion._sortTextLow < b.suggestion._sortTextLow) {
|
||||
if (a.sortTextLow && b.sortTextLow) {
|
||||
if (a.sortTextLow < b.sortTextLow) {
|
||||
return -1;
|
||||
} else if (a.suggestion._sortTextLow > b.suggestion._sortTextLow) {
|
||||
} else if (a.sortTextLow > b.sortTextLow) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// check with 'label'
|
||||
if (a.suggestion.label < b.suggestion.label) {
|
||||
if (a.completion.label < b.completion.label) {
|
||||
return -1;
|
||||
} else if (a.suggestion.label > b.suggestion.label) {
|
||||
} else if (a.completion.label > b.completion.label) {
|
||||
return 1;
|
||||
}
|
||||
// check with 'type'
|
||||
return a.suggestion.kind - b.suggestion.kind;
|
||||
return a.completion.kind - b.completion.kind;
|
||||
}
|
||||
|
||||
function snippetUpComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
function snippetUpComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return defaultComparator(a, b);
|
||||
}
|
||||
|
||||
function snippetDownComparator(a: ISuggestionItem, b: ISuggestionItem): number {
|
||||
if (a.suggestion.kind !== b.suggestion.kind) {
|
||||
if (a.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
function snippetDownComparator(a: CompletionItem, b: CompletionItem): number {
|
||||
if (a.completion.kind !== b.completion.kind) {
|
||||
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return 1;
|
||||
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
|
||||
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return defaultComparator(a, b);
|
||||
}
|
||||
|
||||
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISuggestionItem, b: ISuggestionItem) => number {
|
||||
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: CompletionItem, b: CompletionItem) => number {
|
||||
if (snippetConfig === 'top') {
|
||||
return snippetUpComparator;
|
||||
} else if (snippetConfig === 'bottom') {
|
||||
@@ -245,12 +245,12 @@ export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISugg
|
||||
|
||||
registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
|
||||
|
||||
const result: CompletionList = {
|
||||
const result: modes.CompletionList = {
|
||||
incomplete: false,
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
let resolving: Thenable<any>[] = [];
|
||||
let resolving: Promise<any>[] = [];
|
||||
let maxItemsToResolve = args['maxItemsToResolve'] || 0;
|
||||
|
||||
return provideSuggestionItems(model, position).then(items => {
|
||||
@@ -259,7 +259,7 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
|
||||
resolving.push(item.resolve(CancellationToken.None));
|
||||
}
|
||||
result.incomplete = result.incomplete || item.container.incomplete;
|
||||
result.suggestions.push(item.suggestion);
|
||||
result.suggestions.push(item.completion);
|
||||
}
|
||||
}).then(() => {
|
||||
return Promise.all(resolving);
|
||||
@@ -269,15 +269,15 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
|
||||
});
|
||||
|
||||
interface SuggestController extends IEditorContribution {
|
||||
triggerSuggest(onlyFrom?: CompletionItemProvider[]): void;
|
||||
triggerSuggest(onlyFrom?: modes.CompletionItemProvider[]): void;
|
||||
}
|
||||
|
||||
|
||||
let _provider = new class implements CompletionItemProvider {
|
||||
let _provider = new class implements modes.CompletionItemProvider {
|
||||
|
||||
onlyOnceSuggestions: CompletionItem[] = [];
|
||||
onlyOnceSuggestions: modes.CompletionItem[] = [];
|
||||
|
||||
provideCompletionItems(): CompletionList {
|
||||
provideCompletionItems(): modes.CompletionList {
|
||||
let suggestions = this.onlyOnceSuggestions.slice(0);
|
||||
let result = { suggestions };
|
||||
this.onlyOnceSuggestions.length = 0;
|
||||
@@ -285,9 +285,9 @@ let _provider = new class implements CompletionItemProvider {
|
||||
}
|
||||
};
|
||||
|
||||
CompletionProviderRegistry.register('*', _provider);
|
||||
modes.CompletionProviderRegistry.register('*', _provider);
|
||||
|
||||
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: CompletionItem[]) {
|
||||
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) {
|
||||
setTimeout(() => {
|
||||
_provider.onlyOnceSuggestions.push(...suggestions);
|
||||
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest([_provider]);
|
||||
|
||||
@@ -16,8 +16,8 @@ export class SuggestAlternatives {
|
||||
private readonly _ckOtherSuggestions: IContextKey<boolean>;
|
||||
|
||||
private _index: number;
|
||||
private _model: CompletionModel;
|
||||
private _acceptNext: (selected: ISelectedSuggestion) => any;
|
||||
private _model: CompletionModel | undefined;
|
||||
private _acceptNext: ((selected: ISelectedSuggestion) => any) | undefined;
|
||||
private _listener: IDisposable;
|
||||
private _ignore: boolean;
|
||||
|
||||
@@ -73,7 +73,7 @@ export class SuggestAlternatives {
|
||||
if (newIndex === index) {
|
||||
break;
|
||||
}
|
||||
if (!model.items[newIndex].suggestion.additionalTextEdits) {
|
||||
if (!model.items[newIndex].completion.additionalTextEdits) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class SuggestAlternatives {
|
||||
try {
|
||||
this._ignore = true;
|
||||
this._index = SuggestAlternatives._moveIndex(fwd, this._model, this._index);
|
||||
this._acceptNext({ index: this._index, item: this._model.items[this._index], model: this._model });
|
||||
this._acceptNext!({ index: this._index, item: this._model.items[this._index], model: this._model });
|
||||
} finally {
|
||||
this._ignore = false;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user