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:
Anthony Dresser
2019-12-04 19:28:22 -08:00
committed by GitHub
parent a8818ab0df
commit f5ce7fb2a5
1507 changed files with 42813 additions and 27370 deletions

View File

@@ -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"),

View File

@@ -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;
});

View File

@@ -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);

View 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);
}
}

View File

@@ -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;

View File

@@ -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;
};
}

View File

@@ -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);
}
}

View File

@@ -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 };
}
}

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View 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,
) { }
}

View File

@@ -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);
}

View File

@@ -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;
}
});
}
}

View File

@@ -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; }

View File

@@ -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 = '&nbsp;';
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 = '&nbsp;';
}
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>&#160;|&#160;</span>';
}
html.push(part);
}
}
const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === '&nbsp;';
this._domNode.innerHTML = html.join('<span>&nbsp;|&nbsp;</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 = '&#160;';
}
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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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
}*/
});
}

View File

@@ -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

View File

@@ -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 {

View 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)]);
});
});
});

View File

@@ -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);

View File

@@ -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(`

View File

@@ -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"),

View File

@@ -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}; }`);
}
});
});

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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));
});
});
});

View File

@@ -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');
});
});

View File

@@ -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 });

View File

@@ -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.

View File

@@ -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

View File

@@ -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
});

View File

@@ -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);

View File

@@ -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';

View File

@@ -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;
}

View 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

View File

@@ -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));

View File

@@ -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';

View File

@@ -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);

View File

@@ -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));
}
}
});

View File

@@ -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;
}
}

View File

@@ -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 {` +

View File

@@ -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);
}
}

View File

@@ -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';

View File

@@ -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');

View File

@@ -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;
}

View File

@@ -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}; }`);

View File

@@ -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);
});
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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"),

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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', () => {

View File

@@ -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') {

View File

@@ -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';

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}; }`);

View File

@@ -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;

View File

@@ -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.'));

View File

@@ -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));

View File

@@ -238,7 +238,7 @@ export class RenameAction extends EditorAction {
primary: KeyCode.F2,
weight: KeybindingWeight.EditorContrib
},
menuOpts: {
contextMenuOpts: {
group: '1_modification',
order: 1.1
}

View File

@@ -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"),

View File

@@ -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();
});

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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
}
});

View File

@@ -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) {

View 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();
}
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,