mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 12:00:24 -04:00
Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
This commit is contained in:
@@ -77,11 +77,11 @@ class ExecCommandCutAction extends ExecCommandAction {
|
||||
alias: 'Cut',
|
||||
precondition: EditorContextKeys.writable,
|
||||
kbOpts: kbOpts,
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: CLIPBOARD_CONTEXT_MENU_GROUP,
|
||||
order: 1
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '2_ccp',
|
||||
title: nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"),
|
||||
@@ -126,11 +126,11 @@ class ExecCommandCopyAction extends ExecCommandAction {
|
||||
alias: 'Copy',
|
||||
precondition: undefined,
|
||||
kbOpts: kbOpts,
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: CLIPBOARD_CONTEXT_MENU_GROUP,
|
||||
order: 2
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '2_ccp',
|
||||
title: nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"),
|
||||
@@ -175,11 +175,11 @@ class ExecCommandPasteAction extends ExecCommandAction {
|
||||
alias: 'Paste',
|
||||
precondition: EditorContextKeys.writable,
|
||||
kbOpts: kbOpts,
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: CLIPBOARD_CONTEXT_MENU_GROUP,
|
||||
order: 3
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '2_ccp',
|
||||
title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"),
|
||||
|
||||
@@ -13,12 +13,19 @@ 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, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger';
|
||||
import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types';
|
||||
import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const codeActionCommandId = 'editor.action.codeAction';
|
||||
export const refactorCommandId = 'editor.action.refactor';
|
||||
export const sourceActionCommandId = 'editor.action.sourceAction';
|
||||
export const organizeImportsCommandId = 'editor.action.organizeImports';
|
||||
export const fixAllCommandId = 'editor.action.fixAll';
|
||||
|
||||
export interface CodeActionSet extends IDisposable {
|
||||
readonly actions: readonly CodeAction[];
|
||||
readonly validActions: readonly CodeAction[];
|
||||
readonly allActions: readonly CodeAction[];
|
||||
readonly hasAutoFix: boolean;
|
||||
}
|
||||
|
||||
@@ -38,16 +45,18 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet {
|
||||
}
|
||||
}
|
||||
|
||||
public readonly actions: readonly CodeAction[];
|
||||
public readonly validActions: readonly CodeAction[];
|
||||
public readonly allActions: readonly CodeAction[];
|
||||
|
||||
public constructor(actions: readonly CodeAction[], disposables: DisposableStore) {
|
||||
super();
|
||||
this._register(disposables);
|
||||
this.actions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator);
|
||||
this.allActions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator);
|
||||
this.validActions = this.allActions.filter(action => !action.disabled);
|
||||
}
|
||||
|
||||
public get hasAutoFix() {
|
||||
return this.actions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
|
||||
return this.validActions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +69,7 @@ export function getCodeActions(
|
||||
const filter = trigger.filter || {};
|
||||
|
||||
const codeActionContext: CodeActionContext = {
|
||||
only: filter.kind ? filter.kind.value : undefined,
|
||||
only: filter.include?.value,
|
||||
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
};
|
||||
|
||||
@@ -68,21 +77,21 @@ export function getCodeActions(
|
||||
const providers = getCodeActionProviders(model, filter);
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
const promises = providers.map(provider => {
|
||||
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => {
|
||||
const promises = providers.map(async provider => {
|
||||
try {
|
||||
const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token);
|
||||
if (cts.token.isCancellationRequested || !providedCodeActions) {
|
||||
return [];
|
||||
}
|
||||
disposables.add(providedCodeActions);
|
||||
return providedCodeActions.actions.filter(action => action && filtersAction(filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
} catch (err) {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const listener = CodeActionProviderRegistry.onDidChange(() => {
|
||||
@@ -140,9 +149,9 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor,
|
||||
const codeActionSet = await getCodeActions(
|
||||
model,
|
||||
validatedRangeOrSelection,
|
||||
{ type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
|
||||
{ type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
|
||||
CancellationToken.None);
|
||||
|
||||
setTimeout(() => codeActionSet.dispose(), 100);
|
||||
return codeActionSet.actions;
|
||||
return codeActionSet.validActions;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -15,7 +16,7 @@ import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi';
|
||||
import { MessageController } from 'vs/editor/contrib/message/messageController';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -29,7 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
|
||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './types';
|
||||
|
||||
function contextKeyForSupportedActions(kind: CodeActionKind) {
|
||||
return ContextKeyExpr.regex(
|
||||
@@ -37,6 +38,33 @@ function contextKeyForSupportedActions(kind: CodeActionKind) {
|
||||
new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b'));
|
||||
}
|
||||
|
||||
const argsSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
required: ['kind'],
|
||||
defaultSnippets: [{ body: { kind: '' } }],
|
||||
properties: {
|
||||
'kind': {
|
||||
type: 'string',
|
||||
description: nls.localize('args.schema.kind', "Kind of the code action to run."),
|
||||
},
|
||||
'apply': {
|
||||
type: 'string',
|
||||
description: nls.localize('args.schema.apply', "Controls when the returned actions are applied."),
|
||||
default: CodeActionAutoApply.IfSingle,
|
||||
enum: [CodeActionAutoApply.First, CodeActionAutoApply.IfSingle, CodeActionAutoApply.Never],
|
||||
enumDescriptions: [
|
||||
nls.localize('args.schema.apply.first', "Always apply the first returned code action."),
|
||||
nls.localize('args.schema.apply.ifSingle', "Apply the first returned code action if it is the only one."),
|
||||
nls.localize('args.schema.apply.never', "Do not apply the returned code actions."),
|
||||
]
|
||||
},
|
||||
'preferred': {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: nls.localize('args.schema.preferred', "Controls if only preferred code actions should be returned."),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class QuickFixController extends Disposable implements IEditorContribution {
|
||||
|
||||
@@ -78,7 +106,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
}
|
||||
}
|
||||
}
|
||||
}, contextMenuService, keybindingService))
|
||||
}, this._instantiationService))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,7 +115,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
|
||||
}
|
||||
|
||||
public showCodeActions(actions: CodeActionSet, at: IAnchor | IPosition) {
|
||||
return this._ui.getValue().showCodeActionList(actions, at);
|
||||
return this._ui.getValue().showCodeActionList(actions, at, { includeDisabledActions: false });
|
||||
}
|
||||
|
||||
public manualTriggerAtCurrentPosition(
|
||||
@@ -185,85 +213,34 @@ export class QuickFixAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(defaults.kind, defaults.apply, false);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg, defaults.kind),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply),
|
||||
CodeActionCommandArgs.getPreferredUser(arg));
|
||||
}
|
||||
|
||||
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': return CodeActionAutoApply.IfSingle;
|
||||
default: return defaultAutoApply;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any, defaultKind: CodeActionKind) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: 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 preferred: boolean,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class CodeActionCommand extends EditorCommand {
|
||||
|
||||
static readonly Id = 'editor.action.codeAction';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: CodeActionCommand.Id,
|
||||
id: codeActionCommandId,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
|
||||
description: {
|
||||
description: `Trigger a code action`,
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
'type': 'object',
|
||||
'required': ['kind'],
|
||||
'properties': {
|
||||
'kind': {
|
||||
'type': 'string'
|
||||
},
|
||||
'apply': {
|
||||
'type': 'string',
|
||||
'default': 'ifSingle',
|
||||
'enum': ['first', 'ifSingle', 'never']
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
description: 'Trigger a code action',
|
||||
args: [{ name: 'args', schema: argsSchema, }]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any) {
|
||||
const args = CodeActionCommandArgs.fromUser(userArgs, {
|
||||
kind: CodeActionKind.Empty,
|
||||
apply: CodeActionAutoApply.IfSingle,
|
||||
});
|
||||
return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
typeof userArgs?.kind === 'string'
|
||||
? args.preferred
|
||||
? nls.localize('editor.action.codeAction.noneMessage.preferred.kind', "No preferred code actions for '{0}' available", userArgs.kind)
|
||||
: nls.localize('editor.action.codeAction.noneMessage.kind', "No code actions for '{0}' available", userArgs.kind)
|
||||
: args.preferred
|
||||
? nls.localize('editor.action.codeAction.noneMessage.preferred', "No preferred code actions available")
|
||||
: nls.localize('editor.action.codeAction.noneMessage', "No code actions available"),
|
||||
{
|
||||
kind: args.kind,
|
||||
include: args.kind,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
@@ -274,11 +251,9 @@ export class CodeActionCommand extends EditorCommand {
|
||||
|
||||
export class RefactorAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.refactor';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: RefactorAction.Id,
|
||||
id: refactorCommandId,
|
||||
label: nls.localize('refactor.label', "Refactor..."),
|
||||
alias: 'Refactor...',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
|
||||
@@ -290,7 +265,7 @@ export class RefactorAction extends EditorAction {
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: '1_modification',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(
|
||||
@@ -299,53 +274,41 @@ export class RefactorAction extends EditorAction {
|
||||
},
|
||||
description: {
|
||||
description: 'Refactor...',
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'kind': {
|
||||
'type': 'string'
|
||||
},
|
||||
'apply': {
|
||||
'type': 'string',
|
||||
'default': 'never',
|
||||
'enum': ['first', 'ifSingle', 'never']
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
args: [{ name: 'args', schema: argsSchema }]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArgs, {
|
||||
kind: CodeActionKind.Refactor,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
|
||||
typeof userArgs?.kind === 'string'
|
||||
? args.preferred
|
||||
? nls.localize('editor.action.refactor.noneMessage.preferred.kind', "No preferred refactorings for '{0}' available", userArgs.kind)
|
||||
: nls.localize('editor.action.refactor.noneMessage.kind', "No refactorings for '{0}' available", userArgs.kind)
|
||||
: args.preferred
|
||||
? nls.localize('editor.action.refactor.noneMessage.preferred', "No preferred refactorings available")
|
||||
: nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
|
||||
{
|
||||
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
include: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.None,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
args.apply);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SourceAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.sourceAction';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: SourceAction.Id,
|
||||
id: sourceActionCommandId,
|
||||
label: nls.localize('source.label', "Source Action..."),
|
||||
alias: 'Source Action...',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: '1_modification',
|
||||
order: 2.1,
|
||||
when: ContextKeyExpr.and(
|
||||
@@ -354,35 +317,26 @@ export class SourceAction extends EditorAction {
|
||||
},
|
||||
description: {
|
||||
description: 'Source Action...',
|
||||
args: [{
|
||||
name: 'args',
|
||||
schema: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'kind': {
|
||||
'type': 'string'
|
||||
},
|
||||
'apply': {
|
||||
'type': 'string',
|
||||
'default': 'never',
|
||||
'enum': ['first', 'ifSingle', 'never']
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
args: [{ name: 'args', schema: argsSchema }]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArg, {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArgs: any): void {
|
||||
const args = CodeActionCommandArgs.fromUser(userArgs, {
|
||||
kind: CodeActionKind.Source,
|
||||
apply: CodeActionAutoApply.Never
|
||||
});
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.source.noneMessage', "No source actions available"),
|
||||
typeof userArgs?.kind === 'string'
|
||||
? args.preferred
|
||||
? nls.localize('editor.action.source.noneMessage.preferred.kind', "No preferred source actions for '{0}' available", userArgs.kind)
|
||||
: nls.localize('editor.action.source.noneMessage.kind', "No source actions for '{0}' available", userArgs.kind)
|
||||
: args.preferred
|
||||
? nls.localize('editor.action.source.noneMessage.preferred', "No preferred source actions available")
|
||||
: nls.localize('editor.action.source.noneMessage', "No source actions available"),
|
||||
{
|
||||
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty,
|
||||
include: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.None,
|
||||
includeSourceActions: true,
|
||||
onlyIncludePreferredActions: args.preferred,
|
||||
},
|
||||
@@ -392,11 +346,9 @@ export class SourceAction extends EditorAction {
|
||||
|
||||
export class OrganizeImportsAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.organizeImports';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: OrganizeImportsAction.Id,
|
||||
id: organizeImportsCommandId,
|
||||
label: nls.localize('organizeImports.label', "Organize Imports"),
|
||||
alias: 'Organize Imports',
|
||||
precondition: ContextKeyExpr.and(
|
||||
@@ -406,25 +358,23 @@ export class OrganizeImportsAction extends EditorAction {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_O,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.organize.noneMessage', "No organize imports action available"),
|
||||
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
|
||||
{ include: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
|
||||
export class FixAllAction extends EditorAction {
|
||||
|
||||
static readonly Id = 'editor.action.fixAll';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: FixAllAction.Id,
|
||||
id: fixAllCommandId,
|
||||
label: nls.localize('fixAll.label', "Fix All"),
|
||||
alias: 'Fix All',
|
||||
precondition: ContextKeyExpr.and(
|
||||
@@ -436,7 +386,7 @@ export class FixAllAction extends EditorAction {
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('fixAll.noneMessage', "No fix all action available"),
|
||||
{ kind: CodeActionKind.SourceFixAll, includeSourceActions: true },
|
||||
{ include: CodeActionKind.SourceFixAll, includeSourceActions: true },
|
||||
CodeActionAutoApply.IfSingle);
|
||||
}
|
||||
}
|
||||
@@ -468,7 +418,7 @@ export class AutoFixAction extends EditorAction {
|
||||
return triggerCodeActionsForEditorSelection(editor,
|
||||
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
|
||||
{
|
||||
kind: CodeActionKind.QuickFix,
|
||||
include: CodeActionKind.QuickFix,
|
||||
onlyIncludePreferredActions: true
|
||||
},
|
||||
CodeActionAutoApply.IfSingle);
|
||||
|
||||
195
src/vs/editor/contrib/codeAction/codeActionMenu.ts
Normal file
195
src/vs/editor/contrib/codeAction/codeActionMenu.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
|
||||
interface CodeActionWidgetDelegate {
|
||||
onSelectCodeAction: (action: CodeAction) => Promise<any>;
|
||||
}
|
||||
|
||||
interface ResolveCodeActionKeybinding {
|
||||
readonly kind: CodeActionKind;
|
||||
readonly preferred: boolean;
|
||||
readonly resolvedKeybinding: ResolvedKeybinding;
|
||||
}
|
||||
|
||||
class CodeActionAction extends Action {
|
||||
constructor(
|
||||
public readonly action: CodeAction,
|
||||
callback: () => Promise<void>,
|
||||
) {
|
||||
super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CodeActionShowOptions {
|
||||
readonly includeDisabledActions: boolean;
|
||||
}
|
||||
|
||||
export class CodeActionMenu extends Disposable {
|
||||
|
||||
private _visible: boolean = false;
|
||||
private readonly _showingActions = this._register(new MutableDisposable<CodeActionSet>());
|
||||
|
||||
private readonly _keybindingResolver: CodeActionKeybindingResolver;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _delegate: CodeActionWidgetDelegate,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._keybindingResolver = new CodeActionKeybindingResolver({
|
||||
getKeybindings: () => keybindingService.getKeybindings()
|
||||
});
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
public async show(codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise<void> {
|
||||
const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions;
|
||||
if (!actionsToShow.length) {
|
||||
this._visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
this._visible = false;
|
||||
throw canceled();
|
||||
}
|
||||
|
||||
this._visible = true;
|
||||
this._showingActions.value = codeActions;
|
||||
|
||||
const menuActions = actionsToShow.map(action =>
|
||||
new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action)));
|
||||
|
||||
const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 };
|
||||
const resolver = this._keybindingResolver.getResolver();
|
||||
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => menuActions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true,
|
||||
getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private _toCoords(position: IPosition): { x: number, y: number } {
|
||||
if (!this._editor.hasModel()) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
this._editor.revealPosition(position, ScrollType.Immediate);
|
||||
this._editor.render();
|
||||
|
||||
// Translate to absolute editor position
|
||||
const cursorCoords = this._editor.getScrolledVisiblePosition(position);
|
||||
const editorCoords = getDomNodePagePosition(this._editor.getDomNode());
|
||||
const x = editorCoords.left + cursorCoords.left;
|
||||
const y = editorCoords.top + cursorCoords.top + cursorCoords.height;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeActionKeybindingResolver {
|
||||
private static readonly codeActionCommands: readonly string[] = [
|
||||
refactorCommandId,
|
||||
codeActionCommandId,
|
||||
sourceActionCommandId,
|
||||
organizeImportsCommandId,
|
||||
fixAllCommandId
|
||||
];
|
||||
|
||||
constructor(
|
||||
private readonly _keybindingProvider: {
|
||||
getKeybindings(): readonly ResolvedKeybindingItem[],
|
||||
},
|
||||
) { }
|
||||
|
||||
public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined {
|
||||
// Lazy since we may not actually ever read the value
|
||||
const allCodeActionBindings = new Lazy<readonly ResolveCodeActionKeybinding[]>(() =>
|
||||
this._keybindingProvider.getKeybindings()
|
||||
.filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0)
|
||||
.filter(item => item.resolvedKeybinding)
|
||||
.map((item): ResolveCodeActionKeybinding => {
|
||||
// Special case these commands since they come built-in with VS Code and don't use 'commandArgs'
|
||||
let commandArgs = item.commandArgs;
|
||||
if (item.command === organizeImportsCommandId) {
|
||||
commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value };
|
||||
} else if (item.command === fixAllCommandId) {
|
||||
commandArgs = { kind: CodeActionKind.SourceFixAll.value };
|
||||
}
|
||||
|
||||
return {
|
||||
resolvedKeybinding: item.resolvedKeybinding!,
|
||||
...CodeActionCommandArgs.fromUser(commandArgs, {
|
||||
kind: CodeActionKind.None,
|
||||
apply: CodeActionAutoApply.Never
|
||||
})
|
||||
};
|
||||
}));
|
||||
|
||||
return (action) => {
|
||||
if (action.kind) {
|
||||
const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue());
|
||||
return binding?.resolvedKeybinding;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
private bestKeybindingForCodeAction(
|
||||
action: CodeAction,
|
||||
candidates: readonly ResolveCodeActionKeybinding[],
|
||||
): ResolveCodeActionKeybinding | undefined {
|
||||
if (!action.kind) {
|
||||
return undefined;
|
||||
}
|
||||
const kind = new CodeActionKind(action.kind);
|
||||
|
||||
return candidates
|
||||
.filter(candidate => candidate.kind.contains(kind))
|
||||
.filter(candidate => {
|
||||
if (candidate.preferred) {
|
||||
// If the candidate keybinding only applies to preferred actions, the this action must also be preferred
|
||||
return action.isPreferred;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.reduceRight((currentBest, candidate) => {
|
||||
if (!currentBest) {
|
||||
return candidate;
|
||||
}
|
||||
// Select the more specific binding
|
||||
return currentBest.kind.contains(candidate.kind) ? candidate : currentBest;
|
||||
}, undefined as ResolveCodeActionKeybinding | undefined);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { getCodeActions, CodeActionSet } from './codeAction';
|
||||
import { CodeActionTrigger } from './codeActionTrigger';
|
||||
import { CodeActionTrigger } from './types';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
@@ -73,10 +73,12 @@ class CodeActionOracle extends Disposable {
|
||||
return undefined;
|
||||
}
|
||||
for (const marker of this._markerService.read({ resource: model.uri })) {
|
||||
if (Range.intersectRanges(marker, selection)) {
|
||||
return Range.lift(marker);
|
||||
const markerRange = model.validateRange(marker);
|
||||
if (Range.intersectRanges(markerRange, selection)) {
|
||||
return Range.lift(markerRange);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -140,7 +142,7 @@ export namespace CodeActionsState {
|
||||
Triggered,
|
||||
}
|
||||
|
||||
export const Empty = new class { readonly type = Type.Empty; };
|
||||
export const Empty = { type: Type.Empty } as const;
|
||||
|
||||
export class Triggered {
|
||||
readonly type = Type.Triggered;
|
||||
|
||||
@@ -1,98 +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 { startsWith } from 'vs/base/common/strings';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly QuickFix = new CodeActionKind('quickfix');
|
||||
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 equals(other: CodeActionKind): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.equals(other) || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
|
||||
public intersects(other: CodeActionKind): boolean {
|
||||
return this.contains(other) || other.contains(this);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CodeActionAutoApply {
|
||||
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 {
|
||||
readonly type: 'auto' | 'manual';
|
||||
readonly filter?: CodeActionFilter;
|
||||
readonly autoApply?: CodeActionAutoApply;
|
||||
readonly context?: {
|
||||
readonly notAvailableMessage: string;
|
||||
readonly position: Position;
|
||||
};
|
||||
}
|
||||
@@ -3,25 +3,25 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { MessageController } from 'vs/editor/contrib/message/messageController';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { CodeActionsState } from './codeActionModel';
|
||||
import { CodeActionAutoApply } from './codeActionTrigger';
|
||||
import { CodeActionWidget } from './codeActionWidget';
|
||||
import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu';
|
||||
import { LightBulbWidget } from './lightBulbWidget';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { CodeActionAutoApply, CodeActionTrigger } from './types';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class CodeActionUi extends Disposable {
|
||||
|
||||
private readonly _codeActionWidget: Lazy<CodeActionWidget>;
|
||||
private readonly _codeActionWidget: Lazy<CodeActionMenu>;
|
||||
private readonly _lightBulbWidget: Lazy<LightBulbWidget>;
|
||||
private readonly _activeCodeActions = this._register(new MutableDisposable<CodeActionSet>());
|
||||
|
||||
@@ -30,15 +30,14 @@ export class CodeActionUi extends Disposable {
|
||||
quickFixActionId: string,
|
||||
preferredFixActionId: string,
|
||||
private readonly delegate: {
|
||||
applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => void
|
||||
applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => Promise<void>
|
||||
},
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._codeActionWidget = new Lazy(() => {
|
||||
return this._register(new CodeActionWidget(this._editor, contextMenuService, {
|
||||
return this._register(instantiationService.createInstance(CodeActionMenu, this._editor, {
|
||||
onSelectCodeAction: async (action) => {
|
||||
this.delegate.applyCodeAction(action, /* retrigger */ true);
|
||||
}
|
||||
@@ -46,17 +45,15 @@ export class CodeActionUi extends Disposable {
|
||||
});
|
||||
|
||||
this._lightBulbWidget = new Lazy(() => {
|
||||
const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService));
|
||||
this._register(widget.onClick(this._handleLightBulbSelect, this));
|
||||
const widget = this._register(instantiationService.createInstance(LightBulbWidget, this._editor, quickFixActionId, preferredFixActionId));
|
||||
this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false })));
|
||||
return widget;
|
||||
});
|
||||
}
|
||||
|
||||
public async update(newState: CodeActionsState.State): Promise<void> {
|
||||
if (newState.type !== CodeActionsState.Type.Triggered) {
|
||||
if (this._lightBulbWidget.hasValue()) {
|
||||
this._lightBulbWidget.getValue().hide();
|
||||
}
|
||||
this._lightBulbWidget.rawValue?.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,29 +67,43 @@ export class CodeActionUi extends Disposable {
|
||||
|
||||
this._lightBulbWidget.getValue().update(actions, newState.position);
|
||||
|
||||
if (!actions.actions.length && newState.trigger.context) {
|
||||
MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position);
|
||||
this._activeCodeActions.value = actions;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newState.trigger.type === 'manual') {
|
||||
if (newState.trigger.filter && newState.trigger.filter.kind) {
|
||||
// Triggered for specific scope
|
||||
if (actions.actions.length > 0) {
|
||||
// Apply if we only have one action or requested autoApply
|
||||
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && actions.actions.length === 1)) {
|
||||
try {
|
||||
await this.delegate.applyCodeAction(actions.actions[0], false);
|
||||
} finally {
|
||||
actions.dispose();
|
||||
}
|
||||
if (newState.trigger.filter?.include) { // Triggered for specific scope
|
||||
// Check to see if we want to auto apply.
|
||||
|
||||
const validActionToApply = this.tryGetValidActionToApply(newState.trigger, actions);
|
||||
if (validActionToApply) {
|
||||
try {
|
||||
await this.delegate.applyCodeAction(validActionToApply, false);
|
||||
} finally {
|
||||
actions.dispose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if there is an action that we would have applied were it not invalid
|
||||
if (newState.trigger.context) {
|
||||
const invalidAction = this.getInvalidActionThatWouldHaveBeenApplied(newState.trigger, actions);
|
||||
if (invalidAction && invalidAction.disabled) {
|
||||
MessageController.get(this._editor).showMessage(invalidAction.disabled, newState.trigger.context.position);
|
||||
actions.dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const includeDisabledActions = !!newState.trigger.filter?.include;
|
||||
if (newState.trigger.context) {
|
||||
if (!actions.allActions.length || !includeDisabledActions && !actions.validActions.length) {
|
||||
MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position);
|
||||
this._activeCodeActions.value = actions;
|
||||
actions.dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._activeCodeActions.value = actions;
|
||||
this._codeActionWidget.getValue().show(actions, newState.position);
|
||||
this._codeActionWidget.getValue().show(actions, newState.position, { includeDisabledActions });
|
||||
} else {
|
||||
// auto magically triggered
|
||||
if (this._codeActionWidget.getValue().isVisible) {
|
||||
@@ -104,11 +115,35 @@ export class CodeActionUi extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public async showCodeActionList(actions: CodeActionSet, at?: IAnchor | IPosition): Promise<void> {
|
||||
this._codeActionWidget.getValue().show(actions, at);
|
||||
private getInvalidActionThatWouldHaveBeenApplied(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined {
|
||||
if (!actions.allActions.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length === 0)
|
||||
|| (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.allActions.length === 1)
|
||||
) {
|
||||
return find(actions.allActions, action => action.disabled);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void {
|
||||
this._codeActionWidget.getValue().show(e.actions, e);
|
||||
private tryGetValidActionToApply(trigger: CodeActionTrigger, actions: CodeActionSet): CodeAction | undefined {
|
||||
if (!actions.validActions.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ((trigger.autoApply === CodeActionAutoApply.First && actions.validActions.length > 0)
|
||||
|| (trigger.autoApply === CodeActionAutoApply.IfSingle && actions.validActions.length === 1)
|
||||
) {
|
||||
return actions.validActions[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise<void> {
|
||||
this._codeActionWidget.getValue().show(actions, at, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +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 { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
interface CodeActionWidgetDelegate {
|
||||
onSelectCodeAction: (action: CodeAction) => Promise<any>;
|
||||
}
|
||||
|
||||
export class CodeActionWidget extends Disposable {
|
||||
|
||||
private _visible: boolean = false;
|
||||
private readonly _showingActions = this._register(new MutableDisposable<CodeActionSet>());
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _contextMenuService: IContextMenuService,
|
||||
private readonly _delegate: CodeActionWidgetDelegate,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise<void> {
|
||||
if (!codeActions.actions.length) {
|
||||
this._visible = false;
|
||||
return;
|
||||
}
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
this._visible = false;
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
|
||||
this._visible = true;
|
||||
const actions = codeActions.actions.map(action => this.codeActionToAction(action));
|
||||
|
||||
this._showingActions.value = codeActions;
|
||||
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.title;
|
||||
return new Action(id, title, undefined, true, () => this._delegate.onSelectCodeAction(action));
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
private _toCoords(position: IPosition): { x: number, y: number } {
|
||||
if (!this._editor.hasModel()) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
this._editor.revealPosition(position, ScrollType.Immediate);
|
||||
this._editor.render();
|
||||
|
||||
// Translate to absolute editor position
|
||||
const cursorCoords = this._editor.getScrolledVisiblePosition(position);
|
||||
const editorCoords = getDomNodePagePosition(this._editor.getDomNode());
|
||||
const x = editorCoords.left + cursorCoords.left;
|
||||
const y = editorCoords.top + cursorCoords.top + cursorCoords.height;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
|
||||
namespace LightBulbState {
|
||||
|
||||
@@ -25,7 +26,7 @@ namespace LightBulbState {
|
||||
Showing,
|
||||
}
|
||||
|
||||
export const Hidden = new class { readonly type = Type.Hidden; };
|
||||
export const Hidden = { type: Type.Hidden } as const;
|
||||
|
||||
export class Showing {
|
||||
readonly type = Type.Showing;
|
||||
@@ -71,7 +72,9 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
this.hide();
|
||||
}
|
||||
}));
|
||||
this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', e => {
|
||||
|
||||
Gesture.ignoreTarget(this._domNode);
|
||||
this._register(dom.addStandardDisposableGenericMouseDownListner(this._domNode, e => {
|
||||
if (this.state.type !== LightBulbState.Type.Showing) {
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +140,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
|
||||
}
|
||||
|
||||
public update(actions: CodeActionSet, atPosition: IPosition) {
|
||||
if (actions.actions.length <= 0) {
|
||||
if (actions.validActions.length <= 0) {
|
||||
return this.hide();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
@@ -125,7 +125,7 @@ suite('CodeAction', () => {
|
||||
testData.tsLint.abc
|
||||
];
|
||||
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None);
|
||||
assert.equal(actions.length, 6);
|
||||
assert.deepEqual(actions, expected);
|
||||
});
|
||||
@@ -140,20 +140,20 @@ suite('CodeAction', () => {
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: 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') } }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: 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') } }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 0);
|
||||
}
|
||||
});
|
||||
@@ -172,7 +172,7 @@ suite('CodeAction', () => {
|
||||
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
});
|
||||
@@ -186,18 +186,40 @@ suite('CodeAction', () => {
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
|
||||
const { validActions: 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 } }, CancellationToken.None);
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
}
|
||||
});
|
||||
|
||||
test('getCodeActions should support filtering out some requested source code actions #84602', async function () {
|
||||
const provider = staticCodeActionProvider(
|
||||
{ title: 'a', kind: CodeActionKind.Source.value },
|
||||
{ title: 'b', kind: CodeActionKind.Source.append('test').value },
|
||||
{ title: 'c', kind: 'c' }
|
||||
);
|
||||
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
{
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
|
||||
type: 'auto', filter: {
|
||||
include: CodeActionKind.Source.append('test'),
|
||||
excludes: [CodeActionKind.Source],
|
||||
includeSourceActions: true,
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.equal(actions.length, 1);
|
||||
assert.strictEqual(actions[0].title, 'b');
|
||||
}
|
||||
});
|
||||
|
||||
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
|
||||
let wasInvoked = false;
|
||||
const provider = new class implements modes.CodeActionProvider {
|
||||
@@ -211,10 +233,10 @@ suite('CodeAction', () => {
|
||||
|
||||
disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
|
||||
const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
|
||||
type: 'auto',
|
||||
filter: {
|
||||
kind: CodeActionKind.QuickFix
|
||||
include: CodeActionKind.QuickFix
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
assert.strictEqual(actions.length, 0);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { refactorCommandId, organizeImportsCommandId } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/codeActionMenu';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
|
||||
|
||||
suite('CodeActionKeybindingResolver', () => {
|
||||
const refactorKeybinding = createCodeActionKeybinding(
|
||||
KeyCode.KEY_A,
|
||||
refactorCommandId,
|
||||
{ kind: CodeActionKind.Refactor.value });
|
||||
|
||||
const refactorExtractKeybinding = createCodeActionKeybinding(
|
||||
KeyCode.KEY_B,
|
||||
refactorCommandId,
|
||||
{ kind: CodeActionKind.Refactor.append('extract').value });
|
||||
|
||||
const organizeImportsKeybinding = createCodeActionKeybinding(
|
||||
KeyCode.KEY_C,
|
||||
organizeImportsCommandId,
|
||||
undefined);
|
||||
|
||||
test('Should match refactor keybindings', async function () {
|
||||
const resolver = new CodeActionKeybindingResolver({
|
||||
getKeybindings: (): readonly ResolvedKeybindingItem[] => {
|
||||
return [refactorKeybinding];
|
||||
},
|
||||
}).getResolver();
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '' }),
|
||||
undefined);
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.Refactor.value }),
|
||||
refactorKeybinding.resolvedKeybinding);
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }),
|
||||
refactorKeybinding.resolvedKeybinding);
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.QuickFix.value }),
|
||||
undefined);
|
||||
});
|
||||
|
||||
test('Should prefer most specific keybinding', async function () {
|
||||
const resolver = new CodeActionKeybindingResolver({
|
||||
getKeybindings: (): readonly ResolvedKeybindingItem[] => {
|
||||
return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding];
|
||||
},
|
||||
}).getResolver();
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.Refactor.value }),
|
||||
refactorKeybinding.resolvedKeybinding);
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }),
|
||||
refactorExtractKeybinding.resolvedKeybinding);
|
||||
});
|
||||
|
||||
test('Organize imports should still return a keybinding even though it does not have args', async function () {
|
||||
const resolver = new CodeActionKeybindingResolver({
|
||||
getKeybindings: (): readonly ResolvedKeybindingItem[] => {
|
||||
return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding];
|
||||
},
|
||||
}).getResolver();
|
||||
|
||||
assert.equal(
|
||||
resolver({ title: '', kind: CodeActionKind.SourceOrganizeImports.value }),
|
||||
organizeImportsKeybinding.resolvedKeybinding);
|
||||
});
|
||||
});
|
||||
|
||||
function createCodeActionKeybinding(keycode: KeyCode, command: string, commandArgs: any) {
|
||||
return new ResolvedKeybindingItem(
|
||||
new USLayoutResolvedKeybinding(
|
||||
new ChordKeybinding([new SimpleKeybinding(false, true, false, false, keycode)]),
|
||||
OperatingSystem.Linux),
|
||||
command,
|
||||
commandArgs,
|
||||
undefined,
|
||||
false);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -55,13 +56,15 @@ suite('CodeActionModel', () => {
|
||||
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.strictEqual(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
|
||||
e.actions.then(fixes => {
|
||||
model.dispose();
|
||||
assert.equal(fixes.actions.length, 1);
|
||||
assert.equal(fixes.validActions.length, 1);
|
||||
done();
|
||||
}, done);
|
||||
}));
|
||||
@@ -94,12 +97,14 @@ suite('CodeActionModel', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
assert.ok(e.actions);
|
||||
e.actions.then(fixes => {
|
||||
model.dispose();
|
||||
assert.equal(fixes.actions.length, 1);
|
||||
assert.equal(fixes.validActions.length, 1);
|
||||
resolve(undefined);
|
||||
}, reject);
|
||||
}));
|
||||
@@ -130,7 +135,9 @@ suite('CodeActionModel', () => {
|
||||
await new Promise(resolve => {
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
const selection = <Selection>e.rangeOrSelection;
|
||||
assert.deepEqual(selection.selectionStartLineNumber, 1);
|
||||
@@ -153,7 +160,9 @@ suite('CodeActionModel', () => {
|
||||
let triggerCount = 0;
|
||||
const contextKeys = new MockContextKeyService();
|
||||
const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined));
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => {
|
||||
disposables.add(model.onDidChangeState((e: CodeActionsState.State) => {
|
||||
assertType(e.type === CodeActionsState.Type.Triggered);
|
||||
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
++triggerCount;
|
||||
|
||||
|
||||
151
src/vs/editor/contrib/codeAction/types.ts
Normal file
151
src/vs/editor/contrib/codeAction/types.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
export class CodeActionKind {
|
||||
private static readonly sep = '.';
|
||||
|
||||
public static readonly None = new CodeActionKind('@@none@@'); // Special code action that contains nothing
|
||||
public static readonly Empty = new CodeActionKind('');
|
||||
public static readonly QuickFix = new CodeActionKind('quickfix');
|
||||
public static readonly Refactor = new CodeActionKind('refactor');
|
||||
public static readonly Source = new CodeActionKind('source');
|
||||
public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
|
||||
public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll');
|
||||
|
||||
constructor(
|
||||
public readonly value: string
|
||||
) { }
|
||||
|
||||
public equals(other: CodeActionKind): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
public contains(other: CodeActionKind): boolean {
|
||||
return this.equals(other) || this.value === '' || startsWith(other.value, this.value + CodeActionKind.sep);
|
||||
}
|
||||
|
||||
public intersects(other: CodeActionKind): boolean {
|
||||
return this.contains(other) || other.contains(this);
|
||||
}
|
||||
|
||||
public append(part: string): CodeActionKind {
|
||||
return new CodeActionKind(this.value + CodeActionKind.sep + part);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CodeActionAutoApply {
|
||||
IfSingle = 'ifSingle',
|
||||
First = 'first',
|
||||
Never = 'never',
|
||||
}
|
||||
|
||||
export interface CodeActionFilter {
|
||||
readonly include?: CodeActionKind;
|
||||
readonly excludes?: readonly 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.include && !filter.include.intersects(providedKind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (!filter.includeSourceActions && CodeActionKind.Source.contains(providedKind)) {
|
||||
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.include) {
|
||||
if (!actionKind || !filter.include.contains(actionKind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.excludes) {
|
||||
if (actionKind && filter.excludes.some(exclude => {
|
||||
// Excludes are overwritten by includes
|
||||
return exclude.contains(actionKind) && (!filter.include || !filter.include.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 {
|
||||
readonly type: 'auto' | 'manual';
|
||||
readonly filter?: CodeActionFilter;
|
||||
readonly autoApply?: CodeActionAutoApply;
|
||||
readonly context?: {
|
||||
readonly notAvailableMessage: string;
|
||||
readonly position: Position;
|
||||
};
|
||||
}
|
||||
|
||||
export class CodeActionCommandArgs {
|
||||
public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs {
|
||||
if (!arg || typeof arg !== 'object') {
|
||||
return new CodeActionCommandArgs(defaults.kind, defaults.apply, false);
|
||||
}
|
||||
return new CodeActionCommandArgs(
|
||||
CodeActionCommandArgs.getKindFromUser(arg, defaults.kind),
|
||||
CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply),
|
||||
CodeActionCommandArgs.getPreferredUser(arg));
|
||||
}
|
||||
|
||||
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': return CodeActionAutoApply.IfSingle;
|
||||
default: return defaultAutoApply;
|
||||
}
|
||||
}
|
||||
|
||||
private static getKindFromUser(arg: any, defaultKind: CodeActionKind) {
|
||||
return typeof arg.kind === 'string'
|
||||
? new CodeActionKind(arg.kind)
|
||||
: 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 preferred: boolean,
|
||||
) { }
|
||||
}
|
||||
@@ -68,11 +68,18 @@ export class CodeLensCache implements ICodeLensCache {
|
||||
}
|
||||
|
||||
put(model: ITextModel, data: CodeLensModel): void {
|
||||
// create a copy of the model that is without command-ids
|
||||
// but with comand-labels
|
||||
const copyItems = data.lenses.map(item => {
|
||||
return <CodeLens>{
|
||||
range: item.symbol.range,
|
||||
command: item.symbol.command && { id: '', title: item.symbol.command?.title },
|
||||
};
|
||||
});
|
||||
const copyModel = new CodeLensModel();
|
||||
copyModel.add({ lenses: copyItems, dispose: () => { } }, this._fakeProvider);
|
||||
|
||||
const lensModel = new CodeLensModel();
|
||||
lensModel.add({ lenses: data.lenses.map(v => v.symbol), dispose() { } }, this._fakeProvider);
|
||||
|
||||
const item = new CacheItem(model.getLineCount(), lensModel);
|
||||
const item = new CacheItem(model.getLineCount(), copyModel);
|
||||
this._cache.set(model.uri.toString(), item);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { createStyleSheet } from 'vs/base/browser/dom';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
@@ -27,6 +29,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
private readonly _globalToDispose = new DisposableStore();
|
||||
private readonly _localToDispose = new DisposableStore();
|
||||
private readonly _styleElement: HTMLStyleElement;
|
||||
private readonly _styleClassName: string;
|
||||
private _lenses: CodeLensWidget[] = [];
|
||||
private _currentFindCodeLensSymbolsPromise: CancelablePromise<CodeLensModel> | undefined;
|
||||
private _oldCodeLensModels = new DisposableStore();
|
||||
@@ -53,7 +57,16 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
}
|
||||
}));
|
||||
this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this));
|
||||
this._globalToDispose.add(this._editor.onDidChangeConfiguration(e => {
|
||||
if (e.hasChanged(EditorOption.fontInfo)) {
|
||||
this._updateLensStyle();
|
||||
}
|
||||
}));
|
||||
this._onModelChange();
|
||||
|
||||
this._styleClassName = hash(this._editor.getId()).toString(16);
|
||||
this._styleElement = createStyleSheet();
|
||||
this._updateLensStyle();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -63,6 +76,15 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
dispose(this._currentCodeLensModel);
|
||||
}
|
||||
|
||||
private _updateLensStyle(): void {
|
||||
const options = this._editor.getOptions();
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const lineHeight = options.get(EditorOption.lineHeight);
|
||||
|
||||
const newStyle = `.monaco-editor .codelens-decoration.${this._styleClassName} { height: ${Math.round(lineHeight * 1.1)}px; line-height: ${lineHeight}px; font-size: ${Math.round(fontInfo.fontSize * 0.9)}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;}`;
|
||||
this._styleElement.innerHTML = newStyle;
|
||||
}
|
||||
|
||||
private _localDispose(): void {
|
||||
if (this._currentFindCodeLensSymbolsPromise) {
|
||||
this._currentFindCodeLensSymbolsPromise.cancel();
|
||||
@@ -200,17 +222,17 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
this._disposeAllLenses(undefined, undefined);
|
||||
}
|
||||
}));
|
||||
this._localToDispose.add(this._editor.onDidChangeConfiguration(e => {
|
||||
if (e.hasChanged(EditorOption.fontInfo)) {
|
||||
for (const lens of this._lenses) {
|
||||
lens.updateHeight();
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._localToDispose.add(this._editor.onMouseUp(e => {
|
||||
if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') {
|
||||
if (e.target.type !== editorBrowser.MouseTargetType.CONTENT_WIDGET) {
|
||||
return;
|
||||
}
|
||||
let target = e.target.element;
|
||||
if (target?.tagName === 'SPAN') {
|
||||
target = target.parentElement;
|
||||
}
|
||||
if (target?.tagName === 'A') {
|
||||
for (const lens of this._lenses) {
|
||||
let command = lens.getCommand(e.target.element as HTMLLinkElement);
|
||||
let command = lens.getCommand(target as HTMLLinkElement);
|
||||
if (command) {
|
||||
this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err));
|
||||
break;
|
||||
@@ -222,8 +244,10 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
}
|
||||
|
||||
private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void {
|
||||
let helper = new CodeLensHelper();
|
||||
this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor));
|
||||
const helper = new CodeLensHelper();
|
||||
for (const lens of this._lenses) {
|
||||
lens.dispose(helper, viewZoneChangeAccessor);
|
||||
}
|
||||
if (decChangeAccessor) {
|
||||
helper.commit(decChangeAccessor);
|
||||
}
|
||||
@@ -276,7 +300,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
groupsIndex++;
|
||||
codeLensIndex++;
|
||||
} else {
|
||||
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
|
||||
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], <editorBrowser.IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
|
||||
codeLensIndex++;
|
||||
groupsIndex++;
|
||||
}
|
||||
@@ -290,7 +314,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
// Create extra symbols
|
||||
while (groupsIndex < groups.length) {
|
||||
this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
|
||||
this._lenses.push(new CodeLensWidget(groups[groupsIndex], <editorBrowser.IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
|
||||
groupsIndex++;
|
||||
}
|
||||
|
||||
@@ -326,7 +350,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentResolveCodeLensSymbolsPromise = createCancelablePromise(token => {
|
||||
const resolvePromise = createCancelablePromise(token => {
|
||||
|
||||
const promises = toResolve.map((request, i) => {
|
||||
|
||||
@@ -343,7 +367,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
if (!token.isCancellationRequested) {
|
||||
if (!token.isCancellationRequested && !lenses[i].isDisposed()) {
|
||||
lenses[i].updateCommands(resolvedSymbols);
|
||||
}
|
||||
});
|
||||
@@ -351,13 +375,21 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
this._currentResolveCodeLensSymbolsPromise = resolvePromise;
|
||||
|
||||
this._currentResolveCodeLensSymbolsPromise.then(() => {
|
||||
if (this._currentCodeLensModel) { // update the cached state with new resolved items
|
||||
this._codeLensCache.put(model, this._currentCodeLensModel);
|
||||
}
|
||||
this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model
|
||||
this._currentResolveCodeLensSymbolsPromise = undefined;
|
||||
if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) {
|
||||
this._currentResolveCodeLensSymbolsPromise = undefined;
|
||||
}
|
||||
}, err => {
|
||||
onUnexpectedError(err); // can also be cancellation!
|
||||
this._currentResolveCodeLensSymbolsPromise = undefined;
|
||||
if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) {
|
||||
this._currentResolveCodeLensSymbolsPromise = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
|
||||
.monaco-editor .codelens-decoration > span,
|
||||
.monaco-editor .codelens-decoration > a {
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
vertical-align: sub;
|
||||
}
|
||||
@@ -28,10 +27,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-editor .codelens-decoration.invisible-cl {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
0% { opacity: 0; visibility: visible;}
|
||||
100% { opacity: 1; }
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import 'vs/css!./codelensWidget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -16,7 +15,6 @@ import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegis
|
||||
import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens';
|
||||
import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
class CodeLensViewZone implements editorBrowser.IViewZone {
|
||||
|
||||
@@ -58,69 +56,65 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
|
||||
private readonly _id: string;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _editor: editorBrowser.ICodeEditor;
|
||||
private readonly _editor: editorBrowser.IActiveCodeEditor;
|
||||
private readonly _commands = new Map<string, Command>();
|
||||
|
||||
private _widgetPosition?: editorBrowser.IContentWidgetPosition;
|
||||
private _isEmpty: boolean = true;
|
||||
|
||||
constructor(
|
||||
editor: editorBrowser.ICodeEditor,
|
||||
symbolRange: Range,
|
||||
data: CodeLensItem[]
|
||||
editor: editorBrowser.IActiveCodeEditor,
|
||||
className: string,
|
||||
line: number,
|
||||
) {
|
||||
this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool);
|
||||
this._editor = editor;
|
||||
this._id = `codelens.widget-${(CodeLensContentWidget._idPool++)}`;
|
||||
|
||||
this.setSymbolRange(symbolRange);
|
||||
this.updatePosition(line);
|
||||
|
||||
this._domNode = document.createElement('span');
|
||||
this._domNode.innerHTML = ' ';
|
||||
dom.addClass(this._domNode, 'codelens-decoration');
|
||||
this.updateHeight();
|
||||
this.withCommands(data.map(data => data.symbol), false);
|
||||
this._domNode.className = `codelens-decoration ${className}`;
|
||||
}
|
||||
|
||||
updateHeight(): void {
|
||||
const options = this._editor.getOptions();
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const lineHeight = options.get(EditorOption.lineHeight);
|
||||
this._domNode.style.height = `${Math.round(lineHeight * 1.1)}px`;
|
||||
this._domNode.style.lineHeight = `${lineHeight}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 = ' ';
|
||||
}
|
||||
|
||||
withCommands(inSymbols: Array<CodeLens | undefined | null>, animate: boolean): void {
|
||||
withCommands(lenses: Array<CodeLens | undefined | null>, animate: boolean): void {
|
||||
this._commands.clear();
|
||||
|
||||
const symbols = coalesce(inSymbols);
|
||||
if (isFalsyOrEmpty(symbols)) {
|
||||
this._domNode.innerHTML = '<span>no commands</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html: string[] = [];
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
const command = symbols[i].command;
|
||||
if (command) {
|
||||
const title = renderCodicons(command.title);
|
||||
let part: string;
|
||||
if (command.id) {
|
||||
part = `<a id=${i}>${title}</a>`;
|
||||
this._commands.set(String(i), command);
|
||||
let innerHtml = '';
|
||||
let hasSymbol = false;
|
||||
for (let i = 0; i < lenses.length; i++) {
|
||||
const lens = lenses[i];
|
||||
if (!lens) {
|
||||
continue;
|
||||
}
|
||||
hasSymbol = true;
|
||||
if (lens.command) {
|
||||
const title = renderCodicons(lens.command.title);
|
||||
if (lens.command.id) {
|
||||
innerHtml += `<a id=${i}>${title}</a>`;
|
||||
this._commands.set(String(i), lens.command);
|
||||
} else {
|
||||
part = `<span>${title}</span>`;
|
||||
innerHtml += `<span>${title}</span>`;
|
||||
}
|
||||
if (i + 1 < lenses.length) {
|
||||
innerHtml += '<span> | </span>';
|
||||
}
|
||||
html.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' ';
|
||||
this._domNode.innerHTML = html.join('<span> | </span>');
|
||||
this._editor.layoutContentWidget(this);
|
||||
if (wasEmpty && animate) {
|
||||
dom.addClass(this._domNode, 'fadein');
|
||||
if (!hasSymbol) {
|
||||
// symbols but no commands
|
||||
this._domNode.innerHTML = '<span>no commands</span>';
|
||||
|
||||
} else {
|
||||
// symbols and commands
|
||||
if (!innerHtml) {
|
||||
innerHtml = ' ';
|
||||
}
|
||||
this._domNode.innerHTML = innerHtml;
|
||||
if (this._isEmpty && animate) {
|
||||
dom.addClass(this._domNode, 'fadein');
|
||||
}
|
||||
this._isEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,14 +132,10 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
setSymbolRange(range: Range): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const lineNumber = range.startLineNumber;
|
||||
const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(lineNumber);
|
||||
updatePosition(line: number): void {
|
||||
const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(line);
|
||||
this._widgetPosition = {
|
||||
position: { lineNumber: lineNumber, column: column },
|
||||
position: { lineNumber: line, column: column },
|
||||
preference: [editorBrowser.ContentWidgetPositionPreference.ABOVE]
|
||||
};
|
||||
}
|
||||
@@ -153,10 +143,6 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
getPosition(): editorBrowser.IContentWidgetPosition | null {
|
||||
return this._widgetPosition || null;
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._domNode.hasAttribute('monaco-visible-content-widget');
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDecorationIdCallback {
|
||||
@@ -194,27 +180,40 @@ export class CodeLensHelper {
|
||||
|
||||
export class CodeLensWidget {
|
||||
|
||||
private readonly _editor: editorBrowser.ICodeEditor;
|
||||
private readonly _editor: editorBrowser.IActiveCodeEditor;
|
||||
private readonly _className: string;
|
||||
private readonly _viewZone!: CodeLensViewZone;
|
||||
private readonly _viewZoneId!: string;
|
||||
private readonly _contentWidget!: CodeLensContentWidget;
|
||||
|
||||
private _contentWidget?: CodeLensContentWidget;
|
||||
private _decorationIds: string[];
|
||||
private _data: CodeLensItem[];
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
constructor(
|
||||
data: CodeLensItem[],
|
||||
editor: editorBrowser.ICodeEditor,
|
||||
editor: editorBrowser.IActiveCodeEditor,
|
||||
className: string,
|
||||
helper: CodeLensHelper,
|
||||
viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor,
|
||||
updateCallback: Function
|
||||
) {
|
||||
this._editor = editor;
|
||||
this._className = className;
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
|
||||
// create combined range, track all ranges with decorations,
|
||||
// check if there is already something to render
|
||||
this._decorationIds = [];
|
||||
let range: Range | undefined;
|
||||
let lenses: CodeLens[] = [];
|
||||
|
||||
this._data.forEach((codeLensData, i) => {
|
||||
|
||||
if (codeLensData.symbol.command) {
|
||||
lenses.push(codeLensData.symbol);
|
||||
}
|
||||
|
||||
helper.addDecoration({
|
||||
range: codeLensData.symbol.range,
|
||||
options: ModelDecorationOptions.EMPTY
|
||||
@@ -228,43 +227,51 @@ export class CodeLensWidget {
|
||||
}
|
||||
});
|
||||
|
||||
if (range) {
|
||||
this._contentWidget = new CodeLensContentWidget(editor, range, this._data);
|
||||
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
|
||||
this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, updateCallback);
|
||||
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
|
||||
|
||||
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
|
||||
this._editor.addContentWidget(this._contentWidget);
|
||||
if (lenses.length > 0) {
|
||||
this._createContentWidgetIfNecessary();
|
||||
this._contentWidget!.withCommands(lenses, false);
|
||||
}
|
||||
}
|
||||
|
||||
private _createContentWidgetIfNecessary(): void {
|
||||
if (!this._contentWidget) {
|
||||
this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1);
|
||||
this._editor.addContentWidget(this._contentWidget!);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
while (this._decorationIds.length) {
|
||||
helper.removeDecoration(this._decorationIds.pop()!);
|
||||
}
|
||||
this._decorationIds.forEach(helper.removeDecoration, helper);
|
||||
this._decorationIds = [];
|
||||
if (viewZoneChangeAccessor) {
|
||||
viewZoneChangeAccessor.removeZone(this._viewZoneId);
|
||||
}
|
||||
this._editor.removeContentWidget(this._contentWidget);
|
||||
if (this._contentWidget) {
|
||||
this._editor.removeContentWidget(this._contentWidget);
|
||||
this._contentWidget = undefined;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
}
|
||||
|
||||
isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
if (!this._editor.hasModel()) {
|
||||
return false;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
return this._decorationIds.some((id, i) => {
|
||||
const range = model.getDecorationRange(id);
|
||||
const range = this._editor.getModel().getDecorationRange(id);
|
||||
const symbol = this._data[i].symbol;
|
||||
return !!(range && Range.isEmpty(symbol.range) === range.isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
updateCodeLensSymbols(data: CodeLensItem[], helper: CodeLensHelper): void {
|
||||
while (this._decorationIds.length) {
|
||||
helper.removeDecoration(this._decorationIds.pop()!);
|
||||
}
|
||||
this._decorationIds.forEach(helper.removeDecoration, helper);
|
||||
this._decorationIds = [];
|
||||
this._data = data;
|
||||
this._decorationIds = new Array<string>(this._data.length);
|
||||
this._data.forEach((codeLensData, i) => {
|
||||
helper.addDecoration({
|
||||
range: codeLensData.symbol.range,
|
||||
@@ -274,7 +281,7 @@ export class CodeLensWidget {
|
||||
}
|
||||
|
||||
computeIfNecessary(model: ITextModel): CodeLensItem[] | null {
|
||||
if (!this._contentWidget.isVisible()) {
|
||||
if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -289,7 +296,10 @@ export class CodeLensWidget {
|
||||
}
|
||||
|
||||
updateCommands(symbols: Array<CodeLens | undefined | null>): void {
|
||||
this._contentWidget.withCommands(symbols, true);
|
||||
|
||||
this._createContentWidgetIfNecessary();
|
||||
this._contentWidget!.withCommands(symbols, true);
|
||||
|
||||
for (let i = 0; i < this._data.length; i++) {
|
||||
const resolved = symbols[i];
|
||||
if (resolved) {
|
||||
@@ -299,33 +309,29 @@ export class CodeLensWidget {
|
||||
}
|
||||
}
|
||||
|
||||
updateHeight(): void {
|
||||
this._contentWidget.updateHeight();
|
||||
}
|
||||
|
||||
getCommand(link: HTMLLinkElement): Command | undefined {
|
||||
return this._contentWidget.getCommand(link);
|
||||
return this._contentWidget?.getCommand(link);
|
||||
}
|
||||
|
||||
getLineNumber(): number {
|
||||
if (this._editor.hasModel()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
return range.startLineNumber;
|
||||
}
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
return range.startLineNumber;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
|
||||
if (this.isValid() && this._editor.hasModel()) {
|
||||
if (this.isValid()) {
|
||||
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
|
||||
if (range) {
|
||||
this._viewZone.afterLineNumber = range.startLineNumber - 1;
|
||||
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
|
||||
|
||||
this._contentWidget.setSymbolRange(range);
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
if (this._contentWidget) {
|
||||
this._contentWidget.updatePosition(range.startLineNumber);
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
.colorpicker-widget {
|
||||
height: 190px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.monaco-editor .colorpicker-hover:focus {
|
||||
@@ -115,4 +117,4 @@
|
||||
.colorpicker-body .strip .overlay {
|
||||
height: 150px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class SaturationBox extends Disposable {
|
||||
|
||||
this.layout();
|
||||
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
|
||||
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
|
||||
this._register(this.model.onDidChangeColor(this.onDidChangeColor, this));
|
||||
this.monitor = null;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ class SaturationBox extends Disposable {
|
||||
|
||||
this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null);
|
||||
|
||||
const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => {
|
||||
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
|
||||
this._onColorFlushed.fire();
|
||||
mouseUpListener.dispose();
|
||||
if (this.monitor) {
|
||||
@@ -250,7 +250,7 @@ abstract class Strip extends Disposable {
|
||||
this.slider = dom.append(this.domNode, $('.slider'));
|
||||
this.slider.style.top = `0px`;
|
||||
|
||||
this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
|
||||
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
|
||||
this.layout();
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ abstract class Strip extends Disposable {
|
||||
|
||||
monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null);
|
||||
|
||||
const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => {
|
||||
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
|
||||
this._onColorFlushed.fire();
|
||||
mouseUpListener.dispose();
|
||||
monitor.stopMonitoring(true);
|
||||
|
||||
@@ -56,13 +56,12 @@ class ToggleCommentLineAction extends CommentLineAction {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.US_SLASH,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
// {{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
|
||||
// }
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '5_insert',
|
||||
title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"),
|
||||
order: 1
|
||||
}*/
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -113,13 +112,12 @@ class BlockCommentAction extends EditorAction {
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
// {{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
|
||||
// }
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} - Remove from menu
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '5_insert',
|
||||
title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"),
|
||||
order: 2
|
||||
}*/
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows
|
||||
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IMenuService, MenuId, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
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';
|
||||
@@ -23,6 +23,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
|
||||
|
||||
export class ContextMenuController implements IEditorContribution {
|
||||
|
||||
@@ -128,7 +129,7 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
|
||||
// Find actions available for menu
|
||||
const menuActions = this._getMenuActions(this._editor.getModel());
|
||||
const menuActions = this._getMenuActions(this._editor.getModel(), MenuId.EditorContext);
|
||||
|
||||
// Show menu if we have actions to show
|
||||
if (menuActions.length > 0) {
|
||||
@@ -136,16 +137,27 @@ export class ContextMenuController implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private _getMenuActions(model: ITextModel): ReadonlyArray<IAction> {
|
||||
private _getMenuActions(model: ITextModel, menuId: MenuId): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
|
||||
let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService);
|
||||
const groups = contextMenu.getActions({ arg: model.uri });
|
||||
contextMenu.dispose();
|
||||
// get menu groups
|
||||
const menu = this._menuService.createMenu(menuId, this._contextKeyService);
|
||||
const groups = menu.getActions({ arg: model.uri });
|
||||
menu.dispose();
|
||||
|
||||
// translate them into other actions
|
||||
for (let group of groups) {
|
||||
const [, actions] = group;
|
||||
result.push(...actions);
|
||||
for (const action of actions) {
|
||||
if (action instanceof SubmenuItemAction) {
|
||||
const subActions = this._getMenuActions(model, action.item.submenu);
|
||||
if (subActions.length > 0) {
|
||||
result.push(new ContextSubMenu(action.label, subActions));
|
||||
}
|
||||
} else {
|
||||
result.push(action);
|
||||
}
|
||||
}
|
||||
result.push(new Separator());
|
||||
}
|
||||
result.pop(); // remove last separator
|
||||
|
||||
@@ -48,7 +48,6 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr
|
||||
|
||||
private _undoStack: CursorState[];
|
||||
private _redoStack: CursorState[];
|
||||
private _prevState: CursorState | null;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
@@ -57,38 +56,36 @@ export class CursorUndoRedoController extends Disposable implements IEditorContr
|
||||
|
||||
this._undoStack = [];
|
||||
this._redoStack = [];
|
||||
this._prevState = null;
|
||||
|
||||
this._register(editor.onDidChangeModel((e) => {
|
||||
this._undoStack = [];
|
||||
this._redoStack = [];
|
||||
this._prevState = null;
|
||||
}));
|
||||
this._register(editor.onDidChangeModelContent((e) => {
|
||||
this._undoStack = [];
|
||||
this._redoStack = [];
|
||||
this._prevState = null;
|
||||
this._pushStateIfNecessary();
|
||||
}));
|
||||
this._register(editor.onDidChangeCursorSelection(() => this._pushStateIfNecessary()));
|
||||
}
|
||||
|
||||
private _pushStateIfNecessary(): void {
|
||||
const newState = new CursorState(this._editor.getSelections()!);
|
||||
|
||||
if (!this._isCursorUndoRedo && this._prevState) {
|
||||
const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(this._prevState));
|
||||
this._register(editor.onDidChangeCursorSelection((e) => {
|
||||
if (this._isCursorUndoRedo) {
|
||||
return;
|
||||
}
|
||||
if (!e.oldSelections) {
|
||||
return;
|
||||
}
|
||||
if (e.oldModelVersionId !== e.modelVersionId) {
|
||||
return;
|
||||
}
|
||||
const prevState = new CursorState(e.oldSelections);
|
||||
const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(prevState));
|
||||
if (!isEqualToLastUndoStack) {
|
||||
this._undoStack.push(this._prevState);
|
||||
this._undoStack.push(prevState);
|
||||
this._redoStack = [];
|
||||
if (this._undoStack.length > 50) {
|
||||
// keep the cursor undo stack bounded
|
||||
this._undoStack.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._prevState = newState;
|
||||
}));
|
||||
}
|
||||
|
||||
public cursorUndo(): void {
|
||||
|
||||
63
src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts
Normal file
63
src/vs/editor/contrib/cursorUndo/test/cursorUndo.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Selection } from 'vs/editor/common/core/selection';
|
||||
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { CursorUndo, CursorUndoRedoController } from 'vs/editor/contrib/cursorUndo/cursorUndo';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
import { CoreNavigationCommands, CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
|
||||
suite('FindController', () => {
|
||||
|
||||
const cursorUndoAction = new CursorUndo();
|
||||
|
||||
test('issue #82535: Edge case with cursorUndo', () => {
|
||||
withTestCodeEditor([
|
||||
''
|
||||
], {}, (editor) => {
|
||||
|
||||
editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController);
|
||||
|
||||
// type hello
|
||||
editor.trigger('test', Handler.Type, { text: 'hello' });
|
||||
|
||||
// press left
|
||||
CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {});
|
||||
|
||||
// press Delete
|
||||
CoreEditingCommands.DeleteRight.runEditorCommand(null, editor, {});
|
||||
assert.deepEqual(editor.getValue(), 'hell');
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]);
|
||||
|
||||
// press left
|
||||
CoreNavigationCommands.CursorLeft.runEditorCommand(null, editor, {});
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]);
|
||||
|
||||
// press Ctrl+U
|
||||
cursorUndoAction.run(null!, editor, {});
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 5, 1, 5)]);
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #82535: Edge case with cursorUndo (reverse)', () => {
|
||||
withTestCodeEditor([
|
||||
''
|
||||
], {}, (editor) => {
|
||||
|
||||
editor.registerAndInstantiateContribution(CursorUndoRedoController.ID, CursorUndoRedoController);
|
||||
|
||||
// type hello
|
||||
editor.trigger('test', Handler.Type, { text: 'hell' });
|
||||
editor.trigger('test', Handler.Type, { text: 'o' });
|
||||
assert.deepEqual(editor.getValue(), 'hello');
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
|
||||
|
||||
// press Ctrl+U
|
||||
cursorUndoAction.run(null!, editor, {});
|
||||
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -50,7 +50,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE
|
||||
this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
|
||||
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
|
||||
this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
|
||||
this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e)));
|
||||
this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e)));
|
||||
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
|
||||
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
|
||||
this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
|
||||
@@ -143,7 +143,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorMouseDrop(mouseEvent: IEditorMouseEvent): void {
|
||||
private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void {
|
||||
if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
|
||||
let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, listErrorForeground, listWarningForeground, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export type OutlineItem = OutlineGroup | OutlineElement;
|
||||
|
||||
@@ -220,26 +222,84 @@ export const enum OutlineSortOrder {
|
||||
|
||||
export class OutlineFilter implements ITreeFilter<OutlineItem> {
|
||||
|
||||
private readonly _filteredTypes = new Set<SymbolKind>();
|
||||
static readonly configNameToKind = Object.freeze({
|
||||
['showFiles']: SymbolKind.File,
|
||||
['showModules']: SymbolKind.Module,
|
||||
['showNamespaces']: SymbolKind.Namespace,
|
||||
['showPackages']: SymbolKind.Package,
|
||||
['showClasses']: SymbolKind.Class,
|
||||
['showMethods']: SymbolKind.Method,
|
||||
['showProperties']: SymbolKind.Property,
|
||||
['showFields']: SymbolKind.Field,
|
||||
['showConstructors']: SymbolKind.Constructor,
|
||||
['showEnums']: SymbolKind.Enum,
|
||||
['showInterfaces']: SymbolKind.Interface,
|
||||
['showFunctions']: SymbolKind.Function,
|
||||
['showVariables']: SymbolKind.Variable,
|
||||
['showConstants']: SymbolKind.Constant,
|
||||
['showStrings']: SymbolKind.String,
|
||||
['showNumbers']: SymbolKind.Number,
|
||||
['showBooleans']: SymbolKind.Boolean,
|
||||
['showArrays']: SymbolKind.Array,
|
||||
['showObjects']: SymbolKind.Object,
|
||||
['showKeys']: SymbolKind.Key,
|
||||
['showNull']: SymbolKind.Null,
|
||||
['showEnumMembers']: SymbolKind.EnumMember,
|
||||
['showStructs']: SymbolKind.Struct,
|
||||
['showEvents']: SymbolKind.Event,
|
||||
['showOperators']: SymbolKind.Operator,
|
||||
['showTypeParameters']: SymbolKind.TypeParameter,
|
||||
});
|
||||
|
||||
static readonly kindToConfigName = Object.freeze({
|
||||
[SymbolKind.File]: 'showFiles',
|
||||
[SymbolKind.Module]: 'showModules',
|
||||
[SymbolKind.Namespace]: 'showNamespaces',
|
||||
[SymbolKind.Package]: 'showPackages',
|
||||
[SymbolKind.Class]: 'showClasses',
|
||||
[SymbolKind.Method]: 'showMethods',
|
||||
[SymbolKind.Property]: 'showProperties',
|
||||
[SymbolKind.Field]: 'showFields',
|
||||
[SymbolKind.Constructor]: 'showConstructors',
|
||||
[SymbolKind.Enum]: 'showEnums',
|
||||
[SymbolKind.Interface]: 'showInterfaces',
|
||||
[SymbolKind.Function]: 'showFunctions',
|
||||
[SymbolKind.Variable]: 'showVariables',
|
||||
[SymbolKind.Constant]: 'showConstants',
|
||||
[SymbolKind.String]: 'showStrings',
|
||||
[SymbolKind.Number]: 'showNumbers',
|
||||
[SymbolKind.Boolean]: 'showBooleans',
|
||||
[SymbolKind.Array]: 'showArrays',
|
||||
[SymbolKind.Object]: 'showObjects',
|
||||
[SymbolKind.Key]: 'showKeys',
|
||||
[SymbolKind.Null]: 'showNull',
|
||||
[SymbolKind.EnumMember]: 'showEnumMembers',
|
||||
[SymbolKind.Struct]: 'showStructs',
|
||||
[SymbolKind.Event]: 'showEvents',
|
||||
[SymbolKind.Operator]: 'showOperators',
|
||||
[SymbolKind.TypeParameter]: 'showTypeParameters',
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly _prefix: string,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
this._filteredTypes.clear();
|
||||
for (const name of SymbolKinds.names()) {
|
||||
if (!this._configService.getValue<boolean>(`${this._prefix}.${name}`)) {
|
||||
this._filteredTypes.add(SymbolKinds.fromString(name) || -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ITextResourceConfigurationService private readonly _textResourceConfigService: ITextResourceConfigurationService,
|
||||
) { }
|
||||
|
||||
filter(element: OutlineItem): boolean {
|
||||
return !(element instanceof OutlineElement) || !this._filteredTypes.has(element.symbol.kind);
|
||||
const outline = OutlineModel.get(element);
|
||||
let uri: URI | undefined;
|
||||
|
||||
if (outline) {
|
||||
uri = outline.textModel.uri;
|
||||
}
|
||||
|
||||
if (!(element instanceof OutlineElement)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const configName = OutlineFilter.kindToConfigName[element.symbol.kind];
|
||||
const configKey = `${this._prefix}.${configName}`;
|
||||
return this._textResourceConfigService.getValue(uri, configKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,11 +356,17 @@ export const SYMBOL_ICON_CLASS_FOREGROUND = registerColor('symbolIcon.classForeg
|
||||
hc: '#EE9D28'
|
||||
}, localize('symbolIcon.classForeground', 'The foreground color for class symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.contstantForeground', {
|
||||
export const SYMBOL_ICON_COLOR_FOREGROUND = registerColor('symbolIcon.colorForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.contstantForeground', 'The foreground color for contstant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
}, localize('symbolIcon.colorForeground', 'The foreground color for color symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_CONSTANT_FOREGROUND = registerColor('symbolIcon.constantForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.constantForeground', 'The foreground color for constant symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_CONSTRUCTOR_FOREGROUND = registerColor('symbolIcon.constructorForeground', {
|
||||
dark: '#B180D7',
|
||||
@@ -338,6 +404,12 @@ export const SYMBOL_ICON_FILE_FOREGROUND = registerColor('symbolIcon.fileForegro
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.fileForeground', 'The foreground color for file symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_FOLDER_FOREGROUND = registerColor('symbolIcon.folderForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.folderForeground', 'The foreground color for folder symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_FUNCTION_FOREGROUND = registerColor('symbolIcon.functionForeground', {
|
||||
dark: '#B180D7',
|
||||
light: '#652D90',
|
||||
@@ -356,6 +428,12 @@ export const SYMBOL_ICON_KEY_FOREGROUND = registerColor('symbolIcon.keyForegroun
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.keyForeground', 'The foreground color for key symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_KEYWORD_FOREGROUND = registerColor('symbolIcon.keywordForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.keywordForeground', 'The foreground color for keyword symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_METHOD_FOREGROUND = registerColor('symbolIcon.methodForeground', {
|
||||
dark: '#B180D7',
|
||||
light: '#652D90',
|
||||
@@ -410,6 +488,18 @@ export const SYMBOL_ICON_PROPERTY_FOREGROUND = registerColor('symbolIcon.propert
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.propertyForeground', 'The foreground color for property symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_REFERENCE_FOREGROUND = registerColor('symbolIcon.referenceForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.referenceForeground', 'The foreground color for reference symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_SNIPPET_FOREGROUND = registerColor('symbolIcon.snippetForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.snippetForeground', 'The foreground color for snippet symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_STRING_FOREGROUND = registerColor('symbolIcon.stringForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
@@ -422,12 +512,24 @@ export const SYMBOL_ICON_STRUCT_FOREGROUND = registerColor('symbolIcon.structFor
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.structForeground', 'The foreground color for struct symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_TEXT_FOREGROUND = registerColor('symbolIcon.textForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.textForeground', 'The foreground color for text symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_TYPEPARAMETER_FOREGROUND = registerColor('symbolIcon.typeParameterForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.typeParameterForeground', 'The foreground color for type parameter symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_UNIT_FOREGROUND = registerColor('symbolIcon.unitForeground', {
|
||||
dark: foreground,
|
||||
light: foreground,
|
||||
hc: foreground
|
||||
}, localize('symbolIcon.unitForeground', 'The foreground color for unit symbols. These symbols appear in the outline, breadcrumb, and suggest widget.'));
|
||||
|
||||
export const SYMBOL_ICON_VARIABLE_FOREGROUND = registerColor('symbolIcon.variableForeground', {
|
||||
dark: '#75BEFF',
|
||||
light: '#007ACC',
|
||||
@@ -472,6 +574,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND);
|
||||
if (symbolIconColorColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-color {
|
||||
color: ${symbolIconColorColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND);
|
||||
if (symbolIconConstantColor) {
|
||||
collector.addRule(`
|
||||
@@ -536,6 +647,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND);
|
||||
if (symbolIconFolderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-folder {
|
||||
color: ${symbolIconFolderColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND);
|
||||
if (symbolIconFunctionColor) {
|
||||
collector.addRule(`
|
||||
@@ -563,6 +683,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND);
|
||||
if (symbolIconKeywordColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-keyword {
|
||||
color: ${symbolIconKeywordColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND);
|
||||
if (symbolIconModuleColor) {
|
||||
collector.addRule(`
|
||||
@@ -635,6 +764,24 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND);
|
||||
if (symbolIconReferenceColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-reference {
|
||||
color: ${symbolIconReferenceColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND);
|
||||
if (symbolIconSnippetColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-snippet {
|
||||
color: ${symbolIconSnippetColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND);
|
||||
if (symbolIconStringColor) {
|
||||
collector.addRule(`
|
||||
@@ -653,6 +800,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND);
|
||||
if (symbolIconTextColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-text {
|
||||
color: ${symbolIconTextColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND);
|
||||
if (symbolIconTypeParameterColor) {
|
||||
collector.addRule(`
|
||||
@@ -662,6 +818,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND);
|
||||
if (symbolIconUnitColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .codicon-symbol-unit {
|
||||
color: ${symbolIconUnitColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND);
|
||||
if (symbolIconVariableColor) {
|
||||
collector.addRule(`
|
||||
|
||||
@@ -395,11 +395,27 @@ export class FindController extends CommonFindController implements IFindControl
|
||||
this._createFindWidget();
|
||||
}
|
||||
|
||||
if (!this._widget!.getPosition() && this._editor.getOption(EditorOption.find).autoFindInSelection) {
|
||||
// not visible yet so we need to set search scope if `editor.find.autoFindInSelection` is `true`
|
||||
opts.updateSearchScope = true;
|
||||
const selection = this._editor.getSelection();
|
||||
let updateSearchScope = false;
|
||||
|
||||
switch (this._editor.getOption(EditorOption.find).autoFindInSelection) {
|
||||
case 'always':
|
||||
updateSearchScope = true;
|
||||
break;
|
||||
case 'never':
|
||||
updateSearchScope = false;
|
||||
break;
|
||||
case 'multiline':
|
||||
const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;
|
||||
updateSearchScope = isSelectionMultipleLine;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
opts.updateSearchScope = updateSearchScope;
|
||||
|
||||
super._start(opts);
|
||||
|
||||
if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {
|
||||
@@ -439,7 +455,7 @@ export class StartFindAction extends EditorAction {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '3_find',
|
||||
title: nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"),
|
||||
@@ -489,7 +505,7 @@ export class StartFindWithSelectionAction extends EditorAction {
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: true,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.FocusFindInput,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: true,
|
||||
updateSearchScope: false
|
||||
});
|
||||
@@ -685,7 +701,7 @@ export class StartFindReplaceAction extends EditorAction {
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_F },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarEditMenu,
|
||||
group: '3_find',
|
||||
title: nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPosit
|
||||
import { FIND_IDS } from 'vs/editor/contrib/find/findModel';
|
||||
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { contrastBorder, editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
@@ -200,6 +200,12 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { background-color: ${widgetBackground}; }`);
|
||||
}
|
||||
|
||||
const widgetForeground = theme.getColor(editorWidgetForeground);
|
||||
if (widgetForeground) {
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { color: ${widgetForeground}; }`);
|
||||
}
|
||||
|
||||
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
@@ -209,4 +215,4 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (hcBorder) {
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { border: 2px solid ${hcBorder}; }`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
||||
export interface FindReplaceStateChangedEvent {
|
||||
@@ -57,7 +57,7 @@ function effectiveOptionValue(override: FindOptionOverride, value: boolean): boo
|
||||
return value;
|
||||
}
|
||||
|
||||
export class FindReplaceState implements IDisposable {
|
||||
export class FindReplaceState extends Disposable {
|
||||
private _searchString: string;
|
||||
private _replaceString: string;
|
||||
private _isRevealed: boolean;
|
||||
@@ -74,7 +74,7 @@ export class FindReplaceState implements IDisposable {
|
||||
private _matchesPosition: number;
|
||||
private _matchesCount: number;
|
||||
private _currentMatch: Range | null;
|
||||
private readonly _onFindReplaceStateChange = new Emitter<FindReplaceStateChangedEvent>();
|
||||
private readonly _onFindReplaceStateChange = this._register(new Emitter<FindReplaceStateChangedEvent>());
|
||||
|
||||
public get searchString(): string { return this._searchString; }
|
||||
public get replaceString(): string { return this._replaceString; }
|
||||
@@ -97,6 +97,7 @@ export class FindReplaceState implements IDisposable {
|
||||
public readonly onFindReplaceStateChange: Event<FindReplaceStateChangedEvent> = this._onFindReplaceStateChange.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._searchString = '';
|
||||
this._replaceString = '';
|
||||
this._isRevealed = false;
|
||||
@@ -115,9 +116,6 @@ export class FindReplaceState implements IDisposable {
|
||||
this._currentMatch = null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void {
|
||||
let changeEvent: FindReplaceStateChangedEvent = {
|
||||
moveCursor: false,
|
||||
|
||||
@@ -32,13 +32,17 @@
|
||||
.monaco-editor .find-widget {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -44px;
|
||||
height: 33px;
|
||||
overflow: hidden;
|
||||
line-height: 19px;
|
||||
transition: top 200ms linear;
|
||||
transition: transform 200ms linear;
|
||||
padding: 0 4px;
|
||||
box-sizing: border-box;
|
||||
transform: translateY(Calc(-100% - 10px)); /* shadow (10px) */
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget textarea {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget.hiddenEditor {
|
||||
@@ -46,30 +50,12 @@
|
||||
}
|
||||
|
||||
/* Find widget when replace is toggled on */
|
||||
.monaco-editor .find-widget.replaceToggled {
|
||||
top: -74px; /* find input height + replace input height + shadow (10px) */
|
||||
}
|
||||
.monaco-editor .find-widget.replaceToggled > .replace-part {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget.visible,
|
||||
.monaco-editor .find-widget.replaceToggled.visible {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* Multiple line find widget */
|
||||
|
||||
.monaco-editor .find-widget.multipleline {
|
||||
top: unset;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget.multipleline.visible,
|
||||
.monaco-editor .find-widget.multipleline.replaceToggled.visible {
|
||||
top: 0px;
|
||||
bottom: unset;
|
||||
.monaco-editor .find-widget.visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .monaco-inputbox.synthetic-focus {
|
||||
@@ -79,7 +65,6 @@
|
||||
|
||||
.monaco-editor .find-widget .monaco-inputbox .input {
|
||||
background-color: transparent;
|
||||
/* Style to compensate for //winjs */
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -92,7 +77,6 @@
|
||||
margin: 4px 0 0 17px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget > .find-part .monaco-inputbox,
|
||||
@@ -128,7 +112,6 @@
|
||||
.monaco-editor .find-widget .monaco-findInput {
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex:1;
|
||||
}
|
||||
|
||||
@@ -144,7 +127,6 @@
|
||||
|
||||
.monaco-editor .find-widget .matchesCount {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex: initial;
|
||||
margin: 0 0 0 3px;
|
||||
padding: 2px 0 0 2px;
|
||||
@@ -156,11 +138,9 @@
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .button {
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex: initial;
|
||||
margin-left: 3px;
|
||||
background-position: center center;
|
||||
@@ -189,14 +169,10 @@
|
||||
.monaco-editor .find-widget .button.toggle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
left: 3px;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .button.toggle.disabled {
|
||||
@@ -249,7 +225,6 @@
|
||||
.monaco-editor .find-widget > .replace-part > .monaco-findInput {
|
||||
position: relative;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
vertical-align: middle;
|
||||
flex: auto;
|
||||
flex-grow: 0;
|
||||
@@ -287,12 +262,6 @@
|
||||
}
|
||||
|
||||
.monaco-editor .findMatch {
|
||||
-webkit-animation-duration: 0;
|
||||
-webkit-animation-name: inherit !important;
|
||||
-moz-animation-duration: 0;
|
||||
-moz-animation-name: inherit !important;
|
||||
-ms-animation-duration: 0;
|
||||
-ms-animation-name: inherit !important;
|
||||
animation-duration: 0;
|
||||
animation-name: inherit !important;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { alert as alertFn } from 'vs/base/browser/ui/aria/aria';
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
|
||||
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput';
|
||||
@@ -120,7 +121,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
private _matchesCount!: HTMLElement;
|
||||
private _prevBtn!: SimpleButton;
|
||||
private _nextBtn!: SimpleButton;
|
||||
private _toggleSelectionFind!: SimpleCheckbox;
|
||||
private _toggleSelectionFind!: Checkbox;
|
||||
private _closeBtn!: SimpleButton;
|
||||
private _replaceBtn!: SimpleButton;
|
||||
private _replaceAllBtn!: SimpleButton;
|
||||
@@ -290,12 +291,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
|
||||
private _onStateChanged(e: FindReplaceStateChangedEvent): void {
|
||||
if (e.searchString) {
|
||||
if (this._state.searchString.indexOf('\n') >= 0) {
|
||||
dom.addClass(this._domNode, 'multipleline');
|
||||
} else {
|
||||
dom.removeClass(this._domNode, 'multipleline');
|
||||
}
|
||||
|
||||
try {
|
||||
this._ignoreChangeEvent = true;
|
||||
this._findInput.setValue(this._state.searchString);
|
||||
@@ -436,7 +431,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
|
||||
let isChecked = this._toggleSelectionFind.checked;
|
||||
|
||||
this._toggleSelectionFind.setEnabled(this._isVisible && (isChecked || isSelection));
|
||||
if (this._isVisible && (isChecked || isSelection)) {
|
||||
this._toggleSelectionFind.enable();
|
||||
} else {
|
||||
this._toggleSelectionFind.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private _updateButtons(): void {
|
||||
@@ -466,12 +465,23 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
this._isVisible = true;
|
||||
|
||||
const selection = this._codeEditor.getSelection();
|
||||
const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
|
||||
if (isSelection && this._codeEditor.getOption(EditorOption.find).autoFindInSelection) {
|
||||
this._toggleSelectionFind.checked = true;
|
||||
} else {
|
||||
this._toggleSelectionFind.checked = false;
|
||||
|
||||
switch (this._codeEditor.getOption(EditorOption.find).autoFindInSelection) {
|
||||
case 'always':
|
||||
this._toggleSelectionFind.checked = true;
|
||||
break;
|
||||
case 'never':
|
||||
this._toggleSelectionFind.checked = false;
|
||||
break;
|
||||
case 'multiline':
|
||||
const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;
|
||||
this._toggleSelectionFind.checked = isSelectionMultipleLine;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this._tryUpdateWidgetWidth();
|
||||
this._updateButtons();
|
||||
|
||||
@@ -636,6 +646,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
};
|
||||
this._findInput.style(inputStyles);
|
||||
this._replaceInput.style(inputStyles);
|
||||
this._toggleSelectionFind.style(inputStyles);
|
||||
}
|
||||
|
||||
private _tryUpdateWidgetWidth() {
|
||||
@@ -981,26 +992,30 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
actionsContainer.appendChild(this._nextBtn.domNode);
|
||||
|
||||
// Toggle selection button
|
||||
this._toggleSelectionFind = this._register(new SimpleCheckbox({
|
||||
parent: actionsContainer,
|
||||
this._toggleSelectionFind = this._register(new Checkbox({
|
||||
actionClassName: 'codicon codicon-selection',
|
||||
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
|
||||
onChange: () => {
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
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);
|
||||
}
|
||||
isChecked: false
|
||||
}));
|
||||
|
||||
this._register(this._toggleSelectionFind.onChange(() => {
|
||||
if (this._toggleSelectionFind.checked) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
this._state.change({ searchScope: null }, true);
|
||||
}
|
||||
}));
|
||||
|
||||
actionsContainer.appendChild(this._toggleSelectionFind.domNode);
|
||||
|
||||
// Close button
|
||||
this._closeBtn = this._register(new SimpleButton({
|
||||
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
|
||||
@@ -1054,7 +1069,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
this._prevBtn.focus();
|
||||
} else if (this._nextBtn.isEnabled()) {
|
||||
this._nextBtn.focus();
|
||||
} else if (this._toggleSelectionFind.isEnabled()) {
|
||||
} else if (this._toggleSelectionFind.enabled) {
|
||||
this._toggleSelectionFind.focus();
|
||||
} else if (this._closeBtn.isEnabled()) {
|
||||
this._closeBtn.focus();
|
||||
@@ -1200,91 +1215,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
}
|
||||
}
|
||||
|
||||
interface ISimpleCheckboxOpts {
|
||||
readonly parent: HTMLElement;
|
||||
readonly title: string;
|
||||
readonly onChange: () => void;
|
||||
}
|
||||
|
||||
class SimpleCheckbox extends Widget {
|
||||
|
||||
private static _COUNTER = 0;
|
||||
|
||||
private readonly _opts: ISimpleCheckboxOpts;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _checkbox: HTMLInputElement;
|
||||
private readonly _label: HTMLLabelElement;
|
||||
|
||||
constructor(opts: ISimpleCheckboxOpts) {
|
||||
super();
|
||||
this._opts = opts;
|
||||
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-checkbox';
|
||||
this._domNode.title = this._opts.title;
|
||||
this._domNode.tabIndex = 0;
|
||||
|
||||
this._checkbox = document.createElement('input');
|
||||
this._checkbox.type = 'checkbox';
|
||||
this._checkbox.className = 'checkbox';
|
||||
this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++;
|
||||
this._checkbox.tabIndex = -1;
|
||||
|
||||
this._label = document.createElement('label');
|
||||
this._label.className = 'codicon codicon-selection';
|
||||
// Connect the label and the checkbox. Checkbox will get checked when the label receives a click.
|
||||
this._label.htmlFor = this._checkbox.id;
|
||||
this._label.tabIndex = -1;
|
||||
|
||||
this._domNode.appendChild(this._checkbox);
|
||||
this._domNode.appendChild(this._label);
|
||||
|
||||
this._opts.parent.appendChild(this._domNode);
|
||||
|
||||
this.onchange(this._checkbox, () => {
|
||||
this._opts.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
public get domNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return (this._domNode.tabIndex >= 0);
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._checkbox.checked;
|
||||
}
|
||||
|
||||
public set checked(newValue: boolean) {
|
||||
this._checkbox.checked = newValue;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._domNode.focus();
|
||||
}
|
||||
|
||||
private enable(): void {
|
||||
this._checkbox.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
private disable(): void {
|
||||
this._checkbox.disabled = true;
|
||||
}
|
||||
|
||||
public setEnabled(enabled: boolean): void {
|
||||
if (enabled) {
|
||||
this.enable();
|
||||
this.domNode.tabIndex = 0;
|
||||
} else {
|
||||
this.disable();
|
||||
this.domNode.tabIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISimpleButtonOpts {
|
||||
readonly label: string;
|
||||
readonly className: string;
|
||||
@@ -1390,7 +1320,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const hcBorder = theme.getColor(contrastBorder);
|
||||
if (hcBorder) {
|
||||
collector.addRule(`.monaco-editor .find-widget { border: 2px solid ${hcBorder}; }`);
|
||||
collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`);
|
||||
}
|
||||
|
||||
const foreground = theme.getColor(editorWidgetForeground);
|
||||
|
||||
@@ -75,7 +75,7 @@ suite('Find', () => {
|
||||
let searchStringSelectionTwoLines = getSelectionSearchString(editor);
|
||||
assert.equal(searchStringSelectionTwoLines, null);
|
||||
|
||||
// Select end of first line newline and and chunk of second
|
||||
// Select end of first line newline and chunk of second
|
||||
editor.setSelection(new Range(1, 7, 2, 4));
|
||||
let searchStringSelectionSpanLines = getSelectionSearchString(editor);
|
||||
assert.equal(searchStringSelectionSpanLines, null);
|
||||
|
||||
@@ -519,7 +519,7 @@ suite('FindController query options persistence', () => {
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 1, 2, 1));
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController.ID, TestFindController);
|
||||
@@ -542,7 +542,7 @@ suite('FindController query options persistence', () => {
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 2, 1, 2));
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController.ID, TestFindController);
|
||||
@@ -565,7 +565,7 @@ suite('FindController query options persistence', () => {
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor, cursor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController.ID, TestFindController);
|
||||
@@ -582,4 +582,28 @@ suite('FindController query options persistence', () => {
|
||||
assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('issue #27083: Find in selection when multiple lines are selected', () => {
|
||||
withTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor, cursor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 6, 2, 1));
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController.ID, TestFindController);
|
||||
|
||||
findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
updateSearchScope: true
|
||||
});
|
||||
|
||||
assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,115 +156,58 @@ suite('Replace Pattern test', () => {
|
||||
});
|
||||
|
||||
test('buildReplaceStringWithCasePreserved test', () => {
|
||||
let replacePattern = 'Def';
|
||||
let actual: string | string[] = 'abc';
|
||||
function assertReplace(target: string[], replaceString: string, expected: string): void {
|
||||
let actual: string = '';
|
||||
actual = buildReplaceStringWithCasePreserved(target, replaceString);
|
||||
assert.equal(actual, expected);
|
||||
}
|
||||
|
||||
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'def');
|
||||
actual = 'Abc';
|
||||
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'Def');
|
||||
actual = 'ABC';
|
||||
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'DEF');
|
||||
|
||||
actual = ['abc', 'Abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'def');
|
||||
actual = ['Abc', 'abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
|
||||
actual = ['ABC', 'abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'DEF');
|
||||
|
||||
actual = ['AbC'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
|
||||
actual = ['aBC'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
|
||||
|
||||
actual = ['Foo-Bar'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-Newbar');
|
||||
actual = ['Foo-Bar-Abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc');
|
||||
actual = ['Foo-Bar-abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar');
|
||||
actual = ['foo-Bar'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-Newbar');
|
||||
actual = ['foo-BAR'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-NEWBAR');
|
||||
|
||||
actual = ['Foo_Bar'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_Newbar');
|
||||
actual = ['Foo_Bar_Abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar_newabc'), 'Newfoo_Newbar_Newabc');
|
||||
actual = ['Foo_Bar_abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_newbar');
|
||||
actual = ['Foo_Bar-abc'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar-abc'), 'Newfoo_newbar-abc');
|
||||
actual = ['foo_Bar'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'newfoo_Newbar');
|
||||
actual = ['Foo_BAR'];
|
||||
assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_NEWBAR');
|
||||
assertReplace(['abc'], 'Def', 'def');
|
||||
assertReplace(['Abc'], 'Def', 'Def');
|
||||
assertReplace(['ABC'], 'Def', 'DEF');
|
||||
assertReplace(['abc', 'Abc'], 'Def', 'def');
|
||||
assertReplace(['Abc', 'abc'], 'Def', 'Def');
|
||||
assertReplace(['ABC', 'abc'], 'Def', 'DEF');
|
||||
assertReplace(['AbC'], 'Def', 'Def');
|
||||
assertReplace(['aBC'], 'Def', 'Def');
|
||||
assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
|
||||
assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
|
||||
assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
|
||||
assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
|
||||
assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
|
||||
assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
|
||||
assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
|
||||
assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');
|
||||
assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');
|
||||
assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');
|
||||
assertReplace(['Foo_BAR'], 'newfoo_newbar', 'Newfoo_NEWBAR');
|
||||
});
|
||||
|
||||
test('preserve case', () => {
|
||||
let replacePattern = parseReplaceString('Def');
|
||||
let actual = replacePattern.buildReplaceString(['abc'], true);
|
||||
assert.equal(actual, 'def');
|
||||
actual = replacePattern.buildReplaceString(['Abc'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['ABC'], true);
|
||||
assert.equal(actual, 'DEF');
|
||||
function assertReplace(target: string[], replaceString: string, expected: string): void {
|
||||
let replacePattern = parseReplaceString(replaceString);
|
||||
let actual = replacePattern.buildReplaceString(target, true);
|
||||
assert.equal(actual, expected);
|
||||
}
|
||||
|
||||
actual = replacePattern.buildReplaceString(['abc', 'Abc'], true);
|
||||
assert.equal(actual, 'def');
|
||||
actual = replacePattern.buildReplaceString(['Abc', 'abc'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['ABC', 'abc'], true);
|
||||
assert.equal(actual, 'DEF');
|
||||
|
||||
actual = replacePattern.buildReplaceString(['AbC'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
actual = replacePattern.buildReplaceString(['aBC'], true);
|
||||
assert.equal(actual, 'Def');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo-newbar');
|
||||
actual = replacePattern.buildReplaceString(['Foo-Bar'], true);
|
||||
assert.equal(actual, 'Newfoo-Newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo-newbar-newabc');
|
||||
actual = replacePattern.buildReplaceString(['Foo-Bar-Abc'], true);
|
||||
assert.equal(actual, 'Newfoo-Newbar-Newabc');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo-newbar');
|
||||
actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true);
|
||||
assert.equal(actual, 'Newfoo-newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo-newbar');
|
||||
actual = replacePattern.buildReplaceString(['foo-Bar'], true);
|
||||
assert.equal(actual, 'newfoo-Newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo-newbar');
|
||||
actual = replacePattern.buildReplaceString(['foo-BAR'], true);
|
||||
assert.equal(actual, 'newfoo-NEWBAR');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar');
|
||||
actual = replacePattern.buildReplaceString(['Foo_Bar'], true);
|
||||
assert.equal(actual, 'Newfoo_Newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar_newabc');
|
||||
actual = replacePattern.buildReplaceString(['Foo_Bar_Abc'], true);
|
||||
assert.equal(actual, 'Newfoo_Newbar_Newabc');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar');
|
||||
actual = replacePattern.buildReplaceString(['Foo_Bar_abc'], true);
|
||||
assert.equal(actual, 'Newfoo_newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar-abc');
|
||||
actual = replacePattern.buildReplaceString(['Foo_Bar-abc'], true);
|
||||
assert.equal(actual, 'Newfoo_newbar-abc');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar');
|
||||
actual = replacePattern.buildReplaceString(['foo_Bar'], true);
|
||||
assert.equal(actual, 'newfoo_Newbar');
|
||||
|
||||
replacePattern = parseReplaceString('newfoo_newbar');
|
||||
actual = replacePattern.buildReplaceString(['foo_BAR'], true);
|
||||
assert.equal(actual, 'newfoo_NEWBAR');
|
||||
assertReplace(['abc'], 'Def', 'def');
|
||||
assertReplace(['Abc'], 'Def', 'Def');
|
||||
assertReplace(['ABC'], 'Def', 'DEF');
|
||||
assertReplace(['abc', 'Abc'], 'Def', 'def');
|
||||
assertReplace(['Abc', 'abc'], 'Def', 'Def');
|
||||
assertReplace(['ABC', 'abc'], 'Def', 'DEF');
|
||||
assertReplace(['AbC'], 'Def', 'Def');
|
||||
assertReplace(['aBC'], 'Def', 'Def');
|
||||
assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
|
||||
assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
|
||||
assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
|
||||
assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
|
||||
assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
|
||||
assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
|
||||
assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
|
||||
assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');
|
||||
assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');
|
||||
assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');
|
||||
assertReplace(['foo_BAR'], 'newfoo_newbar', 'newfoo_NEWBAR');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -423,7 +423,7 @@ export class FoldingController extends Disposable implements IEditorContribution
|
||||
if (iconClicked || isCollapsed) {
|
||||
let toToggle = [region];
|
||||
if (e.event.middleButton || e.event.shiftKey) {
|
||||
toToggle.push(...foldingModel.getRegionsInside(region, r => r.isCollapsed === isCollapsed));
|
||||
toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed));
|
||||
}
|
||||
foldingModel.toggleCollapseState(toToggle);
|
||||
this.reveal({ lineNumber, column: 1 });
|
||||
|
||||
@@ -204,7 +204,7 @@ export class FoldingModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
getRegionsInside(region: FoldingRegion | null, filter?: (r: FoldingRegion, level?: number) => boolean): FoldingRegion[] {
|
||||
getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] {
|
||||
let result: FoldingRegion[] = [];
|
||||
let index = region ? region.regionIndex + 1 : 0;
|
||||
let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE;
|
||||
@@ -229,7 +229,7 @@ export class FoldingModel {
|
||||
for (let i = index, len = this._regions.length; i < len; i++) {
|
||||
let current = this._regions.toRegion(i);
|
||||
if (this._regions.getStartLineNumber(i) < endLineNumber) {
|
||||
if (!filter || filter(current)) {
|
||||
if (!filter || (filter as RegionFilter)(current)) {
|
||||
result.push(current);
|
||||
}
|
||||
} else {
|
||||
@@ -242,6 +242,10 @@ export class FoldingModel {
|
||||
|
||||
}
|
||||
|
||||
type RegionFilter = (r: FoldingRegion) => boolean;
|
||||
type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean;
|
||||
|
||||
|
||||
/**
|
||||
* Collapse or expand the regions at the given locations
|
||||
* @param levels The number of levels. Use 1 to only impact the regions at the location, use Number.MAX_VALUE for all levels.
|
||||
|
||||
@@ -219,7 +219,7 @@ class FormatDocumentAction extends EditorAction {
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
when: EditorContextKeys.hasDocumentFormattingProvider,
|
||||
group: '1_modification',
|
||||
order: 1.3
|
||||
@@ -248,7 +248,7 @@ class FormatSelectionAction extends EditorAction {
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection),
|
||||
group: '1_modification',
|
||||
order: 1.31
|
||||
|
||||
@@ -1,505 +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 { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
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, IRange } from 'vs/editor/common/core/range';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
|
||||
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';
|
||||
import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel';
|
||||
import * as nls from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
|
||||
import { ISymbolNavigationService } from 'vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export class DefinitionActionConfig {
|
||||
|
||||
constructor(
|
||||
public readonly openToSide = false,
|
||||
public readonly openInPeek = false,
|
||||
public readonly filterCurrent = true,
|
||||
public readonly showMessage = true,
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export class DefinitionAction extends EditorAction {
|
||||
|
||||
private readonly _configuration: DefinitionActionConfig;
|
||||
|
||||
constructor(configuration: DefinitionActionConfig, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._configuration = configuration;
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
if (!editor.hasModel()) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const progressService = accessor.get(IEditorProgressService);
|
||||
const symbolNavService = accessor.get(ISymbolNavigationService);
|
||||
|
||||
const model = editor.getModel();
|
||||
const pos = editor.getPosition();
|
||||
|
||||
const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
|
||||
const definitionPromise = raceCancellation(this._getTargetLocationForPosition(model, pos, cts.token), cts.token).then(async references => {
|
||||
|
||||
if (!references || model.isDisposed()) {
|
||||
// new model, no more model
|
||||
return;
|
||||
}
|
||||
|
||||
// * remove falsy references
|
||||
// * find reference at the current pos
|
||||
let idxOfCurrent = -1;
|
||||
const result: LocationLink[] = [];
|
||||
for (const reference of references) {
|
||||
if (!reference || !reference.range) {
|
||||
continue;
|
||||
}
|
||||
const newLen = result.push(reference);
|
||||
if (this._configuration.filterCurrent
|
||||
&& isEqual(reference.uri, model.uri)
|
||||
&& Range.containsPosition(reference.range, pos)
|
||||
&& idxOfCurrent === -1
|
||||
) {
|
||||
idxOfCurrent = newLen - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length === 0) {
|
||||
// no result -> show message
|
||||
if (this._configuration.showMessage) {
|
||||
const info = model.getWordAtPosition(pos);
|
||||
MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos);
|
||||
}
|
||||
} else if (result.length === 1 && idxOfCurrent !== -1) {
|
||||
// only the position at which we are -> adjust selection
|
||||
let [current] = result;
|
||||
return this._openReference(editor, editorService, current, false).then(() => undefined);
|
||||
|
||||
} else {
|
||||
// handle multile results
|
||||
return this._onResult(editorService, symbolNavService, editor, new ReferencesModel(result));
|
||||
}
|
||||
|
||||
}, (err) => {
|
||||
// report an error
|
||||
notificationService.error(err);
|
||||
}).finally(() => {
|
||||
cts.dispose();
|
||||
});
|
||||
|
||||
progressService.showWhile(definitionPromise, 250);
|
||||
return definitionPromise;
|
||||
}
|
||||
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitionsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('noResultWord', "No definition found for '{0}'", info.word)
|
||||
: nls.localize('generic.noResults', "No definition found");
|
||||
}
|
||||
|
||||
protected _getMetaTitle(model: ReferencesModel): string {
|
||||
return model.references.length > 1 ? nls.localize('meta.title', " – {0} definitions", model.references.length) : '';
|
||||
}
|
||||
|
||||
private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: ICodeEditor, model: ReferencesModel): Promise<void> {
|
||||
|
||||
const msg = model.getAriaMessage();
|
||||
alert(msg);
|
||||
|
||||
const gotoLocation = editor.getOption(EditorOption.gotoLocation);
|
||||
if (this._configuration.openInPeek || (gotoLocation.multiple === 'peek' && model.references.length > 1)) {
|
||||
this._openInPeek(editorService, editor, model);
|
||||
|
||||
} else if (editor.hasModel()) {
|
||||
const next = model.firstReference();
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide);
|
||||
if (targetEditor && model.references.length > 1 && gotoLocation.multiple === 'gotoAndPeek') {
|
||||
this._openInPeek(editorService, targetEditor, model);
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
|
||||
// keep remaining locations around when using
|
||||
// 'goto'-mode
|
||||
if (gotoLocation.multiple === 'goto') {
|
||||
symbolNavService.put(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise<ICodeEditor | null> {
|
||||
// range is the target-selection-range when we have one
|
||||
// and the the fallback is the 'full' range
|
||||
let range: IRange | undefined = undefined;
|
||||
if (isLocationLink(reference)) {
|
||||
range = reference.targetSelectionRange;
|
||||
}
|
||||
if (!range) {
|
||||
range = reference.range;
|
||||
}
|
||||
|
||||
return editorService.openCodeEditor({
|
||||
resource: reference.uri,
|
||||
options: {
|
||||
selection: Range.collapseToStart(range),
|
||||
revealInCenterIfOutsideViewport: true
|
||||
}
|
||||
}, editor, sideBySide);
|
||||
}
|
||||
|
||||
private _openInPeek(editorService: ICodeEditorService, target: ICodeEditor, model: ReferencesModel) {
|
||||
let controller = ReferencesController.get(target);
|
||||
if (controller && target.hasModel()) {
|
||||
controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), {
|
||||
getMetaTitle: (model) => {
|
||||
return this._getMetaTitle(model);
|
||||
},
|
||||
onGoto: (reference) => {
|
||||
controller.closeWidget();
|
||||
return this._openReference(target, editorService, reference, false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const goToDefinitionKb = platform.isWeb
|
||||
? KeyMod.CtrlCmd | KeyCode.F12
|
||||
: KeyCode.F12;
|
||||
|
||||
export class GoToDefinitionAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDefinition';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(), {
|
||||
id: GoToDefinitionAction.id,
|
||||
label: nls.localize('actions.goToDecl.label', "Go to Definition"),
|
||||
alias: 'Go to Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: goToDefinitionKb,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.1
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenDefinitionToSideAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDefinitionAside';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(true), {
|
||||
id: OpenDefinitionToSideAction.id,
|
||||
label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"),
|
||||
alias: 'Open Definition to the Side',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class PeekDefinitionAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.peekDefinition';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(undefined, true, false), {
|
||||
id: PeekDefinitionAction.id,
|
||||
label: nls.localize('actions.previewDecl.label', "Peek Definition"),
|
||||
alias: 'Peek Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Alt | KeyCode.F12,
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.2
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeclarationAction extends DefinitionAction {
|
||||
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDeclarationsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word)
|
||||
: nls.localize('decl.generic.noResults', "No declaration found");
|
||||
}
|
||||
|
||||
protected _getMetaTitle(model: ReferencesModel): string {
|
||||
return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : '';
|
||||
}
|
||||
}
|
||||
|
||||
export class GoToDeclarationAction extends DeclarationAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDeclaration';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(), {
|
||||
id: GoToDeclarationAction.id,
|
||||
label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"),
|
||||
alias: 'Go to Declaration',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDeclarationProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.3
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word)
|
||||
: nls.localize('decl.generic.noResults', "No declaration found");
|
||||
}
|
||||
|
||||
protected _getMetaTitle(model: ReferencesModel): string {
|
||||
return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : '';
|
||||
}
|
||||
}
|
||||
|
||||
export class PeekDeclarationAction extends DeclarationAction {
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(undefined, true, false), {
|
||||
id: 'editor.action.peekDeclaration',
|
||||
label: nls.localize('actions.peekDecl.label', "Peek Declaration"),
|
||||
alias: 'Peek Declaration',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDeclarationProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.31
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ImplementationAction extends DefinitionAction {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getImplementationsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word)
|
||||
: nls.localize('goToImplementation.generic.noResults', "No implementation found");
|
||||
}
|
||||
|
||||
protected _getMetaTitle(model: ReferencesModel): string {
|
||||
return model.references.length > 1 ? nls.localize('meta.implementations.title', " – {0} implementations", model.references.length) : '';
|
||||
}
|
||||
}
|
||||
|
||||
export class GoToImplementationAction extends ImplementationAction {
|
||||
|
||||
public static readonly ID = 'editor.action.goToImplementation';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(), {
|
||||
id: GoToImplementationAction.ID,
|
||||
label: nls.localize('actions.goToImplementation.label', "Go to Implementation"),
|
||||
alias: 'Go to Implementation',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasImplementationProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PeekImplementationAction extends ImplementationAction {
|
||||
|
||||
public static readonly ID = 'editor.action.peekImplementation';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(false, true, false), {
|
||||
id: PeekImplementationAction.ID,
|
||||
label: nls.localize('actions.peekImplementation.label', "Peek Implementation"),
|
||||
alias: 'Peek Implementation',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasImplementationProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeDefinitionAction extends DefinitionAction {
|
||||
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getTypeDefinitionsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word)
|
||||
: nls.localize('goToTypeDefinition.generic.noResults', "No type definition found");
|
||||
}
|
||||
|
||||
protected _getMetaTitle(model: ReferencesModel): string {
|
||||
return model.references.length > 1 ? nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length) : '';
|
||||
}
|
||||
}
|
||||
|
||||
export class GoToTypeDefinitionAction extends TypeDefinitionAction {
|
||||
|
||||
public static readonly ID = 'editor.action.goToTypeDefinition';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(), {
|
||||
id: GoToTypeDefinitionAction.ID,
|
||||
label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"),
|
||||
alias: 'Go to Type Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasTypeDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: 0,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.4
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PeekTypeDefinitionAction extends TypeDefinitionAction {
|
||||
|
||||
public static readonly ID = 'editor.action.peekTypeDefinition';
|
||||
|
||||
constructor() {
|
||||
super(new DefinitionActionConfig(false, true, false), {
|
||||
id: PeekTypeDefinitionAction.ID,
|
||||
label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"),
|
||||
alias: 'Peek Type Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasTypeDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: 0,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(GoToDefinitionAction);
|
||||
registerEditorAction(OpenDefinitionToSideAction);
|
||||
registerEditorAction(PeekDefinitionAction);
|
||||
registerEditorAction(GoToDeclarationAction);
|
||||
registerEditorAction(PeekDeclarationAction);
|
||||
registerEditorAction(GoToImplementationAction);
|
||||
registerEditorAction(PeekImplementationAction);
|
||||
registerEditorAction(GoToTypeDefinitionAction);
|
||||
registerEditorAction(PeekTypeDefinitionAction);
|
||||
|
||||
// Go to menu
|
||||
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: '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: '4_symbol_nav',
|
||||
command: {
|
||||
id: 'editor.action.goToImplementation',
|
||||
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
@@ -43,7 +43,7 @@ class MarkerModel {
|
||||
this._markers = [];
|
||||
this._nextIdx = -1;
|
||||
this._ignoreSelectionChange = false;
|
||||
this._onCurrentMarkerChanged = new Emitter<IMarker>();
|
||||
this._onCurrentMarkerChanged = new Emitter<IMarker | undefined>();
|
||||
this._onMarkerSetChanged = new Emitter<MarkerModel>();
|
||||
this.setMarkers(markers);
|
||||
|
||||
@@ -245,7 +245,7 @@ export class MarkerController implements editorCommon.IEditorContribution {
|
||||
];
|
||||
this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService);
|
||||
this._widgetVisible.set(true);
|
||||
this._widget.onDidClose(() => this._cleanUp(), this, this._disposeOnClose);
|
||||
this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose);
|
||||
|
||||
this._disposeOnClose.add(this._model);
|
||||
this._disposeOnClose.add(this._widget);
|
||||
|
||||
@@ -20,11 +20,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { getBaseLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
|
||||
import { PeekViewWidget, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/peekView';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget';
|
||||
import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
.monaco-editor .marker-widget .descriptioncontainer {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
padding: 8px 12px 0px 20px;
|
||||
}
|
||||
|
||||
|
||||
781
src/vs/editor/contrib/gotoSymbol/goToCommands.ts
Normal file
781
src/vs/editor/contrib/gotoSymbol/goToCommands.ts
Normal file
@@ -0,0 +1,781 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { ICodeEditor, isCodeEditor, IActiveCodeEditor } 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, IRange } from 'vs/editor/common/core/range';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes';
|
||||
import { MessageController } from 'vs/editor/contrib/message/messageController';
|
||||
import { PeekContext } from 'vs/editor/contrib/peekView/peekView';
|
||||
import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController';
|
||||
import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel';
|
||||
import * as nls from 'vs/nls';
|
||||
import { MenuId, MenuRegistry, ISubmenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from './goToSymbol';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
|
||||
import { ISymbolNavigationService } from 'vs/editor/contrib/gotoSymbol/symbolNavigation';
|
||||
import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions';
|
||||
import { isStandalone } from 'vs/base/browser/browser';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ScrollType, IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorContext, <ISubmenuItem>{
|
||||
submenu: MenuId.EditorContextPeek,
|
||||
title: nls.localize('peek.submenu', "Peek"),
|
||||
group: 'navigation',
|
||||
order: 100
|
||||
});
|
||||
|
||||
export interface SymbolNavigationActionConfig {
|
||||
openToSide: boolean;
|
||||
openInPeek: boolean;
|
||||
muteMessage: boolean;
|
||||
}
|
||||
|
||||
abstract class SymbolNavigationAction extends EditorAction {
|
||||
|
||||
private readonly _configuration: SymbolNavigationActionConfig;
|
||||
|
||||
constructor(configuration: SymbolNavigationActionConfig, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._configuration = configuration;
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
if (!editor.hasModel()) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const notificationService = accessor.get(INotificationService);
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const progressService = accessor.get(IEditorProgressService);
|
||||
const symbolNavService = accessor.get(ISymbolNavigationService);
|
||||
|
||||
const model = editor.getModel();
|
||||
const pos = editor.getPosition();
|
||||
|
||||
const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
|
||||
const promise = raceCancellation(this._getLocationModel(model, pos, cts.token), cts.token).then(async references => {
|
||||
|
||||
if (!references || cts.token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
alert(references.ariaMessage);
|
||||
|
||||
let altAction: IEditorAction | null | undefined;
|
||||
if (references.referenceAt(model.uri, pos)) {
|
||||
const altActionId = this._getAlternativeCommand(editor);
|
||||
if (altActionId !== this.id) {
|
||||
altAction = editor.getAction(altActionId);
|
||||
}
|
||||
}
|
||||
|
||||
const referenceCount = references.references.length;
|
||||
|
||||
if (referenceCount === 0) {
|
||||
// no result -> show message
|
||||
if (!this._configuration.muteMessage) {
|
||||
const info = model.getWordAtPosition(pos);
|
||||
MessageController.get(editor).showMessage(this._getNoResultFoundMessage(info), pos);
|
||||
}
|
||||
} else if (referenceCount === 1 && altAction) {
|
||||
// already at the only result, run alternative
|
||||
altAction.run();
|
||||
|
||||
} else {
|
||||
// normal results handling
|
||||
return this._onResult(editorService, symbolNavService, editor, references);
|
||||
}
|
||||
|
||||
}, (err) => {
|
||||
// report an error
|
||||
notificationService.error(err);
|
||||
}).finally(() => {
|
||||
cts.dispose();
|
||||
});
|
||||
|
||||
progressService.showWhile(promise, 250);
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected abstract _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel | undefined>;
|
||||
|
||||
protected abstract _getNoResultFoundMessage(info: IWordAtPosition | null): string;
|
||||
|
||||
protected abstract _getAlternativeCommand(editor: IActiveCodeEditor): string;
|
||||
|
||||
protected abstract _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues;
|
||||
|
||||
private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: IActiveCodeEditor, model: ReferencesModel): Promise<void> {
|
||||
|
||||
const gotoLocation = this._getGoToPreference(editor);
|
||||
if (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1)) {
|
||||
this._openInPeek(editor, model);
|
||||
|
||||
} else {
|
||||
const next = model.firstReference()!;
|
||||
const peek = model.references.length > 1 && gotoLocation === 'gotoAndPeek';
|
||||
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide, !peek);
|
||||
if (peek && targetEditor) {
|
||||
this._openInPeek(targetEditor, model);
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
|
||||
// keep remaining locations around when using
|
||||
// 'goto'-mode
|
||||
if (gotoLocation === 'goto') {
|
||||
symbolNavService.put(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean, highlight: boolean): Promise<ICodeEditor | undefined> {
|
||||
// range is the target-selection-range when we have one
|
||||
// and the fallback is the 'full' range
|
||||
let range: IRange | undefined = undefined;
|
||||
if (isLocationLink(reference)) {
|
||||
range = reference.targetSelectionRange;
|
||||
}
|
||||
if (!range) {
|
||||
range = reference.range;
|
||||
}
|
||||
|
||||
const targetEditor = await editorService.openCodeEditor({
|
||||
resource: reference.uri,
|
||||
options: {
|
||||
selection: Range.collapseToStart(range),
|
||||
revealInCenterIfOutsideViewport: true
|
||||
}
|
||||
}, editor, sideBySide);
|
||||
|
||||
if (!targetEditor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (highlight) {
|
||||
const modelNow = targetEditor.getModel();
|
||||
const ids = targetEditor.deltaDecorations([], [{ range, options: { className: 'symbolHighlight' } }]);
|
||||
setTimeout(() => {
|
||||
if (targetEditor.getModel() === modelNow) {
|
||||
targetEditor.deltaDecorations(ids, []);
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
|
||||
return targetEditor;
|
||||
}
|
||||
|
||||
private _openInPeek(target: ICodeEditor, model: ReferencesModel) {
|
||||
let controller = ReferencesController.get(target);
|
||||
if (controller && target.hasModel()) {
|
||||
controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), this._configuration.openInPeek);
|
||||
} else {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#region --- DEFINITION
|
||||
|
||||
export class DefinitionAction extends SymbolNavigationAction {
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getDefinitionsAtPosition(model, position, token), nls.localize('def.title', 'Definitions'));
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('noResultWord', "No definition found for '{0}'", info.word)
|
||||
: nls.localize('generic.noResults', "No definition found");
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand(editor: IActiveCodeEditor): string {
|
||||
return editor.getOption(EditorOption.gotoLocation).alternativeDefinitionCommand;
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return editor.getOption(EditorOption.gotoLocation).multipleDefinitions;
|
||||
}
|
||||
}
|
||||
|
||||
const goToDefinitionKb = isWeb && !isStandalone
|
||||
? KeyMod.CtrlCmd | KeyCode.F12
|
||||
: KeyCode.F12;
|
||||
|
||||
registerEditorAction(class GoToDefinitionAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDefinition';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: GoToDefinitionAction.id,
|
||||
label: nls.localize('actions.goToDecl.label', "Go to Definition"),
|
||||
alias: 'Go to Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: goToDefinitionKb,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.1
|
||||
},
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} remove entry
|
||||
menuId: MenuId.MenubarGoMenu,
|
||||
group: '4_symbol_nav',
|
||||
order: 2,
|
||||
title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
|
||||
}*/
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id);
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class OpenDefinitionToSideAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDefinitionAside';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: true,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: OpenDefinitionToSideAction.id,
|
||||
label: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"),
|
||||
alias: 'Open Definition to the Side',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, goToDefinitionKb),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id);
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class PeekDefinitionAction extends DefinitionAction {
|
||||
|
||||
static readonly id = 'editor.action.peekDefinition';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: true,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: PeekDefinitionAction.id,
|
||||
label: nls.localize('actions.previewDecl.label', "Peek Definition"),
|
||||
alias: 'Peek Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDefinitionProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Alt | KeyCode.F12,
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F10 },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
menuId: MenuId.EditorContextPeek,
|
||||
group: 'peek',
|
||||
order: 2
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommandAlias('editor.action.previewDeclaration', PeekDefinitionAction.id);
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --- DECLARATION
|
||||
|
||||
class DeclarationAction extends SymbolNavigationAction {
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getDeclarationsAtPosition(model, position, token), nls.localize('decl.title', 'Declarations'));
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word)
|
||||
: nls.localize('decl.generic.noResults', "No declaration found");
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand(editor: IActiveCodeEditor): string {
|
||||
return editor.getOption(EditorOption.gotoLocation).alternativeDeclarationCommand;
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return editor.getOption(EditorOption.gotoLocation).multipleDeclarations;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(class GoToDeclarationAction extends DeclarationAction {
|
||||
|
||||
static readonly id = 'editor.action.revealDeclaration';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: GoToDeclarationAction.id,
|
||||
label: nls.localize('actions.goToDeclaration.label', "Go to Declaration"),
|
||||
alias: 'Go to Declaration',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDeclarationProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.3
|
||||
},
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} remove entry
|
||||
menuId: MenuId.MenubarGoMenu,
|
||||
group: '4_symbol_nav',
|
||||
order: 3,
|
||||
title: nls.localize({ key: 'miGotoDeclaration', comment: ['&& denotes a mnemonic'] }, "Go to &&Declaration")
|
||||
},*/
|
||||
});
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word)
|
||||
: nls.localize('decl.generic.noResults', "No declaration found");
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class PeekDeclarationAction extends DeclarationAction {
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: true,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: 'editor.action.peekDeclaration',
|
||||
label: nls.localize('actions.peekDecl.label', "Peek Declaration"),
|
||||
alias: 'Peek Declaration',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasDeclarationProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
contextMenuOpts: {
|
||||
menuId: MenuId.EditorContextPeek,
|
||||
group: 'peek',
|
||||
order: 3
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --- TYPE DEFINITION
|
||||
|
||||
class TypeDefinitionAction extends SymbolNavigationAction {
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getTypeDefinitionsAtPosition(model, position, token), nls.localize('typedef.title', 'Type Definitions'));
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word)
|
||||
: nls.localize('goToTypeDefinition.generic.noResults', "No type definition found");
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand(editor: IActiveCodeEditor): string {
|
||||
return editor.getOption(EditorOption.gotoLocation).alternativeTypeDefinitionCommand;
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return editor.getOption(EditorOption.gotoLocation).multipleTypeDefinitions;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(class GoToTypeDefinitionAction extends TypeDefinitionAction {
|
||||
|
||||
public static readonly ID = 'editor.action.goToTypeDefinition';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: GoToTypeDefinitionAction.ID,
|
||||
label: nls.localize('actions.goToTypeDefinition.label', "Go to Type Definition"),
|
||||
alias: 'Go to Type Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasTypeDefinitionProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: 0,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.4
|
||||
},
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} remove entry
|
||||
menuId: MenuId.MenubarGoMenu,
|
||||
group: '4_symbol_nav',
|
||||
order: 3,
|
||||
title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
|
||||
}*/
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction {
|
||||
|
||||
public static readonly ID = 'editor.action.peekTypeDefinition';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: true,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: PeekTypeDefinitionAction.ID,
|
||||
label: nls.localize('actions.peekTypeDefinition.label', "Peek Type Definition"),
|
||||
alias: 'Peek Type Definition',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasTypeDefinitionProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
contextMenuOpts: {
|
||||
menuId: MenuId.EditorContextPeek,
|
||||
group: 'peek',
|
||||
order: 4
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --- IMPLEMENTATION
|
||||
|
||||
class ImplementationAction extends SymbolNavigationAction {
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getImplementationsAtPosition(model, position, token), nls.localize('impl.title', 'Implementations'));
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && info.word
|
||||
? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word)
|
||||
: nls.localize('goToImplementation.generic.noResults', "No implementation found");
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand(editor: IActiveCodeEditor): string {
|
||||
return editor.getOption(EditorOption.gotoLocation).alternativeImplementationCommand;
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return editor.getOption(EditorOption.gotoLocation).multipleImplementations;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(class GoToImplementationAction extends ImplementationAction {
|
||||
|
||||
public static readonly ID = 'editor.action.goToImplementation';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: GoToImplementationAction.ID,
|
||||
label: nls.localize('actions.goToImplementation.label', "Go to Implementations"),
|
||||
alias: 'Go to Implementations',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasImplementationProvider,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} remove entry
|
||||
menuId: MenuId.MenubarGoMenu,
|
||||
group: '4_symbol_nav',
|
||||
order: 4,
|
||||
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations")
|
||||
},*/
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.45
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class PeekImplementationAction extends ImplementationAction {
|
||||
|
||||
public static readonly ID = 'editor.action.peekImplementation';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: true,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: PeekImplementationAction.ID,
|
||||
label: nls.localize('actions.peekImplementation.label', "Peek Implementations"),
|
||||
alias: 'Peek Implementations',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasImplementationProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
menuId: MenuId.EditorContextPeek,
|
||||
group: 'peek',
|
||||
order: 5
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --- REFERENCES
|
||||
|
||||
abstract class ReferencesAction extends SymbolNavigationAction {
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info
|
||||
? nls.localize('references.no', "No references found for '{0}'", info.word)
|
||||
: nls.localize('references.noGeneric', "No references found");
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand(editor: IActiveCodeEditor): string {
|
||||
return editor.getOption(EditorOption.gotoLocation).alternativeReferenceCommand;
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return editor.getOption(EditorOption.gotoLocation).multipleReferences;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(class GoToReferencesAction extends ReferencesAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: false,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: 'editor.action.goToReferences',
|
||||
label: nls.localize('goToReferences.label', "Go to References"),
|
||||
alias: 'Go to References',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasReferenceProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Shift | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.45
|
||||
},
|
||||
/*menuOpts: { {{SQL CARBON EDIT}} remove entry
|
||||
menuId: MenuId.MenubarGoMenu,
|
||||
group: '4_symbol_nav',
|
||||
order: 5,
|
||||
title: nls.localize({ key: 'miGotoReference', comment: ['&& denotes a mnemonic'] }, "Go to &&References")
|
||||
},*/
|
||||
});
|
||||
}
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getReferencesAtPosition(model, position, true, token), nls.localize('ref.title', 'References'));
|
||||
}
|
||||
});
|
||||
|
||||
registerEditorAction(class PeekReferencesAction extends ReferencesAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
openToSide: false,
|
||||
openInPeek: true,
|
||||
muteMessage: false
|
||||
}, {
|
||||
id: 'editor.action.referenceSearch.trigger',
|
||||
label: nls.localize('references.action.label', "Peek References"),
|
||||
alias: 'Peek References',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasReferenceProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
contextMenuOpts: {
|
||||
menuId: MenuId.EditorContextPeek,
|
||||
group: 'peek',
|
||||
order: 6
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
|
||||
return new ReferencesModel(await getReferencesAtPosition(model, position, false, token), nls.localize('ref.title', 'References'));
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region --- GENERIC goto symbols command
|
||||
|
||||
class GenericGoToLocationAction extends SymbolNavigationAction {
|
||||
|
||||
constructor(
|
||||
private readonly _references: Location[],
|
||||
private readonly _gotoMultipleBehaviour: GoToLocationValues | undefined
|
||||
) {
|
||||
super({
|
||||
muteMessage: true,
|
||||
openInPeek: false,
|
||||
openToSide: false
|
||||
}, {
|
||||
id: 'editor.action.goToLocation',
|
||||
label: nls.localize('label.generic', "Go To Any Symbol"),
|
||||
alias: 'Go To Any Symbol',
|
||||
precondition: ContextKeyExpr.and(
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
protected async _getLocationModel(_model: ITextModel, _position: corePosition.Position, _token: CancellationToken): Promise<ReferencesModel | undefined> {
|
||||
return new ReferencesModel(this._references, nls.localize('generic.title', 'Locations'));
|
||||
}
|
||||
|
||||
protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
|
||||
return info && nls.localize('generic.noResult', "No results for '{0}'", info.word) || '';
|
||||
}
|
||||
|
||||
protected _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues {
|
||||
return this._gotoMultipleBehaviour ?? editor.getOption(EditorOption.gotoLocation).multipleReferences;
|
||||
}
|
||||
|
||||
protected _getAlternativeCommand() { return ''; }
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'editor.action.goToLocations',
|
||||
description: {
|
||||
description: 'Go to locations from a position in a file',
|
||||
args: [
|
||||
{ name: 'uri', description: 'The text document in which to start', constraint: URI },
|
||||
{ name: 'position', description: 'The position at which to start', constraint: corePosition.Position.isIPosition },
|
||||
{ name: 'locations', description: 'An array of locations.', constraint: Array },
|
||||
{ name: 'multiple', description: 'Define what to do when having multiple results, either `peek`, `gotoAndPeek`, or `goto' },
|
||||
]
|
||||
},
|
||||
handler: async (accessor: ServicesAccessor, resource: any, position: any, references: any, multiple?: any) => {
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(corePosition.Position.isIPosition(position));
|
||||
assertType(Array.isArray(references));
|
||||
assertType(typeof multiple === 'undefined' || typeof multiple === 'string');
|
||||
|
||||
const editorService = accessor.get(ICodeEditorService);
|
||||
const editor = await editorService.openCodeEditor({ resource }, editorService.getFocusedCodeEditor());
|
||||
|
||||
if (isCodeEditor(editor)) {
|
||||
editor.setPosition(position);
|
||||
editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth);
|
||||
|
||||
return editor.invokeWithinContext(accessor => {
|
||||
const command = new GenericGoToLocationAction(references, multiple as GoToLocationValues);
|
||||
accessor.get(IInstantiationService).invokeFunction(command.run.bind(command), editor);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region --- REFERENCE search special commands
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'editor.action.findReferences',
|
||||
handler: (accessor: ServicesAccessor, resource: any, position: any) => {
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(corePosition.Position.isIPosition(position));
|
||||
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => {
|
||||
if (!isCodeEditor(control) || !control.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const controller = ReferencesController.get(control);
|
||||
if (!controller) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const references = createCancelablePromise(token => getReferencesAtPosition(control.getModel(), corePosition.Position.lift(position), false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References'))));
|
||||
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
|
||||
return Promise.resolve(controller.toggleWidget(range, references, false));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// use NEW command
|
||||
CommandsRegistry.registerCommandAlias('editor.action.showReferences', 'editor.action.goToLocations');
|
||||
|
||||
//#endregion
|
||||
@@ -9,11 +9,11 @@ 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 { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult } from 'vs/editor/common/modes';
|
||||
import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
|
||||
|
||||
|
||||
function getDefinitions<T>(
|
||||
function getLocationLinks<T>(
|
||||
model: ITextModel,
|
||||
position: Position,
|
||||
registry: LanguageFeatureRegistry<T>,
|
||||
@@ -35,30 +35,45 @@ function getDefinitions<T>(
|
||||
|
||||
|
||||
export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position) => {
|
||||
return getLocationLinks(model, position, DefinitionProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideDefinition(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, DeclarationProviderRegistry, (provider, model, position) => {
|
||||
return getLocationLinks(model, position, DeclarationProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideDeclaration(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position) => {
|
||||
return getLocationLinks(model, position, ImplementationProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideImplementation(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => {
|
||||
return getLocationLinks(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => {
|
||||
return provider.provideTypeDefinition(model, position, token);
|
||||
});
|
||||
}
|
||||
|
||||
export function getReferencesAtPosition(model: ITextModel, position: Position, compact: boolean, token: CancellationToken): Promise<LocationLink[]> {
|
||||
return getLocationLinks(model, position, ReferenceProviderRegistry, async (provider, model, position) => {
|
||||
const result = await provider.provideReferences(model, position, { includeDeclaration: true }, token);
|
||||
if (!compact || !result || result.length !== 2) {
|
||||
return result;
|
||||
}
|
||||
const resultWithoutDeclaration = await provider.provideReferences(model, position, { includeDeclaration: false }, token);
|
||||
if (resultWithoutDeclaration && resultWithoutDeclaration.length === 1) {
|
||||
return resultWithoutDeclaration;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None));
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./goToDefinitionMouse';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./goToDefinitionMouse';
|
||||
import 'vs/css!./goToDefinitionAtPosition';
|
||||
import * as nls from 'vs/nls';
|
||||
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -13,29 +13,31 @@ import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes';
|
||||
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { getDefinitionsAtPosition } from './goToDefinition';
|
||||
import { getDefinitionsAtPosition } from '../goToSymbol';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
|
||||
import { DefinitionAction, DefinitionActionConfig } from './goToDefinitionCommands';
|
||||
import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture';
|
||||
import { DefinitionAction } from '../goToCommands';
|
||||
import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture';
|
||||
import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution {
|
||||
export class GotoDefinitionAtPositionEditorContribution implements editorCommon.IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.gotodefinitionwithmouse';
|
||||
public static readonly ID = 'editor.contrib.gotodefinitionatposition';
|
||||
static readonly MAX_SOURCE_PREVIEW_LINES = 8;
|
||||
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly toUnhook = new DisposableStore();
|
||||
private decorations: string[] = [];
|
||||
private currentWordUnderMouse: IWordAtPosition | null = null;
|
||||
private readonly toUnhookForKeyboard = new DisposableStore();
|
||||
private linkDecorations: string[] = [];
|
||||
private currentWordAtPosition: IWordAtPosition | null = null;
|
||||
private previousPromise: CancelablePromise<LocationLink[] | null> | null = null;
|
||||
|
||||
constructor(
|
||||
@@ -49,55 +51,96 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
this.toUnhook.add(linkGesture);
|
||||
|
||||
this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => {
|
||||
this.startFindDefinition(mouseEvent, withNullAsUndefined(keyboardEvent));
|
||||
this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent));
|
||||
}));
|
||||
|
||||
this.toUnhook.add(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => {
|
||||
if (this.isEnabled(mouseEvent)) {
|
||||
this.gotoDefinition(mouseEvent.target, mouseEvent.hasSideBySideModifier).then(() => {
|
||||
this.removeDecorations();
|
||||
this.gotoDefinition(mouseEvent.target.position!, mouseEvent.hasSideBySideModifier).then(() => {
|
||||
this.removeLinkDecorations();
|
||||
}, (error: Error) => {
|
||||
this.removeDecorations();
|
||||
this.removeLinkDecorations();
|
||||
onUnexpectedError(error);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this.toUnhook.add(linkGesture.onCancel(() => {
|
||||
this.removeDecorations();
|
||||
this.currentWordUnderMouse = null;
|
||||
this.removeLinkDecorations();
|
||||
this.currentWordAtPosition = null;
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
private startFindDefinition(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void {
|
||||
static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution {
|
||||
return editor.getContribution<GotoDefinitionAtPositionEditorContribution>(GotoDefinitionAtPositionEditorContribution.ID);
|
||||
}
|
||||
|
||||
startFindDefinitionFromCursor(position: Position) {
|
||||
// For issue: https://github.com/microsoft/vscode/issues/46257
|
||||
// equivalent to mouse move with meta/ctrl key
|
||||
|
||||
// First find the definition and add decorations
|
||||
// to the editor to be shown with the content hover widget
|
||||
return this.startFindDefinition(position).then(() => {
|
||||
|
||||
// Add listeners for editor cursor move and key down events
|
||||
// Dismiss the "extended" editor decorations when the user hides
|
||||
// the hover widget. There is no event for the widget itself so these
|
||||
// serve as a best effort. After removing the link decorations, the hover
|
||||
// widget is clean and will only show declarations per next request.
|
||||
this.toUnhookForKeyboard.add(this.editor.onDidChangeCursorPosition(() => {
|
||||
this.currentWordAtPosition = null;
|
||||
this.removeLinkDecorations();
|
||||
this.toUnhookForKeyboard.clear();
|
||||
}));
|
||||
|
||||
this.toUnhookForKeyboard.add(this.editor.onKeyDown((e: IKeyboardEvent) => {
|
||||
if (e) {
|
||||
this.currentWordAtPosition = null;
|
||||
this.removeLinkDecorations();
|
||||
this.toUnhookForKeyboard.clear();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private startFindDefinitionFromMouse(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void {
|
||||
|
||||
// check if we are active and on a content widget
|
||||
if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.decorations.length > 0) {
|
||||
if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.linkDecorations.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) {
|
||||
this.currentWordUnderMouse = null;
|
||||
this.removeDecorations();
|
||||
this.currentWordAtPosition = null;
|
||||
this.removeLinkDecorations();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find word at mouse position
|
||||
const word = mouseEvent.target.position ? this.editor.getModel().getWordAtPosition(mouseEvent.target.position) : null;
|
||||
if (!word) {
|
||||
this.currentWordUnderMouse = null;
|
||||
this.removeDecorations();
|
||||
return;
|
||||
}
|
||||
const position = mouseEvent.target.position!;
|
||||
|
||||
// Return early if word at position is still the same
|
||||
if (this.currentWordUnderMouse && this.currentWordUnderMouse.startColumn === word.startColumn && this.currentWordUnderMouse.endColumn === word.endColumn && this.currentWordUnderMouse.word === word.word) {
|
||||
return;
|
||||
this.startFindDefinition(position);
|
||||
}
|
||||
|
||||
private startFindDefinition(position: Position): Promise<number | undefined> {
|
||||
|
||||
// Dispose listeners for updating decorations when using keyboard to show definition hover
|
||||
this.toUnhookForKeyboard.clear();
|
||||
|
||||
// Find word at mouse position
|
||||
const word = position ? this.editor.getModel()?.getWordAtPosition(position) : null;
|
||||
if (!word) {
|
||||
this.currentWordAtPosition = null;
|
||||
this.removeLinkDecorations();
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
this.currentWordUnderMouse = word;
|
||||
// Return early if word at position is still the same
|
||||
if (this.currentWordAtPosition && this.currentWordAtPosition.startColumn === word.startColumn && this.currentWordAtPosition.endColumn === word.endColumn && this.currentWordAtPosition.word === word.word) {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
this.currentWordAtPosition = word;
|
||||
|
||||
// Find definition and decorate word if found
|
||||
let state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll);
|
||||
@@ -107,11 +150,11 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
this.previousPromise = null;
|
||||
}
|
||||
|
||||
this.previousPromise = createCancelablePromise(token => this.findDefinition(mouseEvent.target, token));
|
||||
this.previousPromise = createCancelablePromise(token => this.findDefinition(position, token));
|
||||
|
||||
this.previousPromise.then(results => {
|
||||
return this.previousPromise.then(results => {
|
||||
if (!results || !results.length || !state.validate(this.editor)) {
|
||||
this.removeDecorations();
|
||||
this.removeLinkDecorations();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -170,7 +213,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number, result: LocationLink) {
|
||||
let rangeToUse = result.targetSelectionRange ? result.range : this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber);
|
||||
const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber;
|
||||
if (numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) {
|
||||
if (numberOfLinesInRange >= GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES) {
|
||||
rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber);
|
||||
}
|
||||
|
||||
@@ -193,7 +236,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
|
||||
private getPreviewRangeBasedOnIndentation(textEditorModel: ITextModel, startLineNumber: number) {
|
||||
const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber);
|
||||
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES);
|
||||
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES);
|
||||
let endLineNumber = startLineNumber + 1;
|
||||
|
||||
for (; endLineNumber < maxLineNumber; endLineNumber++) {
|
||||
@@ -208,7 +251,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
}
|
||||
|
||||
private getPreviewRangeBasedOnBrackets(textEditorModel: ITextModel, startLineNumber: number) {
|
||||
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES);
|
||||
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES);
|
||||
|
||||
const brackets: IFoundBracket[] = [];
|
||||
|
||||
@@ -263,12 +306,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
}
|
||||
};
|
||||
|
||||
this.decorations = this.editor.deltaDecorations(this.decorations, [newDecorations]);
|
||||
this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, [newDecorations]);
|
||||
}
|
||||
|
||||
private removeDecorations(): void {
|
||||
if (this.decorations.length > 0) {
|
||||
this.decorations = this.editor.deltaDecorations(this.decorations, []);
|
||||
private removeLinkDecorations(): void {
|
||||
if (this.linkDecorations.length > 0) {
|
||||
this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,18 +323,18 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
DefinitionProviderRegistry.has(this.editor.getModel());
|
||||
}
|
||||
|
||||
private findDefinition(target: IMouseTarget, token: CancellationToken): Promise<LocationLink[] | null> {
|
||||
private findDefinition(position: Position, token: CancellationToken): Promise<LocationLink[] | null> {
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return getDefinitionsAtPosition(model, target.position!, token);
|
||||
return getDefinitionsAtPosition(model, position, token);
|
||||
}
|
||||
|
||||
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise<any> {
|
||||
this.editor.setPosition(target.position!);
|
||||
const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: undefined });
|
||||
private gotoDefinition(position: Position, openToSide: boolean): Promise<any> {
|
||||
this.editor.setPosition(position);
|
||||
const action = new DefinitionAction({ openToSide, openInPeek: false, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined });
|
||||
return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor));
|
||||
}
|
||||
|
||||
@@ -300,7 +343,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(GotoDefinitionWithMouseEditorContribution.ID, GotoDefinitionWithMouseEditorContribution);
|
||||
registerEditorContribution(GotoDefinitionAtPositionEditorContribution.ID, GotoDefinitionAtPositionEditorContribution);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const activeLinkForeground = theme.getColor(editorActiveLinkForeground);
|
||||
@@ -7,47 +7,47 @@ import * as nls from 'vs/nls';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ReferencesModel } from './referencesModel';
|
||||
import { ReferencesModel, OneReference } from '../referencesModel';
|
||||
import { ReferenceWidget, LayoutData } from './referencesWidget';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Location } from 'vs/editor/common/modes';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CancelablePromise } from 'vs/base/common/async';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView';
|
||||
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSearchVisible', false);
|
||||
|
||||
export interface RequestOptions {
|
||||
getMetaTitle(model: ReferencesModel): string;
|
||||
onGoto?: (reference: Location) => Promise<any>;
|
||||
}
|
||||
|
||||
export abstract class ReferencesController implements editorCommon.IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.referencesController';
|
||||
static readonly ID = 'editor.contrib.referencesController';
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _editor: ICodeEditor;
|
||||
|
||||
private _widget?: ReferenceWidget;
|
||||
private _model?: ReferencesModel;
|
||||
private _peekMode?: boolean;
|
||||
private _requestIdPool = 0;
|
||||
private _ignoreModelChangeEvent = false;
|
||||
|
||||
private readonly _referenceSearchVisible: IContextKey<boolean>;
|
||||
|
||||
public static get(editor: ICodeEditor): ReferencesController {
|
||||
static get(editor: ICodeEditor): ReferencesController {
|
||||
return editor.getContribution<ReferencesController>(ReferencesController.ID);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
private readonly _defaultTreeKeyboardSupport: boolean,
|
||||
editor: ICodeEditor,
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@@ -55,24 +55,20 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
this._editor = editor;
|
||||
|
||||
this._referenceSearchVisible = ctxReferenceSearchVisible.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this._referenceSearchVisible.reset();
|
||||
dispose(this._disposables);
|
||||
if (this._widget) {
|
||||
dispose(this._widget);
|
||||
this._widget = undefined;
|
||||
}
|
||||
if (this._model) {
|
||||
dispose(this._model);
|
||||
this._model = undefined;
|
||||
}
|
||||
this._disposables.dispose();
|
||||
dispose(this._widget);
|
||||
dispose(this._model);
|
||||
this._widget = undefined;
|
||||
this._model = undefined;
|
||||
}
|
||||
|
||||
public toggleWidget(range: Range, modelPromise: CancelablePromise<ReferencesModel>, options: RequestOptions): void {
|
||||
toggleWidget(range: Range, modelPromise: CancelablePromise<ReferencesModel>, peekMode: boolean): void {
|
||||
|
||||
// close current widget and return early is position didn't change
|
||||
let widgetPosition: Position | undefined;
|
||||
@@ -84,6 +80,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
return;
|
||||
}
|
||||
|
||||
this._peekMode = peekMode;
|
||||
this._referenceSearchVisible.set(true);
|
||||
|
||||
// close the widget on model/mode changes
|
||||
@@ -110,27 +107,25 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
|
||||
this._disposables.add(this._widget.onDidSelectReference(event => {
|
||||
let { element, kind } = event;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
switch (kind) {
|
||||
case 'open':
|
||||
if (event.source === 'editor'
|
||||
&& this._configurationService.getValue('editor.stablePeek')) {
|
||||
|
||||
if (event.source !== 'editor' || !this._configurationService.getValue('editor.stablePeek')) {
|
||||
// when stable peek is configured we don't close
|
||||
// the peek window on selecting the editor
|
||||
break;
|
||||
}
|
||||
case 'side':
|
||||
if (element) {
|
||||
this.openReference(element, kind === 'side');
|
||||
this.openReference(element, false);
|
||||
}
|
||||
break;
|
||||
case 'side':
|
||||
this.openReference(element, true);
|
||||
break;
|
||||
case 'goto':
|
||||
if (element) {
|
||||
if (options.onGoto) {
|
||||
options.onGoto(element);
|
||||
} else {
|
||||
this._gotoReference(element);
|
||||
}
|
||||
if (peekMode) {
|
||||
this._gotoReference(element);
|
||||
} else {
|
||||
this.openReference(element, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -154,8 +149,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
// show widget
|
||||
return this._widget.setModel(this._model).then(() => {
|
||||
if (this._widget && this._model && this._editor.hasModel()) { // might have been closed
|
||||
|
||||
// set title
|
||||
this._widget.setMetaTitle(options.getMetaTitle(this._model));
|
||||
if (!this._model.isEmpty) {
|
||||
this._widget.setMetaTitle(nls.localize('metaTitle.N', "{0} ({1})", this._model.title, this._model.references.length));
|
||||
} else {
|
||||
this._widget.setMetaTitle('');
|
||||
}
|
||||
|
||||
// set 'best' selection
|
||||
let uri = this._editor.getModel().uri;
|
||||
@@ -173,7 +173,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
});
|
||||
}
|
||||
|
||||
public async goToNextOrPreviousReference(fwd: boolean) {
|
||||
async goToNextOrPreviousReference(fwd: boolean) {
|
||||
if (!this._editor.hasModel() || !this._model || !this._widget) {
|
||||
// can be called while still resolving...
|
||||
return;
|
||||
@@ -195,17 +195,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
}
|
||||
}
|
||||
|
||||
public closeWidget(): void {
|
||||
if (this._widget) {
|
||||
dispose(this._widget);
|
||||
this._widget = undefined;
|
||||
}
|
||||
closeWidget(): void {
|
||||
this._referenceSearchVisible.reset();
|
||||
this._disposables.clear();
|
||||
if (this._model) {
|
||||
dispose(this._model);
|
||||
this._model = undefined;
|
||||
}
|
||||
dispose(this._widget);
|
||||
dispose(this._model);
|
||||
this._widget = undefined;
|
||||
this._model = undefined;
|
||||
this._editor.focus();
|
||||
this._requestIdPool += 1; // Cancel pending requests
|
||||
}
|
||||
@@ -224,21 +220,31 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
}, this._editor).then(openedEditor => {
|
||||
this._ignoreModelChangeEvent = false;
|
||||
|
||||
if (!openedEditor || openedEditor !== this._editor) {
|
||||
// TODO@Alex TODO@Joh
|
||||
// when opening the current reference we might end up
|
||||
// in a different editor instance. that means we also have
|
||||
// a different instance of this reference search controller
|
||||
// and cannot hold onto the widget (which likely doesn't
|
||||
// exist). Instead of bailing out we should find the
|
||||
// 'sister' action and pass our current model on to it.
|
||||
if (!openedEditor || !this._widget) {
|
||||
// something went wrong...
|
||||
this.closeWidget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._widget) {
|
||||
if (this._editor === openedEditor) {
|
||||
//
|
||||
this._widget.show(range);
|
||||
this._widget.focus();
|
||||
|
||||
} else {
|
||||
// we opened a different editor instance which means a different controller instance.
|
||||
// therefore we stop with this controller and continue with the other
|
||||
const other = ReferencesController.get(openedEditor);
|
||||
const model = this._model!.clone();
|
||||
|
||||
this.closeWidget();
|
||||
openedEditor.focus();
|
||||
|
||||
other.toggleWidget(
|
||||
range,
|
||||
createCancelablePromise(_ => Promise.resolve(model)),
|
||||
this._peekMode ?? false
|
||||
);
|
||||
}
|
||||
|
||||
}, (err) => {
|
||||
@@ -247,7 +253,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
});
|
||||
}
|
||||
|
||||
public openReference(ref: Location, sideBySide: boolean): void {
|
||||
openReference(ref: Location, sideBySide: boolean): void {
|
||||
// clear stage
|
||||
if (!sideBySide) {
|
||||
this.closeWidget();
|
||||
@@ -260,3 +266,105 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
}, this._editor, sideBySide);
|
||||
}
|
||||
}
|
||||
|
||||
function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void {
|
||||
const outerEditor = getOuterEditor(accessor);
|
||||
if (!outerEditor) {
|
||||
return;
|
||||
}
|
||||
let controller = ReferencesController.get(outerEditor);
|
||||
if (controller) {
|
||||
fn(controller);
|
||||
}
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToNextReference',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyCode.F4,
|
||||
secondary: [KeyCode.F12],
|
||||
when: ctxReferenceSearchVisible,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToNextReferenceFromEmbeddedEditor',
|
||||
weight: KeybindingWeight.EditorContrib + 50,
|
||||
primary: KeyCode.F4,
|
||||
secondary: [KeyCode.F12],
|
||||
when: PeekContext.inPeekEditor,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToPreviousReference',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyMod.Shift | KeyCode.F4,
|
||||
secondary: [KeyMod.Shift | KeyCode.F12],
|
||||
when: ctxReferenceSearchVisible,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToPreviousReferenceFromEmbeddedEditor',
|
||||
weight: KeybindingWeight.EditorContrib + 50,
|
||||
primary: KeyMod.Shift | KeyCode.F4,
|
||||
secondary: [KeyMod.Shift | KeyCode.F12],
|
||||
when: PeekContext.inPeekEditor,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'closeReferenceSearch',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
withController(accessor, controller => controller.closeWidget());
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'closeReferenceSearchEditor',
|
||||
weight: KeybindingWeight.EditorContrib - 101,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
withController(accessor, controller => controller.closeWidget());
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'openReferenceToSide',
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
mac: {
|
||||
primary: KeyMod.WinCtrl | KeyCode.Enter
|
||||
},
|
||||
when: ContextKeyExpr.and(ctxReferenceSearchVisible, WorkbenchListFocusContextKey),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
const listService = accessor.get(IListService);
|
||||
const focus = <any[]>listService.lastFocusedList?.getFocus();
|
||||
if (Array.isArray(focus) && focus[0] instanceof OneReference) {
|
||||
withController(accessor, controller => controller.openReference(focus[0], true));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ReferencesModel, FileReferences, OneReference } from './referencesModel';
|
||||
import { ReferencesModel, FileReferences, OneReference } from '../referencesModel';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
@@ -101,7 +101,7 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro
|
||||
export class IdentityProvider implements IIdentityProvider<TreeElement> {
|
||||
|
||||
getId(element: TreeElement): { toString(): string; } {
|
||||
return element.id;
|
||||
return element instanceof OneReference ? element.id : element.uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +216,6 @@ export class OneReferenceRenderer implements ITreeRenderer<OneReference, FuzzySc
|
||||
export class AriaProvider implements IAccessibilityProvider<FileReferences | OneReference> {
|
||||
|
||||
getAriaLabel(element: FileReferences | OneReference): string | null {
|
||||
if (element instanceof FileReferences) {
|
||||
return element.getAriaMessage();
|
||||
} else if (element instanceof OneReference) {
|
||||
return element.getAriaMessage();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return element.ariaMessage;
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./referencesWidget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { GestureEvent } from 'vs/base/browser/touch';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable, IReference, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { basenameOrAuthority, dirname, isEqual } from 'vs/base/common/resources';
|
||||
import 'vs/css!./media/referencesWidget';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -21,19 +22,15 @@ 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, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/referenceSearch/referencesTree';
|
||||
import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree';
|
||||
import * as nls from 'vs/nls';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
|
||||
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { PeekViewWidget, IPeekViewService } 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 * as peekView from 'vs/editor/contrib/peekView/peekView';
|
||||
import { FileReferences, OneReference, ReferencesModel } from '../referencesModel';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
|
||||
@@ -55,7 +52,7 @@ class DecorationsManager implements IDisposable {
|
||||
this._onModelChanged();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this._callOnModelChange.dispose();
|
||||
this._callOnDispose.dispose();
|
||||
this.removeDecorations();
|
||||
@@ -147,7 +144,7 @@ class DecorationsManager implements IDisposable {
|
||||
this._editor.deltaDecorations(toRemove, []);
|
||||
}
|
||||
|
||||
public removeDecorations(): void {
|
||||
removeDecorations(): void {
|
||||
let toRemove: string[] = [];
|
||||
this._decorations.forEach((value, key) => {
|
||||
toRemove.push(key);
|
||||
@@ -158,8 +155,8 @@ class DecorationsManager implements IDisposable {
|
||||
}
|
||||
|
||||
export class LayoutData {
|
||||
public ratio: number = 0.7;
|
||||
public heightInLines: number = 18;
|
||||
ratio: number = 0.7;
|
||||
heightInLines: number = 18;
|
||||
|
||||
static fromJSON(raw: string): LayoutData {
|
||||
let ratio: number | undefined;
|
||||
@@ -184,19 +181,19 @@ export interface SelectionEvent {
|
||||
readonly element?: Location;
|
||||
}
|
||||
|
||||
export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey<boolean>('referenceSearchTreeFocused', true);
|
||||
|
||||
/**
|
||||
* ZoneWidget that is shown inside the editor
|
||||
*/
|
||||
export class ReferenceWidget extends PeekViewWidget {
|
||||
export class ReferenceWidget extends peekView.PeekViewWidget {
|
||||
|
||||
private _model?: ReferencesModel;
|
||||
private _decorationsManager?: DecorationsManager;
|
||||
|
||||
private readonly _disposeOnNewModel = new DisposableStore();
|
||||
private readonly _callOnDispose = new DisposableStore();
|
||||
|
||||
private readonly _onDidSelectReference = new Emitter<SelectionEvent>();
|
||||
readonly onDidSelectReference = this._onDidSelectReference.event;
|
||||
|
||||
private _tree!: WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>;
|
||||
private _treeContainer!: HTMLElement;
|
||||
@@ -215,7 +212,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITextModelService private readonly _textModelResolverService: ITextModelService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IPeekViewService private readonly _peekViewService: IPeekViewService,
|
||||
@peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService,
|
||||
@ILabelService private readonly _uriLabel: ILabelService
|
||||
) {
|
||||
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true });
|
||||
@@ -239,20 +236,16 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme) {
|
||||
const borderColor = theme.getColor(peekViewBorder) || Color.transparent;
|
||||
const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent;
|
||||
this.style({
|
||||
arrowColor: borderColor,
|
||||
frameColor: borderColor,
|
||||
headerBackgroundColor: theme.getColor(peekViewTitleBackground) || Color.transparent,
|
||||
primaryHeadingColor: theme.getColor(peekViewTitleForeground),
|
||||
secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground)
|
||||
headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent,
|
||||
primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground),
|
||||
secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground)
|
||||
});
|
||||
}
|
||||
|
||||
get onDidSelectReference(): Event<SelectionEvent> {
|
||||
return this._onDidSelectReference.event;
|
||||
}
|
||||
|
||||
show(where: IRange) {
|
||||
this.editor.revealRangeInCenterIfOutsideViewport(where, editorCommon.ScrollType.Smooth);
|
||||
super.show(where, this.layoutData.heightInLines || 18);
|
||||
@@ -304,14 +297,17 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
|
||||
// tree
|
||||
this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline'));
|
||||
const treeOptions: IAsyncDataTreeOptions<TreeElement, FuzzyScore> = {
|
||||
const treeOptions: IWorkbenchAsyncDataTreeOptions<TreeElement, FuzzyScore> = {
|
||||
ariaLabel: nls.localize('treeAriaLabel', "References"),
|
||||
keyboardSupport: this._defaultTreeKeyboardSupport,
|
||||
accessibilityProvider: new AriaProvider(),
|
||||
keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider),
|
||||
identityProvider: new IdentityProvider()
|
||||
identityProvider: new IdentityProvider(),
|
||||
overrideStyles: {
|
||||
listBackground: peekView.peekViewResultsBackground
|
||||
}
|
||||
};
|
||||
this._tree = this._instantiationService.createInstance<string, HTMLElement, IListVirtualDelegate<TreeElement>, ITreeRenderer<any, FuzzyScore, any>[], IAsyncDataSource<ReferencesModel | FileReferences, TreeElement>, IAsyncDataTreeOptions<TreeElement, FuzzyScore>, WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>>(
|
||||
this._tree = this._instantiationService.createInstance<typeof WorkbenchAsyncDataTree, WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>>(
|
||||
WorkbenchAsyncDataTree,
|
||||
'ReferencesWidget',
|
||||
this._treeContainer,
|
||||
@@ -321,9 +317,8 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
this._instantiationService.createInstance(OneReferenceRenderer),
|
||||
],
|
||||
this._instantiationService.createInstance(DataSource),
|
||||
treeOptions
|
||||
treeOptions,
|
||||
);
|
||||
ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService);
|
||||
|
||||
// split stuff
|
||||
this._splitView.addView({
|
||||
@@ -367,17 +362,17 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
onEvent(e.elements[0], 'show');
|
||||
});
|
||||
this._tree.onDidOpen(e => {
|
||||
const aside = (e.browserEvent instanceof MouseEvent) && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey);
|
||||
let goto = !e.browserEvent || ((e.browserEvent instanceof MouseEvent) && e.browserEvent.detail === 2);
|
||||
if (e.browserEvent instanceof KeyboardEvent) {
|
||||
// todo@joh make this a command
|
||||
goto = true;
|
||||
}
|
||||
if (aside) {
|
||||
if (e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey)) {
|
||||
// modifier-click -> open to the side
|
||||
onEvent(e.elements[0], 'side');
|
||||
} else if (goto) {
|
||||
} else if (e.browserEvent instanceof KeyboardEvent || (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2) || (<GestureEvent>e.browserEvent).tapCount === 2) {
|
||||
// keybinding (list service command)
|
||||
// OR double click
|
||||
// OR double tap
|
||||
// -> close widget and goto target
|
||||
onEvent(e.elements[0], 'goto');
|
||||
} else {
|
||||
// preview location
|
||||
onEvent(e.elements[0], 'show');
|
||||
}
|
||||
});
|
||||
@@ -399,7 +394,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
this._splitView.resizeView(0, widthInPixel * this.layoutData.ratio);
|
||||
}
|
||||
|
||||
public setSelection(selection: OneReference): Promise<any> {
|
||||
setSelection(selection: OneReference): Promise<any> {
|
||||
return this._revealReference(selection, true).then(() => {
|
||||
if (!this._model) {
|
||||
// disposed
|
||||
@@ -411,7 +406,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
});
|
||||
}
|
||||
|
||||
public setModel(newModel: ReferencesModel | undefined): Promise<any> {
|
||||
setModel(newModel: ReferencesModel | undefined): Promise<any> {
|
||||
// clean up
|
||||
this._disposeOnNewModel.clear();
|
||||
this._model = newModel;
|
||||
@@ -426,7 +421,7 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (this._model.empty) {
|
||||
if (this._model.isEmpty) {
|
||||
this.setTitle('');
|
||||
this._messageContainer.innerHTML = nls.localize('noResults', "No results");
|
||||
dom.show(this._messageContainer);
|
||||
@@ -537,34 +532,17 @@ export class ReferenceWidget extends PeekViewWidget {
|
||||
|
||||
// theming
|
||||
|
||||
export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.'));
|
||||
export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.'));
|
||||
export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.'));
|
||||
export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.'));
|
||||
|
||||
export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.'));
|
||||
export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.'));
|
||||
export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.'));
|
||||
export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.'));
|
||||
export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.'));
|
||||
export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.'));
|
||||
export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.'));
|
||||
|
||||
export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.'));
|
||||
export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.'));
|
||||
export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.'));
|
||||
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const findMatchHighlightColor = theme.getColor(peekViewResultsMatchHighlight);
|
||||
const findMatchHighlightColor = theme.getColor(peekView.peekViewResultsMatchHighlight);
|
||||
if (findMatchHighlightColor) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { background-color: ${findMatchHighlightColor}; }`);
|
||||
}
|
||||
const referenceHighlightColor = theme.getColor(peekViewEditorMatchHighlight);
|
||||
const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight);
|
||||
if (referenceHighlightColor) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { background-color: ${referenceHighlightColor}; }`);
|
||||
}
|
||||
const referenceHighlightBorder = theme.getColor(peekViewEditorMatchHighlightBorder);
|
||||
const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder);
|
||||
if (referenceHighlightBorder) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .preview .reference-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`);
|
||||
}
|
||||
@@ -572,27 +550,27 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (hcOutline) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`);
|
||||
}
|
||||
const resultsBackground = theme.getColor(peekViewResultsBackground);
|
||||
const resultsBackground = theme.getColor(peekView.peekViewResultsBackground);
|
||||
if (resultsBackground) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { background-color: ${resultsBackground}; }`);
|
||||
}
|
||||
const resultsMatchForeground = theme.getColor(peekViewResultsMatchForeground);
|
||||
const resultsMatchForeground = theme.getColor(peekView.peekViewResultsMatchForeground);
|
||||
if (resultsMatchForeground) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree { color: ${resultsMatchForeground}; }`);
|
||||
}
|
||||
const resultsFileForeground = theme.getColor(peekViewResultsFileForeground);
|
||||
const resultsFileForeground = theme.getColor(peekView.peekViewResultsFileForeground);
|
||||
if (resultsFileForeground) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .reference-file { color: ${resultsFileForeground}; }`);
|
||||
}
|
||||
const resultsSelectedBackground = theme.getColor(peekViewResultsSelectionBackground);
|
||||
const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground);
|
||||
if (resultsSelectedBackground) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`);
|
||||
}
|
||||
const resultsSelectedForeground = theme.getColor(peekViewResultsSelectionForeground);
|
||||
const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground);
|
||||
if (resultsSelectedForeground) {
|
||||
collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`);
|
||||
}
|
||||
const editorBackground = theme.getColor(peekViewEditorBackground);
|
||||
const editorBackground = theme.getColor(peekView.peekViewEditorBackground);
|
||||
if (editorBackground) {
|
||||
collector.addRule(
|
||||
`.monaco-editor .reference-zone-widget .preview .monaco-editor .monaco-editor-background,` +
|
||||
@@ -600,7 +578,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
` background-color: ${editorBackground};` +
|
||||
`}`);
|
||||
}
|
||||
const editorGutterBackground = theme.getColor(peekViewEditorGutterBackground);
|
||||
const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground);
|
||||
if (editorGutterBackground) {
|
||||
collector.addRule(
|
||||
`.monaco-editor .reference-zone-widget .preview .monaco-editor .margin {` +
|
||||
@@ -18,18 +18,15 @@ import { IMatch } from 'vs/base/common/filters';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
|
||||
export class OneReference {
|
||||
readonly id: string;
|
||||
|
||||
private readonly _onRefChanged = new Emitter<this>();
|
||||
readonly onRefChanged: Event<this> = this._onRefChanged.event;
|
||||
readonly id: string = defaultGenerator.nextId();
|
||||
|
||||
constructor(
|
||||
readonly isProviderFirst: boolean,
|
||||
readonly parent: FileReferences,
|
||||
private _range: IRange,
|
||||
readonly isProviderFirst: boolean
|
||||
) {
|
||||
this.id = defaultGenerator.nextId();
|
||||
}
|
||||
private _rangeCallback: (ref: OneReference) => void
|
||||
) { }
|
||||
|
||||
get uri(): URI {
|
||||
return this.parent.uri;
|
||||
@@ -41,10 +38,10 @@ export class OneReference {
|
||||
|
||||
set range(value: IRange) {
|
||||
this._range = value;
|
||||
this._onRefChanged.fire(this);
|
||||
this._rangeCallback(this);
|
||||
}
|
||||
|
||||
getAriaMessage(): string {
|
||||
get ariaMessage(): string {
|
||||
return localize(
|
||||
'aria.oneReference', "symbol in {0} on line {1} at column {2}",
|
||||
basename(this.uri), this.range.startLineNumber, this.range.startColumn
|
||||
@@ -56,11 +53,10 @@ export class FilePreview implements IDisposable {
|
||||
|
||||
constructor(
|
||||
private readonly _modelReference: IReference<ITextEditorModel>
|
||||
) {
|
||||
}
|
||||
) { }
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._modelReference);
|
||||
this._modelReference.dispose();
|
||||
}
|
||||
|
||||
preview(range: IRange, n: number = 8): { value: string; highlight: IMatch } | undefined {
|
||||
@@ -88,29 +84,20 @@ export class FilePreview implements IDisposable {
|
||||
|
||||
export class FileReferences implements IDisposable {
|
||||
|
||||
private _children: OneReference[];
|
||||
readonly children: OneReference[] = [];
|
||||
|
||||
private _preview?: FilePreview;
|
||||
private _resolved?: boolean;
|
||||
private _loadFailure: any;
|
||||
private _loadFailure?: any;
|
||||
|
||||
constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) {
|
||||
this._children = [];
|
||||
}
|
||||
constructor(
|
||||
readonly parent: ReferencesModel,
|
||||
readonly uri: URI
|
||||
) { }
|
||||
|
||||
get id(): string {
|
||||
return this._uri.toString();
|
||||
}
|
||||
|
||||
get parent(): ReferencesModel {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
get children(): OneReference[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
get uri(): URI {
|
||||
return this._uri;
|
||||
dispose(): void {
|
||||
dispose(this._preview);
|
||||
this._preview = undefined;
|
||||
}
|
||||
|
||||
get preview(): FilePreview | undefined {
|
||||
@@ -121,7 +108,7 @@ export class FileReferences implements IDisposable {
|
||||
return this._loadFailure;
|
||||
}
|
||||
|
||||
getAriaMessage(): string {
|
||||
get ariaMessage(): string {
|
||||
const len = this.children.length;
|
||||
if (len === 1) {
|
||||
return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath);
|
||||
@@ -136,7 +123,7 @@ export class FileReferences implements IDisposable {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
return Promise.resolve(textModelResolverService.createModelReference(this._uri).then(modelReference => {
|
||||
return Promise.resolve(textModelResolverService.createModelReference(this.uri).then(modelReference => {
|
||||
const model = modelReference.object;
|
||||
|
||||
if (!model) {
|
||||
@@ -150,62 +137,76 @@ export class FileReferences implements IDisposable {
|
||||
|
||||
}, err => {
|
||||
// something wrong here
|
||||
this._children = [];
|
||||
this.children.length = 0;
|
||||
this._resolved = true;
|
||||
this._loadFailure = err;
|
||||
return this;
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._preview) {
|
||||
this._preview.dispose();
|
||||
this._preview = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ReferencesModel implements IDisposable {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _links: LocationLink[];
|
||||
private readonly _title: string;
|
||||
|
||||
readonly groups: FileReferences[] = [];
|
||||
readonly references: OneReference[] = [];
|
||||
|
||||
readonly _onDidChangeReferenceRange = new Emitter<OneReference>();
|
||||
readonly onDidChangeReferenceRange: Event<OneReference> = this._onDidChangeReferenceRange.event;
|
||||
|
||||
constructor(references: LocationLink[]) {
|
||||
constructor(links: LocationLink[], title: string) {
|
||||
this._links = links;
|
||||
this._title = title;
|
||||
|
||||
// grouping and sorting
|
||||
const [providersFirst] = references;
|
||||
references.sort(ReferencesModel._compareReferences);
|
||||
const [providersFirst] = links;
|
||||
links.sort(ReferencesModel._compareReferences);
|
||||
|
||||
let current: FileReferences | undefined;
|
||||
for (let ref of references) {
|
||||
if (!current || current.uri.toString() !== ref.uri.toString()) {
|
||||
for (let link of links) {
|
||||
if (!current || current.uri.toString() !== link.uri.toString()) {
|
||||
// new group
|
||||
current = new FileReferences(this, ref.uri);
|
||||
current = new FileReferences(this, link.uri);
|
||||
this.groups.push(current);
|
||||
}
|
||||
|
||||
// append, check for equality first!
|
||||
if (current.children.length === 0
|
||||
|| !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) {
|
||||
if (current.children.length === 0 || !Range.equalsRange(link.range, current.children[current.children.length - 1].range)) {
|
||||
|
||||
let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range, providersFirst === ref);
|
||||
this._disposables.add(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e)));
|
||||
const oneRef = new OneReference(
|
||||
providersFirst === link, current, link.targetSelectionRange || link.range,
|
||||
ref => this._onDidChangeReferenceRange.fire(ref)
|
||||
);
|
||||
this.references.push(oneRef);
|
||||
current.children.push(oneRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
dispose(): void {
|
||||
dispose(this.groups);
|
||||
this._disposables.dispose();
|
||||
this._onDidChangeReferenceRange.dispose();
|
||||
this.groups.length = 0;
|
||||
}
|
||||
|
||||
clone(): ReferencesModel {
|
||||
return new ReferencesModel(this._links, this._title);
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
get isEmpty(): boolean {
|
||||
return this.groups.length === 0;
|
||||
}
|
||||
|
||||
getAriaMessage(): string {
|
||||
if (this.empty) {
|
||||
get ariaMessage(): string {
|
||||
if (this.isEmpty) {
|
||||
return localize('aria.result.0', "No results found");
|
||||
} else if (this.references.length === 1) {
|
||||
return localize('aria.result.1', "Found 1 symbol in {0}", this.references[0].uri.fsPath);
|
||||
@@ -272,6 +273,17 @@ export class ReferencesModel implements IDisposable {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
referenceAt(resource: URI, position: Position): OneReference | undefined {
|
||||
for (const ref of this.references) {
|
||||
if (ref.uri.toString() === resource.toString()) {
|
||||
if (Range.containsPosition(ref.range, position)) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
firstReference(): OneReference | undefined {
|
||||
for (const ref of this.references) {
|
||||
if (ref.isProviderFirst) {
|
||||
@@ -281,21 +293,7 @@ export class ReferencesModel implements IDisposable {
|
||||
return this.references[0];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.groups);
|
||||
this._disposables.dispose();
|
||||
this.groups.length = 0;
|
||||
}
|
||||
|
||||
private static _compareReferences(a: Location, b: Location): number {
|
||||
const auri = a.uri.toString();
|
||||
const buri = b.uri.toString();
|
||||
if (auri < buri) {
|
||||
return -1;
|
||||
} else if (auri > buri) {
|
||||
return 1;
|
||||
} else {
|
||||
return Range.compareRangesUsingStarts(a.range, b.range);
|
||||
}
|
||||
return strings.compare(a.uri.toString(), b.uri.toString()) || Range.compareRangesUsingStarts(a.range, b.range);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ReferencesModel, OneReference } from 'vs/editor/contrib/referenceSearch/referencesModel';
|
||||
import { ReferencesModel, OneReference } from 'vs/editor/contrib/gotoSymbol/referencesModel';
|
||||
import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
@@ -2,11 +2,12 @@
|
||||
* 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 { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel';
|
||||
import { ReferencesModel } from 'vs/editor/contrib/gotoSymbol/referencesModel';
|
||||
|
||||
suite('references', function () {
|
||||
|
||||
@@ -20,7 +21,7 @@ suite('references', function () {
|
||||
}, {
|
||||
uri: URI.file('/src/can'),
|
||||
range: new Range(1, 1, 1, 1)
|
||||
}]);
|
||||
}], 'FOO');
|
||||
|
||||
let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1));
|
||||
assert.equal(ref!.uri.path, '/src/can');
|
||||
@@ -8,12 +8,9 @@
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
z-index: 50;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
-khtml-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-o-user-select: text;
|
||||
user-select: text;
|
||||
box-sizing: initial;
|
||||
animation: fadein 100ms linear;
|
||||
line-height: 1.5em;
|
||||
@@ -32,6 +29,10 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
|
||||
min-width: 100vw;
|
||||
}
|
||||
|
||||
.monaco-editor-hover p,
|
||||
.monaco-editor-hover ul {
|
||||
margin: 8px 0;
|
||||
@@ -103,3 +104,9 @@
|
||||
.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .markdown-hover .hover-contents .codicon {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHov
|
||||
import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition';
|
||||
|
||||
export class ModesHoverController implements IEditorContribution {
|
||||
|
||||
@@ -258,8 +259,50 @@ class ShowHoverAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
class ShowDefinitionPreviewHoverAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.showDefinitionPreviewHover',
|
||||
label: nls.localize({
|
||||
key: 'showDefinitionPreviewHover',
|
||||
comment: [
|
||||
'Label for action that will trigger the showing of definition preview hover in the editor.',
|
||||
'This allows for users to show the definition preview hover without using the mouse.'
|
||||
]
|
||||
}, "Show Definition Preview Hover"),
|
||||
alias: 'Show Definition Preview Hover',
|
||||
precondition: undefined
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = ModesHoverController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const position = editor.getPosition();
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
|
||||
const goto = GotoDefinitionAtPositionEditorContribution.get(editor);
|
||||
const promise = goto.startFindDefinitionFromCursor(position);
|
||||
if (promise) {
|
||||
promise.then(() => {
|
||||
controller.showContentHover(range, HoverStartMode.Immediate, true);
|
||||
});
|
||||
} else {
|
||||
controller.showContentHover(range, HoverStartMode.Immediate, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(ModesHoverController.ID, ModesHoverController);
|
||||
registerEditorAction(ShowHoverAction);
|
||||
registerEditorAction(ShowDefinitionPreviewHoverAction);
|
||||
|
||||
// theming
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
@@ -282,6 +325,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (link) {
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover a { color: ${link}; }`);
|
||||
}
|
||||
const hoverForeground = theme.getColor(editorHoverForeground);
|
||||
if (hoverForeground) {
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover { color: ${hoverForeground}; }`);
|
||||
}
|
||||
const actionsBackground = theme.getColor(editorHoverStatusBarBackground);
|
||||
if (actionsBackground) {
|
||||
collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row .actions { background-color: ${actionsBackground}; }`);
|
||||
|
||||
@@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -207,7 +207,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
private readonly _themeService: IThemeService,
|
||||
private readonly _keybindingService: IKeybindingService,
|
||||
private readonly _modeService: IModeService,
|
||||
private readonly _openerService: IOpenerService | null = NullOpenerService,
|
||||
private readonly _openerService: IOpenerService = NullOpenerService,
|
||||
) {
|
||||
super(ModesContentHoverWidget.ID, editor);
|
||||
|
||||
@@ -354,7 +354,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
containColorPicker = true;
|
||||
|
||||
const { red, green, blue, alpha } = msg.color;
|
||||
const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
|
||||
const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha);
|
||||
const color = new Color(rgba);
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
@@ -548,7 +548,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
quickfixPlaceholderElement.style.transition = '';
|
||||
quickfixPlaceholderElement.style.opacity = '1';
|
||||
|
||||
if (!actions.actions.length) {
|
||||
if (!actions.validActions.length) {
|
||||
actions.dispose();
|
||||
quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available");
|
||||
return;
|
||||
@@ -586,7 +586,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
return getCodeActions(
|
||||
this._editor.getModel()!,
|
||||
new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn),
|
||||
{ type: 'manual', filter: { kind: CodeActionKind.QuickFix } },
|
||||
{ type: 'manual', filter: { include: CodeActionKind.QuickFix } },
|
||||
cancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget {
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
modeService: IModeService,
|
||||
openerService: IOpenerService | null = NullOpenerService,
|
||||
openerService: IOpenerService = NullOpenerService,
|
||||
) {
|
||||
super(ModesGlyphHoverWidget.ID, editor);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] {
|
||||
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
|
||||
@@ -442,7 +442,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
this.callOnModel.clear();
|
||||
|
||||
// we are disabled
|
||||
if (!this.editor.getOption(EditorOption.autoIndent) || this.editor.getOption(EditorOption.formatOnPaste)) {
|
||||
if (this.editor.getOption(EditorOption.autoIndent) < EditorAutoIndentStrategy.Full || this.editor.getOption(EditorOption.formatOnPaste)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -470,6 +470,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
|
||||
return;
|
||||
}
|
||||
const autoIndent = this.editor.getOption(EditorOption.autoIndent);
|
||||
const { tabSize, indentSize, insertSpaces } = model.getOptions();
|
||||
this.editor.pushUndoStop();
|
||||
let textEdits: TextEdit[] = [];
|
||||
@@ -499,7 +500,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
|
||||
let firstLineText = model.getLineContent(startLineNumber);
|
||||
if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) {
|
||||
let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(model, model.getLanguageIdentifier().id, startLineNumber, indentConverter);
|
||||
let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, model, model.getLanguageIdentifier().id, startLineNumber, indentConverter);
|
||||
|
||||
if (indentOfFirstLine !== null) {
|
||||
let oldIndentation = strings.getLeadingWhitespace(firstLineText);
|
||||
@@ -557,7 +558,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
};
|
||||
let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter);
|
||||
let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(autoIndent, virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter);
|
||||
if (indentOfSecondLine !== null) {
|
||||
let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize);
|
||||
let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize);
|
||||
|
||||
@@ -64,7 +64,7 @@ class CopyLinesUpAction extends AbstractCopyLinesAction {
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '2_line',
|
||||
title: nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"),
|
||||
@@ -87,7 +87,7 @@ class CopyLinesDownAction extends AbstractCopyLinesAction {
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '2_line',
|
||||
title: nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"),
|
||||
@@ -105,7 +105,7 @@ export class DuplicateSelectionAction extends EditorAction {
|
||||
label: nls.localize('duplicateSelection', "Duplicate Selection"),
|
||||
alias: 'Duplicate Selection',
|
||||
precondition: EditorContextKeys.writable,
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '2_line',
|
||||
title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"),
|
||||
@@ -178,7 +178,7 @@ class MoveLinesUpAction extends AbstractMoveLinesAction {
|
||||
linux: { primary: KeyMod.Alt | KeyCode.UpArrow },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '2_line',
|
||||
title: nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"),
|
||||
@@ -201,7 +201,7 @@ class MoveLinesDownAction extends AbstractMoveLinesAction {
|
||||
linux: { primary: KeyMod.Alt | KeyCode.DownArrow },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '2_line',
|
||||
title: nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"),
|
||||
|
||||
@@ -13,18 +13,19 @@ import { IndentAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { IIndentConverter, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
|
||||
import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export class MoveLinesCommand implements ICommand {
|
||||
|
||||
private readonly _selection: Selection;
|
||||
private readonly _isMovingDown: boolean;
|
||||
private readonly _autoIndent: boolean;
|
||||
private readonly _autoIndent: EditorAutoIndentStrategy;
|
||||
|
||||
private _selectionId: string | null;
|
||||
private _moveEndPositionDown?: boolean;
|
||||
private _moveEndLineSelectionShrink: boolean;
|
||||
|
||||
constructor(selection: Selection, isMovingDown: boolean, autoIndent: boolean) {
|
||||
constructor(selection: Selection, isMovingDown: boolean, autoIndent: EditorAutoIndentStrategy) {
|
||||
this._selection = selection;
|
||||
this._isMovingDown = isMovingDown;
|
||||
this._autoIndent = autoIndent;
|
||||
@@ -117,7 +118,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
return model.getLineContent(lineNumber);
|
||||
}
|
||||
};
|
||||
let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition(
|
||||
let indentOfMovingLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(
|
||||
movingLineNumber, 1), s.startLineNumber, indentConverter);
|
||||
if (indentOfMovingLine !== null) {
|
||||
let oldIndentation = strings.getLeadingWhitespace(model.getLineContent(movingLineNumber));
|
||||
@@ -152,7 +153,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
}
|
||||
};
|
||||
|
||||
let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition(
|
||||
let newIndentatOfMovingBlock = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(
|
||||
movingLineNumber, 1), s.startLineNumber + 1, indentConverter);
|
||||
|
||||
if (newIndentatOfMovingBlock !== null) {
|
||||
@@ -197,7 +198,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
}
|
||||
} else {
|
||||
// it doesn't match any onEnter rule, let's check indentation rules then.
|
||||
let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter);
|
||||
let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(this._autoIndent, virtualModel, model.getLanguageIdAtPosition(s.startLineNumber, 1), movingLineNumber, indentConverter);
|
||||
if (indentOfFirstLine !== null) {
|
||||
// adjust the indentation of the moving block
|
||||
let oldIndent = strings.getLeadingWhitespace(model.getLineContent(s.startLineNumber));
|
||||
@@ -251,20 +252,19 @@ export class MoveLinesCommand implements ICommand {
|
||||
}
|
||||
|
||||
let maxColumn = model.getLineMaxColumn(validPrecedingLine);
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
|
||||
|
||||
if (enter) {
|
||||
let enterPrefix = enter.indentation;
|
||||
let enterAction = enter.enterAction;
|
||||
|
||||
if (enterAction.indentAction === IndentAction.None) {
|
||||
enterPrefix = enter.indentation + enterAction.appendText;
|
||||
} else if (enterAction.indentAction === IndentAction.Indent) {
|
||||
enterPrefix = enter.indentation + enterAction.appendText;
|
||||
} else if (enterAction.indentAction === IndentAction.IndentOutdent) {
|
||||
if (enter.indentAction === IndentAction.None) {
|
||||
enterPrefix = enter.indentation + enter.appendText;
|
||||
} else if (enter.indentAction === IndentAction.Indent) {
|
||||
enterPrefix = enter.indentation + enter.appendText;
|
||||
} else if (enter.indentAction === IndentAction.IndentOutdent) {
|
||||
enterPrefix = enter.indentation;
|
||||
} else if (enterAction.indentAction === IndentAction.Outdent) {
|
||||
enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enterAction.appendText;
|
||||
} else if (enter.indentAction === IndentAction.Outdent) {
|
||||
enterPrefix = indentConverter.unshiftIndent(enter.indentation) + enter.appendText;
|
||||
}
|
||||
let movingLineText = model.getLineContent(line);
|
||||
if (this.trimLeft(movingLineText).indexOf(this.trimLeft(enterPrefix)) >= 0) {
|
||||
@@ -288,7 +288,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
}
|
||||
|
||||
private shouldAutoIndent(model: ITextModel, selection: Selection) {
|
||||
if (!this._autoIndent) {
|
||||
if (this._autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return false;
|
||||
}
|
||||
// if it's not easy to tokenize, we stop auto indent.
|
||||
|
||||
@@ -11,6 +11,14 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod
|
||||
|
||||
export class SortLinesCommand implements editorCommon.ICommand {
|
||||
|
||||
private static _COLLATOR: Intl.Collator | null = null;
|
||||
public static getCollator(): Intl.Collator {
|
||||
if (!SortLinesCommand._COLLATOR) {
|
||||
SortLinesCommand._COLLATOR = new Intl.Collator();
|
||||
}
|
||||
return SortLinesCommand._COLLATOR;
|
||||
}
|
||||
|
||||
private readonly selection: Selection;
|
||||
private readonly descending: boolean;
|
||||
private selectionId: string | null;
|
||||
@@ -76,9 +84,7 @@ function getSortData(model: ITextModel, selection: Selection, descending: boolea
|
||||
}
|
||||
|
||||
let sorted = linesToSort.slice(0);
|
||||
sorted.sort((a, b) => {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
});
|
||||
sorted.sort(SortLinesCommand.getCollator().compare);
|
||||
|
||||
// If descending, reverse the order.
|
||||
if (descending === true) {
|
||||
|
||||
@@ -9,21 +9,22 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo
|
||||
import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/moveLinesCommand';
|
||||
import { testCommand } from 'vs/editor/test/browser/testCommand';
|
||||
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
|
||||
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, false), expectedLines, expectedSelection);
|
||||
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection);
|
||||
}
|
||||
|
||||
function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
|
||||
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, false), expectedLines, expectedSelection);
|
||||
testCommand(lines, null, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced), expectedLines, expectedSelection);
|
||||
}
|
||||
|
||||
function testMoveLinesDownWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
|
||||
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, true), expectedLines, expectedSelection);
|
||||
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection);
|
||||
}
|
||||
|
||||
function testMoveLinesUpWithIndentCommand(languageId: LanguageIdentifier, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void {
|
||||
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, true), expectedLines, expectedSelection);
|
||||
testCommand(lines, languageId, selection, (sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full), expectedLines, expectedSelection);
|
||||
}
|
||||
|
||||
suite('Editor Contrib - Move Lines Command', () => {
|
||||
|
||||
@@ -44,17 +44,9 @@ export class Link implements ILink {
|
||||
return this._link.tooltip;
|
||||
}
|
||||
|
||||
resolve(token: CancellationToken): Promise<URI> {
|
||||
async resolve(token: CancellationToken): Promise<URI | string> {
|
||||
if (this._link.url) {
|
||||
try {
|
||||
if (typeof this._link.url === 'string') {
|
||||
return Promise.resolve(URI.parse(this._link.url));
|
||||
} else {
|
||||
return Promise.resolve(this._link.url);
|
||||
}
|
||||
} catch (e) {
|
||||
return Promise.reject(new Error('invalid'));
|
||||
}
|
||||
return this._link.url;
|
||||
}
|
||||
|
||||
if (typeof this._provider.resolveLink === 'function') {
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { LinkProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture';
|
||||
import { ClickLinkGesture, ClickLinkKeyboardEvent, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/link/clickLinkGesture';
|
||||
import { Link, getLinks, LinksList } from 'vs/editor/contrib/links/getLinks';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
@@ -7,7 +7,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
|
||||
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -29,7 +28,7 @@ export class MarkdownRenderer extends Disposable {
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@optional(IOpenerService) private readonly _openerService: IOpenerService | null = NullOpenerService,
|
||||
@optional(IOpenerService) private readonly _openerService: IOpenerService = NullOpenerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -64,15 +63,7 @@ export class MarkdownRenderer extends Disposable {
|
||||
codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(),
|
||||
actionHandler: {
|
||||
callback: (content) => {
|
||||
let uri: URI | undefined;
|
||||
try {
|
||||
uri = URI.parse(content);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (uri && this._openerService) {
|
||||
this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError);
|
||||
}
|
||||
this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError);
|
||||
},
|
||||
disposeables
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class InsertCursorAbove extends EditorAction {
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miInsertCursorAbove', comment: ['&& denotes a mnemonic'] }, "&&Add Cursor Above"),
|
||||
@@ -95,7 +95,7 @@ export class InsertCursorBelow extends EditorAction {
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miInsertCursorBelow', comment: ['&& denotes a mnemonic'] }, "A&&dd Cursor Below"),
|
||||
@@ -140,7 +140,7 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction {
|
||||
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_I,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miInsertCursorAtEndOfEachLineSelected', comment: ['&& denotes a mnemonic'] }, "Add C&&ursors to Line Ends"),
|
||||
@@ -603,6 +603,17 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
matches = this._session.selectAll();
|
||||
}
|
||||
|
||||
if (findState.searchScope) {
|
||||
const state = findState.searchScope;
|
||||
let inSelection: FindMatch[] | null = [];
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
if (matches[i].range.endLineNumber <= state.endLineNumber && matches[i].range.startLineNumber >= state.startLineNumber) {
|
||||
inSelection.push(matches[i]);
|
||||
}
|
||||
}
|
||||
matches = inSelection;
|
||||
}
|
||||
|
||||
if (matches.length > 0) {
|
||||
const editorSelection = this._editor.getSelection();
|
||||
// Have the primary cursor remain the one where the action was invoked
|
||||
@@ -620,6 +631,12 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
|
||||
this._setSelections(matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn)));
|
||||
}
|
||||
}
|
||||
|
||||
public selectAllUsingSelections(selections: Selection[]): void {
|
||||
if (selections.length > 0) {
|
||||
this._setSelections(selections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MultiCursorSelectionControllerAction extends EditorAction {
|
||||
@@ -651,7 +668,7 @@ export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionContr
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_D,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miAddSelectionToNextFindMatch', comment: ['&& denotes a mnemonic'] }, "Add &&Next Occurrence"),
|
||||
@@ -671,7 +688,7 @@ export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionC
|
||||
label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"),
|
||||
alias: 'Add Selection To Previous Find Match',
|
||||
precondition: undefined,
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miAddSelectionToPreviousFindMatch', comment: ['&& denotes a mnemonic'] }, "Add P&&revious Occurrence"),
|
||||
@@ -729,7 +746,7 @@ export class SelectHighlightsAction extends MultiCursorSelectionControllerAction
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '3_multi',
|
||||
title: nls.localize({ key: 'miSelectHighlights', comment: ['&& denotes a mnemonic'] }, "Select All &&Occurrences"),
|
||||
@@ -754,7 +771,7 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.F2,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: '1_modification',
|
||||
order: 1.2
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
.monaco-editor .parameter-hints-widget > .wrapper {
|
||||
max-width: 440px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget.multiple {
|
||||
min-height: 3.3em;
|
||||
padding: 0 0 0 1.9em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget.visible {
|
||||
@@ -62,20 +62,19 @@
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .buttons {
|
||||
position: absolute;
|
||||
.monaco-editor .parameter-hints-widget .controls {
|
||||
display: none;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 22px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget.multiple .buttons {
|
||||
display: block;
|
||||
.monaco-editor .parameter-hints-widget.multiple .controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget.multiple .button {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
@@ -88,26 +87,16 @@
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .button.next {
|
||||
bottom: 0;
|
||||
background-image: url('arrow-down.svg');
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .overloads {
|
||||
position: absolute;
|
||||
display: none;
|
||||
text-align: center;
|
||||
bottom: 14px;
|
||||
left: 0;
|
||||
width: 22px;
|
||||
height: 12px;
|
||||
line-height: 12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget.multiple .overloads {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .signature .parameter.active {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace ParameterHintState {
|
||||
Pending,
|
||||
}
|
||||
|
||||
export const Default = new class { readonly type = Type.Default; };
|
||||
export const Default = { type: Type.Default } as const;
|
||||
|
||||
export class Pending {
|
||||
readonly type = Type.Pending;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { domEvent, stop } from 'vs/base/browser/event';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./parameterHints';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -19,19 +19,20 @@ 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 { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } 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';
|
||||
import { pad } from 'vs/base/common/strings';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class ParameterHintsWidget extends Disposable implements IContentWidget, IDisposable {
|
||||
export class ParameterHintsWidget extends Disposable implements IContentWidget {
|
||||
|
||||
private static readonly ID = 'editor.widget.parameterHintsWidget';
|
||||
|
||||
private readonly markdownRenderer: MarkdownRenderer;
|
||||
private readonly renderDisposeables = this._register(new DisposableStore());
|
||||
private readonly model = this._register(new MutableDisposable<ParameterHintsModel>());
|
||||
private readonly model: ParameterHintsModel;
|
||||
private readonly keyVisible: IContextKey<boolean>;
|
||||
private readonly keyMultipleSignatures: IContextKey<boolean>;
|
||||
|
||||
@@ -57,11 +58,11 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
) {
|
||||
super();
|
||||
this.markdownRenderer = this._register(new MarkdownRenderer(editor, modeService, openerService));
|
||||
this.model.value = new ParameterHintsModel(editor);
|
||||
this.model = this._register(new ParameterHintsModel(editor));
|
||||
this.keyVisible = Context.Visible.bindTo(contextKeyService);
|
||||
this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService);
|
||||
|
||||
this._register(this.model.value.onChangedHints(newParameterHints => {
|
||||
this._register(this.model.onChangedHints(newParameterHints => {
|
||||
if (newParameterHints) {
|
||||
this.show();
|
||||
this.render(newParameterHints);
|
||||
@@ -76,9 +77,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
const wrapper = dom.append(element, $('.wrapper'));
|
||||
wrapper.tabIndex = -1;
|
||||
|
||||
const buttons = dom.append(wrapper, $('.buttons'));
|
||||
const previous = dom.append(buttons, $('.button.previous'));
|
||||
const next = dom.append(buttons, $('.button.next'));
|
||||
const controls = dom.append(wrapper, $('.controls'));
|
||||
const previous = dom.append(controls, $('.button.previous'));
|
||||
const overloads = dom.append(controls, $('.overloads'));
|
||||
const next = dom.append(controls, $('.button.next'));
|
||||
|
||||
const onPreviousClick = stop(domEvent(previous, 'click'));
|
||||
this._register(onPreviousClick(this.previous, this));
|
||||
@@ -86,8 +88,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
const onNextClick = stop(domEvent(next, 'click'));
|
||||
this._register(onNextClick(this.next, this));
|
||||
|
||||
const overloads = dom.append(wrapper, $('.overloads'));
|
||||
|
||||
const body = $('.body');
|
||||
const scrollbar = new DomScrollableElement(body, {});
|
||||
this._register(scrollbar);
|
||||
@@ -134,7 +134,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
}
|
||||
|
||||
private show(): void {
|
||||
if (!this.model || this.visible) {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
}
|
||||
|
||||
private hide(): void {
|
||||
if (!this.model || !this.visible) {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
this.domNodes.docs.innerHTML = '';
|
||||
|
||||
const signature = hints.signatures[hints.activeSignature];
|
||||
|
||||
if (!signature) {
|
||||
return;
|
||||
}
|
||||
@@ -204,14 +203,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
if (!hasParameters) {
|
||||
const label = dom.append(code, $('span'));
|
||||
label.textContent = signature.label;
|
||||
|
||||
} else {
|
||||
this.renderParameters(code, signature, hints.activeParameter);
|
||||
}
|
||||
|
||||
this.renderDisposeables.clear();
|
||||
|
||||
const activeParameter = signature.parameters[hints.activeParameter];
|
||||
const activeParameter: modes.ParameterInformation | undefined = signature.parameters[hints.activeParameter];
|
||||
|
||||
if (activeParameter && activeParameter.documentation) {
|
||||
const documentation = $('span.documentation');
|
||||
@@ -236,30 +234,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
dom.append(this.domNodes.docs, renderedContents.element);
|
||||
}
|
||||
|
||||
let hasDocs = false;
|
||||
if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) {
|
||||
hasDocs = true;
|
||||
}
|
||||
if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) {
|
||||
hasDocs = true;
|
||||
}
|
||||
if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) {
|
||||
hasDocs = true;
|
||||
}
|
||||
if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) {
|
||||
hasDocs = true;
|
||||
}
|
||||
const hasDocs = this.hasDocs(signature, activeParameter);
|
||||
|
||||
dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs);
|
||||
dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs);
|
||||
|
||||
let currentOverload = String(hints.activeSignature + 1);
|
||||
|
||||
if (hints.signatures.length < 10) {
|
||||
currentOverload += `/${hints.signatures.length}`;
|
||||
}
|
||||
|
||||
this.domNodes.overloads.textContent = currentOverload;
|
||||
this.domNodes.overloads.textContent =
|
||||
pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length;
|
||||
|
||||
if (activeParameter) {
|
||||
const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter);
|
||||
@@ -276,8 +257,23 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
this.domNodes.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
|
||||
private hasDocs(signature: modes.SignatureInformation, activeParameter: modes.ParameterInformation | undefined): boolean {
|
||||
if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
|
||||
const [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
|
||||
|
||||
const beforeSpan = document.createElement('span');
|
||||
@@ -317,23 +313,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
}
|
||||
|
||||
next(): void {
|
||||
if (this.model.value) {
|
||||
this.editor.focus();
|
||||
this.model.value.next();
|
||||
}
|
||||
this.editor.focus();
|
||||
this.model.next();
|
||||
}
|
||||
|
||||
previous(): void {
|
||||
if (this.model.value) {
|
||||
this.editor.focus();
|
||||
this.model.value.previous();
|
||||
}
|
||||
this.editor.focus();
|
||||
this.model.previous();
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
if (this.model.value) {
|
||||
this.model.value.cancel();
|
||||
}
|
||||
this.model.cancel();
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
@@ -348,9 +338,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget,
|
||||
}
|
||||
|
||||
trigger(context: TriggerContext): void {
|
||||
if (this.model.value) {
|
||||
this.model.value.trigger(context, 0);
|
||||
}
|
||||
this.model.trigger(context, 0);
|
||||
}
|
||||
|
||||
private updateMaxHeight(): void {
|
||||
@@ -385,6 +373,11 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`);
|
||||
}
|
||||
|
||||
const foreground = theme.getColor(editorHoverForeground);
|
||||
if (foreground) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget { color: ${foreground}; }`);
|
||||
}
|
||||
|
||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`);
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .peekview-widget .head {
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .head .peekview-title {
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
@@ -24,6 +21,15 @@
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .head .peekview-title .meta {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .head .peekview-title .meta:not(:empty)::before {
|
||||
content: '-';
|
||||
padding: 0 0.3em;
|
||||
}
|
||||
|
||||
.monaco-editor .peekview-widget .head .peekview-actions {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
@@ -3,25 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/peekViewWidget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { ActionBar, IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import 'vs/css!./media/peekViewWidget';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
|
||||
export const IPeekViewService = createDecorator<IPeekViewService>('IPeekViewService');
|
||||
@@ -33,7 +36,7 @@ export interface IPeekViewService {
|
||||
registerSingleton(IPeekViewService, class implements IPeekViewService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _widgets = new Map<ICodeEditor, { widget: PeekViewWidget, listener: IDisposable }>();
|
||||
private readonly _widgets = new Map<ICodeEditor, { widget: PeekViewWidget, listener: IDisposable; }>();
|
||||
|
||||
addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void {
|
||||
const existing = this._widgets.get(editor);
|
||||
@@ -57,6 +60,24 @@ export namespace PeekContext {
|
||||
export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated();
|
||||
}
|
||||
|
||||
class PeekContextController implements IEditorContribution {
|
||||
|
||||
static readonly ID = 'editor.contrib.referenceController';
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
if (editor instanceof EmbeddedCodeEditorWidget) {
|
||||
PeekContext.inPeekEditor.bindTo(contextKeyService);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
}
|
||||
|
||||
registerEditorContribution(PeekContextController.ID, PeekContextController);
|
||||
|
||||
export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null {
|
||||
let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
|
||||
if (editor instanceof EmbeddedCodeEditorWidget) {
|
||||
@@ -81,9 +102,10 @@ const defaultOptions: IPeekViewOptions = {
|
||||
|
||||
export abstract class PeekViewWidget extends ZoneWidget {
|
||||
|
||||
public _serviceBrand: undefined;
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidClose = new Emitter<PeekViewWidget>();
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
protected _headElement?: HTMLDivElement;
|
||||
protected _primaryHeading?: HTMLElement;
|
||||
@@ -97,16 +119,12 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
objects.mixin(this.options, defaultOptions, false);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this._onDidClose.fire(this);
|
||||
}
|
||||
|
||||
public get onDidClose(): Event<PeekViewWidget> {
|
||||
return this._onDidClose.event;
|
||||
}
|
||||
|
||||
public style(styles: IPeekViewStyles): void {
|
||||
style(styles: IPeekViewStyles): void {
|
||||
let options = <IPeekViewOptions>this.options;
|
||||
if (styles.headerBackgroundColor) {
|
||||
options.headerBackgroundColor = styles.headerBackgroundColor;
|
||||
@@ -185,7 +203,7 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
// implement me
|
||||
}
|
||||
|
||||
public setTitle(primaryHeading: string, secondaryHeading?: string): void {
|
||||
setTitle(primaryHeading: string, secondaryHeading?: string): void {
|
||||
if (this._primaryHeading && this._secondaryHeading) {
|
||||
this._primaryHeading.innerHTML = strings.escape(primaryHeading);
|
||||
this._primaryHeading.setAttribute('aria-label', primaryHeading);
|
||||
@@ -197,19 +215,20 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
}
|
||||
}
|
||||
|
||||
public setMetaTitle(value: string): void {
|
||||
setMetaTitle(value: string): void {
|
||||
if (this._metaHeading) {
|
||||
if (value) {
|
||||
this._metaHeading.innerHTML = strings.escape(value);
|
||||
dom.show(this._metaHeading);
|
||||
} else {
|
||||
dom.clearNode(this._metaHeading);
|
||||
dom.hide(this._metaHeading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract _fillBody(container: HTMLElement): void;
|
||||
|
||||
public _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
|
||||
if (!this._isShowing && heightInPixel < 0) {
|
||||
// Looks like the view zone got folded away!
|
||||
@@ -237,3 +256,21 @@ export abstract class PeekViewWidget extends ZoneWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#1E1E1E', light: '#FFFFFF', hc: '#0C141F' }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.'));
|
||||
export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: '#FFFFFF', light: '#333333', hc: '#FFFFFF' }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.'));
|
||||
export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#6c6c6cb3', hc: '#FFFFFF99' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.'));
|
||||
export const peekViewBorder = registerColor('peekView.border', { dark: '#007acc', light: '#007acc', hc: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.'));
|
||||
|
||||
export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hc: Color.black }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.'));
|
||||
export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hc: Color.white }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.'));
|
||||
export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hc: Color.white }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.'));
|
||||
export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hc: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.'));
|
||||
export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hc: Color.white }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.'));
|
||||
export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hc: Color.black }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.'));
|
||||
export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', { dark: peekViewEditorBackground, light: peekViewEditorBackground, hc: peekViewEditorBackground }, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.'));
|
||||
|
||||
export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hc: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.'));
|
||||
export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hc: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.'));
|
||||
export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.'));
|
||||
@@ -1,290 +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 nls from 'vs/nls';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Location, ReferenceProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { PeekContext, getOuterEditor } from './peekViewWidget';
|
||||
import { ReferencesController, RequestOptions, ctxReferenceSearchVisible } from './referencesController';
|
||||
import { ReferencesModel, OneReference } from './referencesModel';
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { ctxReferenceWidgetSearchTreeFocused } from 'vs/editor/contrib/referenceSearch/referencesWidget';
|
||||
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { coalesce, flatten } from 'vs/base/common/arrays';
|
||||
|
||||
export const defaultReferenceSearchOptions: RequestOptions = {
|
||||
getMetaTitle(model) {
|
||||
return model.references.length > 1 ? nls.localize('meta.titleReference', " – {0} references", model.references.length) : '';
|
||||
}
|
||||
};
|
||||
|
||||
export class ReferenceController implements editorCommon.IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.referenceController';
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
if (editor instanceof EmbeddedCodeEditorWidget) {
|
||||
PeekContext.inPeekEditor.bindTo(contextKeyService);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export class ReferenceAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.referenceSearch.trigger',
|
||||
label: nls.localize('references.action.label', "Peek References"),
|
||||
alias: 'Peek References',
|
||||
precondition: ContextKeyExpr.and(
|
||||
EditorContextKeys.hasReferenceProvider,
|
||||
PeekContext.notInPeekEditor,
|
||||
EditorContextKeys.isInEmbeddedEditor.toNegated()),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Shift | KeyCode.F12,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'navigation',
|
||||
order: 1.5
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = ReferencesController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(ReferenceController.ID, ReferenceController);
|
||||
|
||||
registerEditorAction(ReferenceAction);
|
||||
|
||||
let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition) => {
|
||||
if (!(resource instanceof URI)) {
|
||||
throw new Error('illegal argument, uri');
|
||||
}
|
||||
if (!position) {
|
||||
throw new Error('illegal argument, position');
|
||||
}
|
||||
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => {
|
||||
if (!isCodeEditor(control) || !control.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let controller = ReferencesController.get(control);
|
||||
if (!controller) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let references = createCancelablePromise(token => provideReferences(control.getModel(), Position.lift(position), token).then(references => new ReferencesModel(references)));
|
||||
let range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
|
||||
return Promise.resolve(controller.toggleWidget(range, references, defaultReferenceSearchOptions));
|
||||
});
|
||||
};
|
||||
|
||||
let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resource: URI, position: IPosition, references: Location[]) => {
|
||||
if (!(resource instanceof URI)) {
|
||||
throw new Error('illegal argument, uri expected');
|
||||
}
|
||||
|
||||
if (!references) {
|
||||
throw new Error('missing references');
|
||||
}
|
||||
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => {
|
||||
if (!isCodeEditor(control)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let controller = ReferencesController.get(control);
|
||||
if (!controller) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return controller.toggleWidget(
|
||||
new Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
||||
createCancelablePromise(_ => Promise.resolve(new ReferencesModel(references))),
|
||||
defaultReferenceSearchOptions
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// register commands
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'editor.action.findReferences',
|
||||
handler: findReferencesCommand
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'editor.action.showReferences',
|
||||
handler: showReferencesCommand,
|
||||
description: {
|
||||
description: 'Show references at a position in a file',
|
||||
args: [
|
||||
{ name: 'uri', description: 'The text document in which to show references', constraint: URI },
|
||||
{ name: 'position', description: 'The position at which to show', constraint: Position.isIPosition },
|
||||
{ name: 'locations', description: 'An array of locations.', constraint: Array },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
function closeActiveReferenceSearch(accessor: ServicesAccessor, args: any) {
|
||||
withController(accessor, controller => controller.closeWidget());
|
||||
}
|
||||
|
||||
function openReferenceToSide(accessor: ServicesAccessor, args: any) {
|
||||
const listService = accessor.get(IListService);
|
||||
|
||||
const focus = listService.lastFocusedList && listService.lastFocusedList.getFocus();
|
||||
if (focus instanceof OneReference) {
|
||||
withController(accessor, controller => controller.openReference(focus, true));
|
||||
}
|
||||
}
|
||||
|
||||
function withController(accessor: ServicesAccessor, fn: (controller: ReferencesController) => void): void {
|
||||
const outerEditor = getOuterEditor(accessor);
|
||||
if (!outerEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let controller = ReferencesController.get(outerEditor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
fn(controller);
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToNextReference',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyCode.F4,
|
||||
when: ctxReferenceSearchVisible,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToNextReferenceFromEmbeddedEditor',
|
||||
weight: KeybindingWeight.EditorContrib + 50,
|
||||
primary: KeyCode.F4,
|
||||
when: PeekContext.inPeekEditor,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToPreviousReference',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyMod.Shift | KeyCode.F4,
|
||||
when: ctxReferenceSearchVisible,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToPreviousReferenceFromEmbeddedEditor',
|
||||
weight: KeybindingWeight.EditorContrib + 50,
|
||||
primary: KeyMod.Shift | KeyCode.F4,
|
||||
when: PeekContext.inPeekEditor,
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.goToNextOrPreviousReference(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'closeReferenceSearch',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')),
|
||||
handler: closeActiveReferenceSearch
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'closeReferenceSearchEditor',
|
||||
weight: KeybindingWeight.EditorContrib - 101,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek')),
|
||||
handler: closeActiveReferenceSearch
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'openReferenceToSide',
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
mac: {
|
||||
primary: KeyMod.WinCtrl | KeyCode.Enter
|
||||
},
|
||||
when: ContextKeyExpr.and(ctxReferenceSearchVisible, ctxReferenceWidgetSearchTreeFocused),
|
||||
handler: openReferenceToSide
|
||||
});
|
||||
|
||||
export function provideReferences(model: ITextModel, position: Position, token: CancellationToken): Promise<Location[]> {
|
||||
|
||||
// collect references from all providers
|
||||
const promises = ReferenceProviderRegistry.ordered(model).map(provider => {
|
||||
return Promise.resolve(provider.provideReferences(model, position, { includeDeclaration: true }, token)).then(result => {
|
||||
if (Array.isArray(result)) {
|
||||
return <Location[]>result;
|
||||
}
|
||||
return undefined;
|
||||
}, err => {
|
||||
onUnexpectedExternalError(err);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(references => flatten(coalesce(references)));
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => provideReferences(model, position, CancellationToken.None));
|
||||
@@ -238,7 +238,7 @@ export class RenameAction extends EditorAction {
|
||||
primary: KeyCode.F2,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menuOpts: {
|
||||
contextMenuOpts: {
|
||||
group: '1_modification',
|
||||
order: 1.1
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class GrowSelectionAction extends AbstractSmartSelect {
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '1_basic',
|
||||
title: nls.localize({ key: 'miSmartSelectGrow', comment: ['&& denotes a mnemonic'] }, "&&Expand Selection"),
|
||||
@@ -196,7 +196,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect {
|
||||
},
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
menubarOpts: {
|
||||
menuOpts: {
|
||||
menuId: MenuId.MenubarSelectionMenu,
|
||||
group: '1_basic',
|
||||
title: nls.localize({ key: 'miSmartSelectShrink', comment: ['&& denotes a mnemonic'] }, "&&Shrink Selection"),
|
||||
|
||||
@@ -17,6 +17,7 @@ import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelec
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
|
||||
import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test';
|
||||
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
|
||||
|
||||
class MockJSMode extends MockMode {
|
||||
|
||||
@@ -45,7 +46,7 @@ suite('SmartSelect', () => {
|
||||
|
||||
setup(() => {
|
||||
const configurationService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService));
|
||||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService());
|
||||
mode = new MockJSMode();
|
||||
});
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ const _defaultOptions: ISnippetSessionInsertOptions = {
|
||||
|
||||
export class SnippetSession {
|
||||
|
||||
static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet): void {
|
||||
static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): void {
|
||||
const line = model.getLineContent(position.lineNumber);
|
||||
const lineLeadingWhitespace = getLeadingWhitespace(line, 0, position.column - 1);
|
||||
|
||||
@@ -342,13 +342,19 @@ export class SnippetSession {
|
||||
// adjust indentation of text markers, except for choise elements
|
||||
// which get adjusted when being selected
|
||||
const lines = marker.value.split(/\r\n|\r|\n/);
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
let templateLeadingWhitespace = getLeadingWhitespace(lines[i]);
|
||||
lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length);
|
||||
|
||||
if (adjustIndentation) {
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
let templateLeadingWhitespace = getLeadingWhitespace(lines[i]);
|
||||
lines[i] = model.normalizeIndentation(lineLeadingWhitespace + templateLeadingWhitespace) + lines[i].substr(templateLeadingWhitespace.length);
|
||||
}
|
||||
}
|
||||
const newValue = lines.join(model.getEOL());
|
||||
if (newValue !== marker.value) {
|
||||
marker.parent.replace(marker, [new Text(newValue)]);
|
||||
|
||||
if (adjustNewlines) {
|
||||
const newValue = lines.join(model.getEOL());
|
||||
if (newValue !== marker.value) {
|
||||
marker.parent.replace(marker, [new Text(newValue)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -439,9 +445,11 @@ export class SnippetSession {
|
||||
// happens when being asked for (default) or when this is a secondary
|
||||
// cursor and the leading whitespace is different
|
||||
const start = snippetSelection.getStartPosition();
|
||||
if (adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber))) {
|
||||
SnippetSession.adjustWhitespace(model, start, snippet);
|
||||
}
|
||||
SnippetSession.adjustWhitespace(
|
||||
model, start, snippet,
|
||||
adjustWhitespace || (idx > 0 && firstLineFirstNonWhitespace !== model.getLineFirstNonWhitespaceColumn(selection.positionLineNumber)),
|
||||
true
|
||||
);
|
||||
|
||||
snippet.resolveVariables(new CompositeSnippetVariableResolver([
|
||||
modelBasedVariableResolver,
|
||||
|
||||
@@ -41,7 +41,7 @@ suite('SnippetSession', function () {
|
||||
|
||||
function assertNormalized(position: IPosition, input: string, expected: string): void {
|
||||
const snippet = new SnippetParser().parse(input);
|
||||
SnippetSession.adjustWhitespace(model, position, snippet);
|
||||
SnippetSession.adjustWhitespace(model, position, snippet, true, true);
|
||||
assert.equal(snippet.toTextmateString(), expected);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ export class CompletionModel {
|
||||
// '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 - item.completion.range.startColumn;
|
||||
const overwriteBefore = item.position.column - item.editStart.column;
|
||||
const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column);
|
||||
if (word.length !== wordLen) {
|
||||
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
|
||||
|
||||
@@ -68,12 +68,9 @@
|
||||
}
|
||||
|
||||
.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;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/** Styles for each row in the list element **/
|
||||
@@ -279,3 +276,11 @@
|
||||
border-radius: 3px;
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
|
||||
|
||||
/* replace/insert decorations */
|
||||
|
||||
.monaco-editor .suggest-insert-unexpected {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ export class CompletionItem {
|
||||
|
||||
readonly resolve: (token: CancellationToken) => Promise<void>;
|
||||
|
||||
//
|
||||
readonly editStart: IPosition;
|
||||
readonly editInsertEnd: IPosition;
|
||||
readonly editReplaceEnd: IPosition;
|
||||
|
||||
// perf
|
||||
readonly labelLow: string;
|
||||
readonly sortTextLow?: string;
|
||||
@@ -54,6 +59,17 @@ export class CompletionItem {
|
||||
this.sortTextLow = completion.sortText && completion.sortText.toLowerCase();
|
||||
this.filterTextLow = completion.filterText && completion.filterText.toLowerCase();
|
||||
|
||||
// normalize ranges
|
||||
if (Range.isIRange(completion.range)) {
|
||||
this.editStart = new Position(completion.range.startLineNumber, completion.range.startColumn);
|
||||
this.editInsertEnd = new Position(completion.range.endLineNumber, completion.range.endColumn);
|
||||
this.editReplaceEnd = new Position(completion.range.endLineNumber, completion.range.endColumn);
|
||||
} else {
|
||||
this.editStart = new Position(completion.range.insert.startLineNumber, completion.range.insert.startColumn);
|
||||
this.editInsertEnd = new Position(completion.range.insert.endLineNumber, completion.range.insert.endColumn);
|
||||
this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn);
|
||||
}
|
||||
|
||||
// create the suggestion resolver
|
||||
const { resolveCompletionItem } = provider;
|
||||
if (typeof resolveCompletionItem !== 'function') {
|
||||
@@ -122,8 +138,12 @@ export function provideSuggestionItems(
|
||||
token: CancellationToken = CancellationToken.None
|
||||
): Promise<CompletionItem[]> {
|
||||
|
||||
const wordUntil = model.getWordUntilPosition(position);
|
||||
const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);
|
||||
const word = model.getWordAtPosition(position);
|
||||
const defaultReplaceRange = word ? new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) : Range.fromPositions(position);
|
||||
const defaultInsertRange = defaultReplaceRange.setEndPosition(position.lineNumber, position.column);
|
||||
|
||||
// const wordUntil = model.getWordUntilPosition(position);
|
||||
// const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);
|
||||
|
||||
position = position.clone();
|
||||
|
||||
@@ -159,7 +179,7 @@ export function provideSuggestionItems(
|
||||
|
||||
// fill in default range when missing
|
||||
if (!suggestion.range) {
|
||||
suggestion.range = defaultRange;
|
||||
suggestion.range = { insert: defaultInsertRange, replace: defaultReplaceRange };
|
||||
}
|
||||
// fill in default sortText when missing
|
||||
if (!suggestion.sortText) {
|
||||
|
||||
@@ -26,7 +26,7 @@ export class CommitCharacterController {
|
||||
this._disposables.add(widget.onDidHide(this.reset, this));
|
||||
|
||||
this._disposables.add(editor.onWillType(text => {
|
||||
if (this._active) {
|
||||
if (this._active && !widget.isFrozen()) {
|
||||
const ch = text.charCodeAt(text.length - 1);
|
||||
if (this._active.acceptCharacters.has(ch) && editor.getOption(EditorOption.acceptSuggestionOnCommitCharacter)) {
|
||||
accept(this._active.item);
|
||||
|
||||
@@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { isObject, assertType } from 'vs/base/common/types';
|
||||
import { CommitCharacterController } from './suggestCommitCharacters';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter';
|
||||
|
||||
/**
|
||||
* Stop suggest widget from disappearing when clicking into other areas
|
||||
@@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution {
|
||||
return editor.getContribution<SuggestController>(SuggestController.ID);
|
||||
}
|
||||
|
||||
private readonly _model: SuggestModel;
|
||||
private readonly _widget: IdleValue<SuggestWidget>;
|
||||
readonly editor: ICodeEditor;
|
||||
readonly model: SuggestModel;
|
||||
readonly widget: IdleValue<SuggestWidget>;
|
||||
|
||||
private readonly _alternatives: IdleValue<SuggestAlternatives>;
|
||||
private readonly _lineSuffix = new MutableDisposable<LineSuffix>();
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
editor: ICodeEditor,
|
||||
@IEditorWorkerService editorWorker: IEditorWorkerService,
|
||||
@ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._model = new SuggestModel(this._editor, editorWorker);
|
||||
this.editor = editor;
|
||||
this.model = new SuggestModel(this.editor, editorWorker);
|
||||
|
||||
this._widget = new IdleValue(() => {
|
||||
this.widget = new IdleValue(() => {
|
||||
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this.editor);
|
||||
|
||||
this._toDispose.add(widget);
|
||||
this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this));
|
||||
|
||||
// Wire up logic to accept a suggestion on certain characters
|
||||
const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop));
|
||||
const commitCharacterController = new CommitCharacterController(this.editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop));
|
||||
this._toDispose.add(commitCharacterController);
|
||||
this._toDispose.add(this._model.onDidSuggest(e => {
|
||||
this._toDispose.add(this.model.onDidSuggest(e => {
|
||||
if (e.completionModel.items.length === 0) {
|
||||
commitCharacterController.reset();
|
||||
}
|
||||
@@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution {
|
||||
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
|
||||
this._toDispose.add(widget.onDidFocus(({ item }) => {
|
||||
|
||||
const position = this._editor.getPosition()!;
|
||||
const startColumn = item.completion.range.startColumn;
|
||||
const position = this.editor.getPosition()!;
|
||||
const startColumn = item.editStart.column;
|
||||
const endColumn = position.column;
|
||||
let value = true;
|
||||
if (
|
||||
this._editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart'
|
||||
&& this._model.state === State.Auto
|
||||
this.editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart'
|
||||
&& this.model.state === State.Auto
|
||||
&& !item.completion.command
|
||||
&& !item.completion.additionalTextEdits
|
||||
&& !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)
|
||||
&& endColumn - startColumn === item.completion.insertText.length
|
||||
) {
|
||||
const oldText = this._editor.getModel()!.getValueInRange({
|
||||
const oldText = this.editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
@@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution {
|
||||
}));
|
||||
this._toDispose.add(toDisposable(() => makesTextEdit.reset()));
|
||||
|
||||
|
||||
|
||||
return widget;
|
||||
});
|
||||
|
||||
this._alternatives = new IdleValue(() => {
|
||||
return this._toDispose.add(new SuggestAlternatives(this._editor, this._contextKeyService));
|
||||
return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService));
|
||||
});
|
||||
|
||||
this._toDispose.add(_instantiationService.createInstance(WordContextKey, _editor));
|
||||
this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor));
|
||||
|
||||
this._toDispose.add(this._model.onDidTrigger(e => {
|
||||
this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
this._lineSuffix.value = new LineSuffix(this._editor.getModel()!, e.position);
|
||||
this._toDispose.add(this.model.onDidTrigger(e => {
|
||||
this.widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position);
|
||||
}));
|
||||
this._toDispose.add(this._model.onDidSuggest(e => {
|
||||
this._toDispose.add(this.model.onDidSuggest(e => {
|
||||
if (!e.shy) {
|
||||
let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items);
|
||||
this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
let index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items);
|
||||
this.widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(this._model.onDidCancel(e => {
|
||||
this._toDispose.add(this.model.onDidCancel(e => {
|
||||
if (!e.retrigger) {
|
||||
this._widget.getValue().hideWidget();
|
||||
this.widget.getValue().hideWidget();
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(this._editor.onDidBlurEditorWidget(() => {
|
||||
this._toDispose.add(this.editor.onDidBlurEditorWidget(() => {
|
||||
if (!_sticky) {
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
this._toDispose.add(this._widget.getValue().onDetailsKeyDown(e => {
|
||||
this._toDispose.add(this.widget.getValue().onDetailsKeyDown(e => {
|
||||
// cmd + c on macOS, ctrl + c on Win / Linux
|
||||
if (
|
||||
e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) ||
|
||||
@@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
if (!e.toKeybinding().isModifierKey()) {
|
||||
this._editor.focus();
|
||||
this.editor.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
// Manage the acceptSuggestionsOnEnter context key
|
||||
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
|
||||
let updateFromConfig = () => {
|
||||
const acceptSuggestionOnEnter = this._editor.getOption(EditorOption.acceptSuggestionOnEnter);
|
||||
const acceptSuggestionOnEnter = this.editor.getOption(EditorOption.acceptSuggestionOnEnter);
|
||||
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
|
||||
};
|
||||
this._toDispose.add(this._editor.onDidChangeConfiguration(() => updateFromConfig()));
|
||||
this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
|
||||
updateFromConfig();
|
||||
|
||||
// create range highlighter
|
||||
this._toDispose.add(new SuggestRangeHighlighter(this));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._alternatives.dispose();
|
||||
this._toDispose.dispose();
|
||||
this._widget.dispose();
|
||||
this._model.dispose();
|
||||
this.widget.dispose();
|
||||
this.model.dispose();
|
||||
this._lineSuffix.dispose();
|
||||
}
|
||||
|
||||
@@ -231,85 +240,66 @@ export class SuggestController implements IEditorContribution {
|
||||
): void {
|
||||
if (!event || !event.item) {
|
||||
this._alternatives.getValue().reset();
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const model = this.editor.getModel();
|
||||
const modelVersionNow = model.getAlternativeVersionId();
|
||||
const { completion: suggestion, position } = event.item;
|
||||
const columnDelta = this._editor.getPosition().column - position.column;
|
||||
const { item } = event;
|
||||
const { completion: suggestion } = item;
|
||||
|
||||
// pushing undo stops *before* additional text edits and
|
||||
// *after* the main edit
|
||||
if (!(flags & InsertFlags.NoBeforeUndoStop)) {
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
}
|
||||
|
||||
if (Array.isArray(suggestion.additionalTextEdits)) {
|
||||
this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
// keep item in memory
|
||||
this._memoryService.memorize(model, this._editor.getPosition(), event.item);
|
||||
this._memoryService.memorize(model, this.editor.getPosition(), item);
|
||||
|
||||
let { insertText } = suggestion;
|
||||
if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) {
|
||||
insertText = SnippetParser.escape(insertText);
|
||||
}
|
||||
|
||||
let overwriteBefore = position.column - suggestion.range.startColumn;
|
||||
let overwriteAfter = suggestion.range.endColumn - position.column;
|
||||
let suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0;
|
||||
let word = model.getWordAtPosition(this._editor.getPosition());
|
||||
const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig));
|
||||
|
||||
const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig
|
||||
? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept
|
||||
: this._editor.getOption(EditorOption.suggest).overwriteOnAccept;
|
||||
if (!overwriteConfig) {
|
||||
if (overwriteAfter > 0 && word && suggestion.range.endColumn === word.endColumn) {
|
||||
// don't overwrite anything right of the cursor, overrule extension even when the
|
||||
// completion only replaces a word...
|
||||
overwriteAfter = 0;
|
||||
}
|
||||
} else {
|
||||
if (overwriteAfter === 0 && word) {
|
||||
// compute fallback overwrite length
|
||||
overwriteAfter = word.endColumn - this._editor.getPosition().column;
|
||||
}
|
||||
}
|
||||
|
||||
SnippetController2.get(this._editor).insert(insertText, {
|
||||
overwriteBefore: overwriteBefore + columnDelta,
|
||||
overwriteAfter: overwriteAfter + suffixDelta,
|
||||
SnippetController2.get(this.editor).insert(insertText, {
|
||||
overwriteBefore: info.overwriteBefore,
|
||||
overwriteAfter: info.overwriteAfter,
|
||||
undoStopBefore: false,
|
||||
undoStopAfter: false,
|
||||
adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace)
|
||||
});
|
||||
|
||||
if (!(flags & InsertFlags.NoAfterUndoStop)) {
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
}
|
||||
|
||||
if (!suggestion.command) {
|
||||
// done
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
|
||||
} else if (suggestion.command.id === TriggerSuggestAction.id) {
|
||||
// retigger
|
||||
this._model.trigger({ auto: true, shy: false }, true);
|
||||
this.model.trigger({ auto: true, shy: false }, true);
|
||||
|
||||
} else {
|
||||
// exec command, done
|
||||
this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : []))
|
||||
.catch(onUnexpectedError)
|
||||
.finally(() => this._model.clear()); // <- clear only now, keep commands alive
|
||||
this._model.cancel();
|
||||
.finally(() => this.model.clear()); // <- clear only now, keep commands alive
|
||||
this.model.cancel();
|
||||
}
|
||||
|
||||
if (flags & InsertFlags.KeepAlternativeSuggestions) {
|
||||
@@ -333,6 +323,24 @@ export class SuggestController implements IEditorContribution {
|
||||
this._alertCompletionItem(event.item);
|
||||
}
|
||||
|
||||
getOverwriteInfo(item: CompletionItem, toggleMode: boolean): { overwriteBefore: number, overwriteAfter: number } {
|
||||
assertType(this.editor.hasModel());
|
||||
|
||||
let replace = this.editor.getOption(EditorOption.suggest).insertMode === 'replace';
|
||||
if (toggleMode) {
|
||||
replace = !replace;
|
||||
}
|
||||
const overwriteBefore = item.position.column - item.editStart.column;
|
||||
const overwriteAfter = (replace ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column;
|
||||
const columnDelta = this.editor.getPosition().column - item.position.column;
|
||||
const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0;
|
||||
|
||||
return {
|
||||
overwriteBefore: overwriteBefore + columnDelta,
|
||||
overwriteAfter: overwriteAfter + suffixDelta
|
||||
};
|
||||
}
|
||||
|
||||
private _alertCompletionItem({ completion: suggestion }: CompletionItem): void {
|
||||
if (isNonEmptyArray(suggestion.additionalTextEdits)) {
|
||||
let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length);
|
||||
@@ -341,22 +349,22 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
triggerSuggest(onlyFrom?: Set<CompletionItemProvider>): void {
|
||||
if (this._editor.hasModel()) {
|
||||
this._model.trigger({ auto: false, shy: false }, false, onlyFrom);
|
||||
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
if (this.editor.hasModel()) {
|
||||
this.model.trigger({ auto: false, shy: false }, false, onlyFrom);
|
||||
this.editor.revealLine(this.editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
triggerSuggestAndAcceptBest(arg: { fallback: string }): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
|
||||
}
|
||||
const positionNow = this._editor.getPosition();
|
||||
const positionNow = this.editor.getPosition();
|
||||
|
||||
const fallback = () => {
|
||||
if (positionNow.equals(this._editor.getPosition()!)) {
|
||||
if (positionNow.equals(this.editor.getPosition()!)) {
|
||||
this._commandService.executeCommand(arg.fallback);
|
||||
}
|
||||
};
|
||||
@@ -366,14 +374,14 @@ export class SuggestController implements IEditorContribution {
|
||||
// snippet, other editor -> makes edit
|
||||
return true;
|
||||
}
|
||||
const position = this._editor.getPosition()!;
|
||||
const startColumn = item.completion.range.startColumn;
|
||||
const position = this.editor.getPosition()!;
|
||||
const startColumn = item.editStart.column;
|
||||
const endColumn = position.column;
|
||||
if (endColumn - startColumn !== item.completion.insertText.length) {
|
||||
// unequal lengths -> makes edit
|
||||
return true;
|
||||
}
|
||||
const textNow = this._editor.getModel()!.getValueInRange({
|
||||
const textNow = this.editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
@@ -383,41 +391,41 @@ export class SuggestController implements IEditorContribution {
|
||||
return textNow !== item.completion.insertText;
|
||||
};
|
||||
|
||||
Event.once(this._model.onDidTrigger)(_ => {
|
||||
Event.once(this.model.onDidTrigger)(_ => {
|
||||
// wait for trigger because only then the cancel-event is trustworthy
|
||||
let listener: IDisposable[] = [];
|
||||
|
||||
Event.any<any>(this._model.onDidTrigger, this._model.onDidCancel)(() => {
|
||||
Event.any<any>(this.model.onDidTrigger, this.model.onDidCancel)(() => {
|
||||
// retrigger or cancel -> try to type default text
|
||||
dispose(listener);
|
||||
fallback();
|
||||
}, undefined, listener);
|
||||
|
||||
this._model.onDidSuggest(({ completionModel }) => {
|
||||
this.model.onDidSuggest(({ completionModel }) => {
|
||||
dispose(listener);
|
||||
if (completionModel.items.length === 0) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
const index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, completionModel.items);
|
||||
const index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, completionModel.items);
|
||||
const item = completionModel.items[index];
|
||||
if (!makesTextEdit(item)) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop);
|
||||
|
||||
}, undefined, listener);
|
||||
});
|
||||
|
||||
this._model.trigger({ auto: false, shy: true });
|
||||
this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
this.model.trigger({ auto: false, shy: true });
|
||||
this.editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void {
|
||||
const item = this._widget.getValue().getFocusedItem();
|
||||
const item = this.widget.getValue().getFocusedItem();
|
||||
let flags = 0;
|
||||
if (keepAlternativeSuggestions) {
|
||||
flags |= InsertFlags.KeepAlternativeSuggestions;
|
||||
@@ -437,45 +445,45 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
cancelSuggestWidget(): void {
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this._widget.getValue().hideWidget();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
this.widget.getValue().hideWidget();
|
||||
}
|
||||
|
||||
selectNextSuggestion(): void {
|
||||
this._widget.getValue().selectNext();
|
||||
this.widget.getValue().selectNext();
|
||||
}
|
||||
|
||||
selectNextPageSuggestion(): void {
|
||||
this._widget.getValue().selectNextPage();
|
||||
this.widget.getValue().selectNextPage();
|
||||
}
|
||||
|
||||
selectLastSuggestion(): void {
|
||||
this._widget.getValue().selectLast();
|
||||
this.widget.getValue().selectLast();
|
||||
}
|
||||
|
||||
selectPrevSuggestion(): void {
|
||||
this._widget.getValue().selectPrevious();
|
||||
this.widget.getValue().selectPrevious();
|
||||
}
|
||||
|
||||
selectPrevPageSuggestion(): void {
|
||||
this._widget.getValue().selectPreviousPage();
|
||||
this.widget.getValue().selectPreviousPage();
|
||||
}
|
||||
|
||||
selectFirstSuggestion(): void {
|
||||
this._widget.getValue().selectFirst();
|
||||
this.widget.getValue().selectFirst();
|
||||
}
|
||||
|
||||
toggleSuggestionDetails(): void {
|
||||
this._widget.getValue().toggleDetails();
|
||||
this.widget.getValue().toggleDetails();
|
||||
}
|
||||
|
||||
toggleExplainMode(): void {
|
||||
this._widget.getValue().toggleExplainMode();
|
||||
this.widget.getValue().toggleExplainMode();
|
||||
}
|
||||
|
||||
toggleSuggestionFocus(): void {
|
||||
this._widget.getValue().toggleDetailsFocus();
|
||||
this.widget.getValue().toggleDetailsFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +500,7 @@ export class TriggerSuggestAction extends EditorAction {
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Space,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.Space },
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.Space, secondary: [KeyMod.Alt | KeyCode.Escape] },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/comm
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes';
|
||||
import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { CompletionModel } from './completionModel';
|
||||
import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport, SnippetSortOrder, CompletionOptions } from './suggest';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
@@ -388,9 +388,7 @@ export class SuggestModel implements IDisposable {
|
||||
this._requestToken = new CancellationTokenSource();
|
||||
|
||||
// kind filter and snippet sort rules
|
||||
const suggestOptions = this._editor.getOption(EditorOption.suggest);
|
||||
const snippetSuggestions = this._editor.getOption(EditorOption.snippetSuggestions);
|
||||
let itemKindFilter = new Set<CompletionItemKind>();
|
||||
let snippetSortOrder = SnippetSortOrder.Inline;
|
||||
switch (snippetSuggestions) {
|
||||
case 'top':
|
||||
@@ -403,19 +401,9 @@ export class SuggestModel implements IDisposable {
|
||||
case 'bottom':
|
||||
snippetSortOrder = SnippetSortOrder.Bottom;
|
||||
break;
|
||||
case 'none':
|
||||
itemKindFilter.add(CompletionItemKind.Snippet);
|
||||
break;
|
||||
}
|
||||
|
||||
// kind filter
|
||||
for (const key in suggestOptions.filteredTypes) {
|
||||
const kind = completionKindFromString(key, true);
|
||||
if (typeof kind !== 'undefined' && suggestOptions.filteredTypes[key] === false) {
|
||||
itemKindFilter.add(kind);
|
||||
}
|
||||
}
|
||||
|
||||
let itemKindFilter = SuggestModel._createItemKindFilter(this._editor);
|
||||
let wordDistance = WordDistance.create(this._editorWorker, this._editor);
|
||||
|
||||
let items = provideSuggestionItems(
|
||||
@@ -467,6 +455,48 @@ export class SuggestModel implements IDisposable {
|
||||
}).catch(onUnexpectedError);
|
||||
}
|
||||
|
||||
private static _createItemKindFilter(editor: ICodeEditor): Set<CompletionItemKind> {
|
||||
// kind filter and snippet sort rules
|
||||
const result = new Set<CompletionItemKind>();
|
||||
|
||||
// snippet setting
|
||||
const snippetSuggestions = editor.getOption(EditorOption.snippetSuggestions);
|
||||
if (snippetSuggestions === 'none') {
|
||||
result.add(CompletionItemKind.Snippet);
|
||||
}
|
||||
|
||||
// type setting
|
||||
const suggestOptions = editor.getOption(EditorOption.suggest);
|
||||
if (!suggestOptions.showMethods) { result.add(CompletionItemKind.Method); }
|
||||
if (!suggestOptions.showFunctions) { result.add(CompletionItemKind.Function); }
|
||||
if (!suggestOptions.showConstructors) { result.add(CompletionItemKind.Constructor); }
|
||||
if (!suggestOptions.showFields) { result.add(CompletionItemKind.Field); }
|
||||
if (!suggestOptions.showVariables) { result.add(CompletionItemKind.Variable); }
|
||||
if (!suggestOptions.showClasses) { result.add(CompletionItemKind.Class); }
|
||||
if (!suggestOptions.showStructs) { result.add(CompletionItemKind.Struct); }
|
||||
if (!suggestOptions.showInterfaces) { result.add(CompletionItemKind.Interface); }
|
||||
if (!suggestOptions.showModules) { result.add(CompletionItemKind.Module); }
|
||||
if (!suggestOptions.showProperties) { result.add(CompletionItemKind.Property); }
|
||||
if (!suggestOptions.showEvents) { result.add(CompletionItemKind.Event); }
|
||||
if (!suggestOptions.showOperators) { result.add(CompletionItemKind.Operator); }
|
||||
if (!suggestOptions.showUnits) { result.add(CompletionItemKind.Unit); }
|
||||
if (!suggestOptions.showValues) { result.add(CompletionItemKind.Value); }
|
||||
if (!suggestOptions.showConstants) { result.add(CompletionItemKind.Constant); }
|
||||
if (!suggestOptions.showEnums) { result.add(CompletionItemKind.Enum); }
|
||||
if (!suggestOptions.showEnumMembers) { result.add(CompletionItemKind.EnumMember); }
|
||||
if (!suggestOptions.showKeywords) { result.add(CompletionItemKind.Keyword); }
|
||||
if (!suggestOptions.showWords) { result.add(CompletionItemKind.Text); }
|
||||
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Color); }
|
||||
if (!suggestOptions.showFiles) { result.add(CompletionItemKind.File); }
|
||||
if (!suggestOptions.showReferences) { result.add(CompletionItemKind.Reference); }
|
||||
if (!suggestOptions.showColors) { result.add(CompletionItemKind.Customcolor); }
|
||||
if (!suggestOptions.showFolders) { result.add(CompletionItemKind.Folder); }
|
||||
if (!suggestOptions.showTypeParameters) { result.add(CompletionItemKind.TypeParameter); }
|
||||
if (!suggestOptions.showSnippets) { result.add(CompletionItemKind.Snippet); }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _onNewContext(ctx: LineContext): void {
|
||||
|
||||
if (!this._context) {
|
||||
|
||||
125
src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts
Normal file
125
src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
|
||||
export class SuggestRangeHighlighter {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
private _decorations: string[] = [];
|
||||
private _widgetListener?: IDisposable;
|
||||
private _shiftKeyListener?: IDisposable;
|
||||
private _currentItem?: CompletionItem;
|
||||
|
||||
constructor(private readonly _controller: SuggestController) {
|
||||
|
||||
this._disposables.add(_controller.model.onDidSuggest(e => {
|
||||
if (!e.shy) {
|
||||
const widget = this._controller.widget.getValue();
|
||||
const focused = widget.getFocusedItem();
|
||||
if (focused) {
|
||||
this._highlight(focused.item);
|
||||
}
|
||||
if (!this._widgetListener) {
|
||||
this._widgetListener = widget.onDidFocus(e => this._highlight(e.item));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._disposables.add(_controller.model.onDidCancel(() => {
|
||||
this._reset();
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._reset();
|
||||
this._disposables.dispose();
|
||||
dispose(this._widgetListener);
|
||||
dispose(this._shiftKeyListener);
|
||||
}
|
||||
|
||||
private _reset(): void {
|
||||
this._decorations = this._controller.editor.deltaDecorations(this._decorations, []);
|
||||
if (this._shiftKeyListener) {
|
||||
this._shiftKeyListener.dispose();
|
||||
this._shiftKeyListener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _highlight(item: CompletionItem) {
|
||||
|
||||
this._currentItem = item;
|
||||
const opts = this._controller.editor.getOption(EditorOption.suggest);
|
||||
let newDeco: IModelDeltaDecoration[] = [];
|
||||
|
||||
if (opts.insertHighlight) {
|
||||
if (!this._shiftKeyListener) {
|
||||
this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!));
|
||||
}
|
||||
|
||||
const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed);
|
||||
const position = this._controller.editor.getPosition()!;
|
||||
|
||||
if (opts.insertMode === 'insert' && info.overwriteAfter > 0) {
|
||||
// wants inserts but got replace-mode -> highlight AFTER range
|
||||
newDeco = [{
|
||||
range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter),
|
||||
options: { inlineClassName: 'suggest-insert-unexpected' }
|
||||
}];
|
||||
|
||||
} else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) {
|
||||
// want replace but likely got insert -> highlight AFTER range
|
||||
const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position);
|
||||
if (wordInfo && wordInfo.endColumn > position.column) {
|
||||
newDeco = [{
|
||||
range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn),
|
||||
options: { inlineClassName: 'suggest-insert-unexpected' }
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update editor decorations
|
||||
this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco);
|
||||
}
|
||||
}
|
||||
|
||||
const shiftKey = new class ShiftKey extends Emitter<boolean> {
|
||||
|
||||
private readonly _subscriptions = new DisposableStore();
|
||||
private _isPressed: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
|
||||
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
|
||||
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false));
|
||||
this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false));
|
||||
}
|
||||
|
||||
get isPressed(): boolean {
|
||||
return this._isPressed;
|
||||
}
|
||||
|
||||
set isPressed(value: boolean) {
|
||||
if (this._isPressed !== value) {
|
||||
this._isPressed = value;
|
||||
this.fire(value);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._subscriptions.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
};
|
||||
@@ -172,7 +172,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
|
||||
if (suggestion.kind === CompletionItemKind.Color && extractColor(element, color)) {
|
||||
// special logic for 'color' completion items
|
||||
data.icon.className = 'icon customcolor';
|
||||
data.iconContainer.className = 'icon customcolor';
|
||||
data.iconContainer.className = 'icon hide';
|
||||
data.colorspan.style.backgroundColor = color[0];
|
||||
|
||||
} else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) {
|
||||
@@ -1074,6 +1074,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
return SuggestWidget.ID;
|
||||
}
|
||||
|
||||
isFrozen(): boolean {
|
||||
return this.state === State.Frozen;
|
||||
}
|
||||
|
||||
private updateListHeight(): number {
|
||||
let height = 0;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as modes from 'vs/editor/common/modes';
|
||||
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
|
||||
import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
import { EditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorOptions, InternalSuggestOptions } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export function createSuggestItem(label: string, overwriteBefore: number, kind = modes.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem {
|
||||
const suggestion: modes.CompletionItem = {
|
||||
@@ -33,6 +33,40 @@ export function createSuggestItem(label: string, overwriteBefore: number, kind =
|
||||
}
|
||||
suite('CompletionModel', function () {
|
||||
|
||||
let defaultOptions = <InternalSuggestOptions>{
|
||||
insertMode: 'insert',
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
filterGraceful: true,
|
||||
localityBonus: false,
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
maxVisibleSuggestions: 12,
|
||||
showMethods: true,
|
||||
showFunctions: true,
|
||||
showConstructors: true,
|
||||
showFields: true,
|
||||
showVariables: true,
|
||||
showClasses: true,
|
||||
showStructs: true,
|
||||
showInterfaces: true,
|
||||
showModules: true,
|
||||
showProperties: true,
|
||||
showEvents: true,
|
||||
showOperators: true,
|
||||
showUnits: true,
|
||||
showValues: true,
|
||||
showConstants: true,
|
||||
showEnums: true,
|
||||
showEnumMembers: true,
|
||||
showKeywords: true,
|
||||
showWords: true,
|
||||
showColors: true,
|
||||
showFiles: true,
|
||||
showReferences: true,
|
||||
showFolders: true,
|
||||
showTypeParameters: true,
|
||||
showSnippets: true,
|
||||
};
|
||||
|
||||
let model: CompletionModel;
|
||||
|
||||
@@ -158,16 +192,7 @@ suite('CompletionModel', function () {
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
characterCountDelta: 0
|
||||
}, WordDistance.None, {
|
||||
overwriteOnAccept: false,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
filterGraceful: true,
|
||||
localityBonus: false,
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
maxVisibleSuggestions: 12,
|
||||
filteredTypes: Object.create(null)
|
||||
}, 'top');
|
||||
}, WordDistance.None, defaultOptions, 'top');
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
@@ -186,16 +211,7 @@ suite('CompletionModel', function () {
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
characterCountDelta: 0
|
||||
}, WordDistance.None, {
|
||||
overwriteOnAccept: false,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
filterGraceful: true,
|
||||
localityBonus: false,
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
maxVisibleSuggestions: 12,
|
||||
filteredTypes: Object.create(null)
|
||||
}, 'bottom');
|
||||
}, WordDistance.None, defaultOptions, 'bottom');
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
@@ -213,16 +229,7 @@ suite('CompletionModel', function () {
|
||||
], 1, {
|
||||
leadingLineContent: 's',
|
||||
characterCountDelta: 0
|
||||
}, WordDistance.None, {
|
||||
overwriteOnAccept: false,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
filterGraceful: true,
|
||||
localityBonus: false,
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
maxVisibleSuggestions: 12,
|
||||
filteredTypes: Object.create(null)
|
||||
}, 'inline');
|
||||
}, WordDistance.None, defaultOptions, 'inline');
|
||||
|
||||
assert.equal(model.items.length, 2);
|
||||
const [a, b] = model.items;
|
||||
|
||||
@@ -351,7 +351,7 @@ suite('WordOperations', () => {
|
||||
});
|
||||
|
||||
test('cursorWordAccessibilityRight', () => {
|
||||
const EXPECTED = [' /* Just| some| more| text| a|+= 3| +5|-3| + 7| */ |'].join('\n');
|
||||
const EXPECTED = [' /* |Just |some |more |text |a+= |3 +|5-|3 + |7 */ |'].join('\n');
|
||||
const [text,] = deserializePipePositions(EXPECTED);
|
||||
const actualStops = testRepeatedActionAndExtractPositions(
|
||||
text,
|
||||
|
||||
Reference in New Issue
Block a user