Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -21,6 +21,7 @@ import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/edito
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
const overviewRulerBracketMatchForeground = registerColor('editorOverviewRuler.bracketMatchForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerBracketMatchForeground', 'Overview ruler marker color for matching brackets.'));
@@ -322,3 +323,13 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-editor .bracket-match { border: 1px solid ${bracketMatchBorder}; }`);
}
});
// Go to menu
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '5_infile_nav',
command: {
id: 'editor.action.jumpToBracket',
title: nls.localize({ key: 'miGoToBracket', comment: ['&& denotes a mnemonic'] }, "Go to &&Bracket")
},
order: 2
});

View File

@@ -28,8 +28,8 @@ class MoveCaretAction extends EditorAction {
let commands: ICommand[] = [];
let selections = editor.getSelections();
for (let i = 0; i < selections.length; i++) {
commands.push(new MoveCaretCommand(selections[i], this.left));
for (const selection of selections) {
commands.push(new MoveCaretCommand(selection, this.left));
}
editor.pushUndoStop();

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten, mergeSort, isNonEmptyArray } from 'vs/base/common/arrays';
import { flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
@@ -13,73 +13,59 @@ import { Selection } from 'vs/editor/common/core/selection';
import { ITextModel } from 'vs/editor/common/model';
import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger';
import { CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind, CodeActionFilter } from './codeActionTrigger';
export function getCodeActions(
model: ITextModel,
rangeOrSelection: Range | Selection,
trigger: CodeActionTrigger,
token: CancellationToken
): Promise<CodeAction[]> {
const filter = trigger.filter || {};
export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Selection, trigger?: CodeActionTrigger, token: CancellationToken = CancellationToken.None): Promise<CodeAction[]> {
const codeActionContext: CodeActionContext = {
only: trigger && trigger.filter && trigger.filter.kind ? trigger.filter.kind.value : undefined,
trigger: trigger && trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
only: filter.kind ? filter.kind.value : undefined,
trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
};
const promises = CodeActionProviderRegistry.all(model)
.filter(provider => {
if (!provider.providedCodeActionKinds) {
return true;
if (filter.kind && CodeActionKind.Source.contains(filter.kind) && rangeOrSelection.isEmpty()) {
rangeOrSelection = model.getFullModelRange();
}
const promises = getCodeActionProviders(model, filter).map(provider => {
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
if (!Array.isArray(providedCodeActions)) {
return [];
}
return providedCodeActions.filter(action => action && filtersAction(filter, action));
}, (err): CodeAction[] => {
if (isPromiseCanceledError(err)) {
throw err;
}
// Avoid calling providers that we know will not return code actions of interest
return provider.providedCodeActionKinds.some(providedKind => {
// Filter out actions by kind
// The provided kind can be either a subset of a superset of the filtered kind
if (trigger && trigger.filter && trigger.filter.kind && !(trigger.filter.kind.contains(providedKind) || new CodeActionKind(providedKind).contains(trigger.filter.kind.value))) {
return false;
}
// Don't return source actions unless they are explicitly requested
if (trigger && CodeActionKind.Source.contains(providedKind) && (!trigger.filter || !trigger.filter.includeSourceActions)) {
return false;
}
return true;
});
})
.map(support => {
return Promise.resolve(support.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
if (!Array.isArray(providedCodeActions)) {
return [];
}
return providedCodeActions.filter(action => isValidAction(trigger && trigger.filter, action));
}, (err): CodeAction[] => {
if (isPromiseCanceledError(err)) {
throw err;
}
onUnexpectedExternalError(err);
return [];
});
onUnexpectedExternalError(err);
return [];
});
});
return Promise.all(promises)
.then(flatten)
.then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator));
}
function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction): boolean {
return action && isValidActionKind(filter, action.kind);
}
function isValidActionKind(filter: CodeActionFilter | undefined, kind: string | undefined): boolean {
// Filter out actions by kind
if (filter && filter.kind && (!kind || !filter.kind.contains(kind))) {
return false;
}
// Don't return source actions unless they are explicitly requested
if (kind && CodeActionKind.Source.contains(kind) && (!filter || !filter.includeSourceActions)) {
return false;
}
return true;
function getCodeActionProviders(
model: ITextModel,
filter: CodeActionFilter
) {
return CodeActionProviderRegistry.all(model)
// Don't include providers that we know will not return code actions of interest
.filter(provider => {
if (!provider.providedCodeActionKinds) {
// We don't know what type of actions this provider will return.
return true;
}
return provider.providedCodeActionKinds.some(kind => mayIncludeActionsOfKind(filter, new CodeActionKind(kind)));
});
}
function codeActionsComparator(a: CodeAction, b: CodeAction): number {
@@ -107,5 +93,9 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args)
throw illegalArgument();
}
return getCodeActions(model, model.validateRange(range), { type: 'manual', filter: { includeSourceActions: true } });
return getCodeActions(
model,
model.validateRange(range),
{ type: 'manual', filter: { includeSourceActions: true } },
CancellationToken.None);
});

View File

@@ -21,7 +21,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { CodeActionModel, CodeActionsComputeEvent, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel';
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
import { CodeActionContextMenu } from './codeActionWidget';
import { LightBulbWidget } from './lightBulbWidget';
@@ -69,7 +69,7 @@ export class QuickFixController implements IEditorContribution {
this._disposables.push(
this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })),
this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
this._model.onDidChangeFixes(e => this._onCodeActionsEvent(e)),
this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e)),
this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
);
}
@@ -79,39 +79,39 @@ export class QuickFixController implements IEditorContribution {
dispose(this._disposables);
}
private _onCodeActionsEvent(e: CodeActionsComputeEvent): void {
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
if (this._activeRequest) {
this._activeRequest.cancel();
this._activeRequest = undefined;
}
if (e && e.actions) {
this._activeRequest = e.actions;
}
if (newState.type === CodeActionsState.Type.Triggered) {
this._activeRequest = newState.actions;
if (e && e.actions && e.trigger.filter && e.trigger.filter.kind) {
// Triggered for specific scope
// Apply if we only have one action or requested autoApply, otherwise show menu
e.actions.then(fixes => {
if (fixes.length > 0 && e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
this._onApplyCodeAction(fixes[0]);
} else {
this._codeActionContextMenu.show(e.actions, e.position);
}
}).catch(onUnexpectedError);
return;
}
if (newState.trigger.filter && newState.trigger.filter.kind) {
// Triggered for specific scope
newState.actions.then(fixes => {
if (fixes.length > 0) {
// Apply if we only have one action or requested autoApply
if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
this._onApplyCodeAction(fixes[0]);
return;
}
}
this._codeActionContextMenu.show(newState.actions, newState.position);
if (e && e.trigger.type === 'manual') {
this._codeActionContextMenu.show(e.actions, e.position);
} else if (e && e.actions) {
// auto magically triggered
// * update an existing list of code actions
// * manage light bulb
if (this._codeActionContextMenu.isVisible) {
this._codeActionContextMenu.show(e.actions, e.position);
}).catch(onUnexpectedError);
} else if (newState.trigger.type === 'manual') {
this._codeActionContextMenu.show(newState.actions, newState.position);
} else {
this._lightBulbWidget.model = e;
// auto magically triggered
// * update an existing list of code actions
// * manage light bulb
if (this._codeActionContextMenu.isVisible) {
this._codeActionContextMenu.show(newState.actions, newState.position);
} else {
this._lightBulbWidget.tryShow(newState);
}
}
} else {
this._lightBulbWidget.hide();
@@ -122,13 +122,11 @@ export class QuickFixController implements IEditorContribution {
return QuickFixController.ID;
}
private _handleLightBulbSelect(coords: { x: number, y: number }): void {
if (this._lightBulbWidget.model && this._lightBulbWidget.model.actions) {
this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords);
}
private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void {
this._codeActionContextMenu.show(e.state.actions, e);
}
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Thenable<CodeAction[] | undefined> {
public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise<CodeAction[] | undefined> {
return this._model.trigger({ type: 'manual', filter, autoApply });
}
@@ -158,7 +156,7 @@ export async function applyCodeAction(
await bulkEditService.apply(action.edit, { editor });
}
if (action.command) {
await commandService.executeCommand(action.command.id, ...action.command.arguments);
await commandService.executeCommand(action.command.id, ...(action.command.arguments || []));
}
}
@@ -168,6 +166,10 @@ function showCodeActionsForEditorSelection(
filter?: CodeActionFilter,
autoApply?: CodeActionAutoApply
) {
if (!editor.hasModel()) {
return;
}
const controller = QuickFixController.get(editor);
if (!controller) {
return;
@@ -206,38 +208,41 @@ export class QuickFixAction extends EditorAction {
class CodeActionCommandArgs {
public static fromUser(arg: any): CodeActionCommandArgs {
public static fromUser(arg: any, defaults: { kind: CodeActionKind, apply: CodeActionAutoApply }): CodeActionCommandArgs {
if (!arg || typeof arg !== 'object') {
return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
return new CodeActionCommandArgs(defaults.kind, defaults.apply, false);
}
return new CodeActionCommandArgs(
CodeActionCommandArgs.getKindFromUser(arg),
CodeActionCommandArgs.getApplyFromUser(arg));
CodeActionCommandArgs.getKindFromUser(arg, defaults.kind),
CodeActionCommandArgs.getApplyFromUser(arg, defaults.apply),
CodeActionCommandArgs.getPreferredUser(arg));
}
private static getApplyFromUser(arg: any) {
private static getApplyFromUser(arg: any, defaultAutoApply: CodeActionAutoApply) {
switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
case 'first':
return CodeActionAutoApply.First;
case 'never':
return CodeActionAutoApply.Never;
case 'ifsingle':
default:
return CodeActionAutoApply.IfSingle;
case 'first': return CodeActionAutoApply.First;
case 'never': return CodeActionAutoApply.Never;
case 'ifsingle': return CodeActionAutoApply.IfSingle;
default: return defaultAutoApply;
}
}
private static getKindFromUser(arg: any) {
private static getKindFromUser(arg: any, defaultKind: CodeActionKind) {
return typeof arg.kind === 'string'
? new CodeActionKind(arg.kind)
: CodeActionKind.Empty;
: defaultKind;
}
private static getPreferredUser(arg: any): boolean {
return typeof arg.preferred === 'boolean'
? arg.preferred
: false;
}
private constructor(
public readonly kind: CodeActionKind,
public readonly apply: CodeActionAutoApply
public readonly apply: CodeActionAutoApply,
public readonly preferred: boolean,
) { }
}
@@ -253,8 +258,17 @@ export class CodeActionCommand extends EditorCommand {
}
public runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
const args = CodeActionCommandArgs.fromUser(userArg);
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply);
const args = CodeActionCommandArgs.fromUser(userArg, {
kind: CodeActionKind.Empty,
apply: CodeActionAutoApply.IfSingle,
});
return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"),
{
kind: args.kind,
includeSourceActions: true,
onlyIncludePreferredActions: args.preferred,
},
args.apply);
}
}
@@ -287,11 +301,18 @@ export class RefactorAction extends EditorAction {
});
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
const args = CodeActionCommandArgs.fromUser(userArg, {
kind: CodeActionKind.Refactor,
apply: CodeActionAutoApply.Never
});
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
{ kind: CodeActionKind.Refactor },
CodeActionAutoApply.Never);
{
kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty,
onlyIncludePreferredActions: args.preferred,
},
args.apply);
}
}
@@ -316,11 +337,19 @@ export class SourceAction extends EditorAction {
});
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(_accessor: ServicesAccessor, editor: ICodeEditor, userArg: any): void {
const args = CodeActionCommandArgs.fromUser(userArg, {
kind: CodeActionKind.Source,
apply: CodeActionAutoApply.Never
});
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.source.noneMessage', "No source actions available"),
{ kind: CodeActionKind.Source, includeSourceActions: true },
CodeActionAutoApply.Never);
{
kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty,
includeSourceActions: true,
onlyIncludePreferredActions: args.preferred,
},
args.apply);
}
}
@@ -350,4 +379,38 @@ export class OrganizeImportsAction extends EditorAction {
{ kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true },
CodeActionAutoApply.IfSingle);
}
}
}
export class AutoFixAction extends EditorAction {
static readonly Id = 'editor.action.autoFix';
constructor() {
super({
id: AutoFixAction.Id,
label: nls.localize('autoFix.label', "Auto Fix..."),
alias: 'Auto Fix',
precondition: ContextKeyExpr.and(
EditorContextKeys.writable,
contextKeyForSupportedActions(CodeActionKind.QuickFix)),
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.US_DOT,
mac: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_DOT
},
weight: KeybindingWeight.EditorContrib
}
});
}
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
return showCodeActionsForEditorSelection(editor,
nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"),
{
kind: CodeActionKind.QuickFix,
onlyIncludePreferredActions: true
},
CodeActionAutoApply.IfSingle);
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction, AutoFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
registerEditorContribution(QuickFixController);
@@ -12,4 +12,5 @@ registerEditorAction(QuickFixAction);
registerEditorAction(RefactorAction);
registerEditorAction(SourceAction);
registerEditorAction(OrganizeImportsAction);
registerEditorAction(AutoFixAction);
registerEditorCommand(new CodeActionCommand());

View File

@@ -28,7 +28,7 @@ export class CodeActionOracle {
constructor(
private _editor: ICodeEditor,
private readonly _markerService: IMarkerService,
private _signalChange: (e: CodeActionsComputeEvent) => any,
private _signalChange: (newState: CodeActionsState.State) => void,
private readonly _delay: number = 250,
private readonly _progressService?: IProgressService,
) {
@@ -112,26 +112,16 @@ export class CodeActionOracle {
return selection ? selection : undefined;
}
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Thenable<CodeAction[] | undefined> {
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise<CodeAction[] | undefined> {
if (!selection) {
// cancel
this._signalChange({
trigger,
rangeOrSelection: undefined,
position: undefined,
actions: undefined,
});
this._signalChange(CodeActionsState.Empty);
return Promise.resolve(undefined);
} else {
const model = this._editor.getModel();
if (!model) {
// cancel
this._signalChange({
trigger,
rangeOrSelection: undefined,
position: undefined,
actions: undefined,
});
this._signalChange(CodeActionsState.Empty);
return Promise.resolve(undefined);
}
@@ -143,22 +133,38 @@ export class CodeActionOracle {
this._progressService.showWhile(actions, 250);
}
this._signalChange({
this._signalChange(new CodeActionsState.Triggered(
trigger,
rangeOrSelection: selection,
selection,
position,
actions
});
));
return actions;
}
}
}
export interface CodeActionsComputeEvent {
trigger: CodeActionTrigger;
rangeOrSelection: Range | Selection | undefined;
position: Position | undefined;
actions: CancelablePromise<CodeAction[]> | undefined;
export namespace CodeActionsState {
export const enum Type {
Empty,
Triggered,
}
export const Empty = new class { readonly type = Type.Empty; };
export class Triggered {
readonly type = Type.Triggered;
constructor(
public readonly trigger: CodeActionTrigger,
public readonly rangeOrSelection: Range | Selection,
public readonly position: Position,
public readonly actions: CancelablePromise<CodeAction[]>,
) { }
}
export type State = typeof Empty | Triggered;
}
export class CodeActionModel {
@@ -166,7 +172,8 @@ export class CodeActionModel {
private _editor: ICodeEditor;
private _markerService: IMarkerService;
private _codeActionOracle?: CodeActionOracle;
private _onDidChangeFixes = new Emitter<CodeActionsComputeEvent>();
private _state: CodeActionsState.State = CodeActionsState.Empty;
private _onDidChangeState = new Emitter<CodeActionsState.State>();
private _disposables: IDisposable[] = [];
private readonly _supportedCodeActions: IContextKey<string>;
@@ -178,7 +185,7 @@ export class CodeActionModel {
this._disposables.push(this._editor.onDidChangeModel(() => this._update()));
this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update()));
this._disposables.push(CodeActionProviderRegistry.onDidChange(this._update, this));
this._disposables.push(CodeActionProviderRegistry.onDidChange(() => this._update()));
this._update();
}
@@ -188,23 +195,26 @@ export class CodeActionModel {
dispose(this._codeActionOracle);
}
get onDidChangeFixes(): Event<CodeActionsComputeEvent> {
return this._onDidChangeFixes.event;
get onDidChangeState(): Event<CodeActionsState.State> {
return this._onDidChangeState.event;
}
private _update(): void {
if (this._codeActionOracle) {
this._codeActionOracle.dispose();
this._codeActionOracle = undefined;
this._onDidChangeFixes.fire(undefined);
}
if (this._state.type === CodeActionsState.Type.Triggered) {
// this._state.actions.cancel();
}
this.setState(CodeActionsState.Empty);
const model = this._editor.getModel();
if (model
&& CodeActionProviderRegistry.has(model)
&& !this._editor.getConfiguration().readOnly) {
&& !this._editor.getConfiguration().readOnly
) {
const supportedActions: string[] = [];
for (const provider of CodeActionProviderRegistry.all(model)) {
if (Array.isArray(provider.providedCodeActionKinds)) {
@@ -214,17 +224,25 @@ export class CodeActionModel {
this._supportedCodeActions.set(supportedActions.join(' '));
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, p => this._onDidChangeFixes.fire(p), undefined, this._progressService);
this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, newState => this.setState(newState), undefined, this._progressService);
this._codeActionOracle.trigger({ type: 'auto' });
} else {
this._supportedCodeActions.reset();
}
}
trigger(trigger: CodeActionTrigger): Thenable<CodeAction[] | undefined> {
public trigger(trigger: CodeActionTrigger): Promise<CodeAction[] | undefined> {
if (this._codeActionOracle) {
return this._codeActionOracle.trigger(trigger);
}
return Promise.resolve(undefined);
}
private setState(newState: CodeActionsState.State) {
if (newState === this._state) {
return;
}
this._state = newState;
this._onDidChangeState.fire(newState);
}
}

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { startsWith } from 'vs/base/common/strings';
import { CodeAction } from 'vs/editor/common/modes';
export class CodeActionKind {
private static readonly sep = '.';
@@ -13,25 +14,72 @@ export class CodeActionKind {
public static readonly Refactor = new CodeActionKind('refactor');
public static readonly Source = new CodeActionKind('source');
public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports');
public static readonly SourceFixAll = new CodeActionKind('source.fixAll');
constructor(
public readonly value: string
) { }
public contains(other: string): boolean {
return this.value === other || startsWith(other, this.value + CodeActionKind.sep);
public contains(other: CodeActionKind): boolean {
return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep);
}
public intersects(other: CodeActionKind): boolean {
return this.contains(other) || other.contains(this);
}
}
export const enum CodeActionAutoApply {
IfSingle = 1,
First = 2,
Never = 3
IfSingle,
First,
Never,
}
export interface CodeActionFilter {
readonly kind?: CodeActionKind;
readonly includeSourceActions?: boolean;
readonly onlyIncludePreferredActions?: boolean;
}
export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: CodeActionKind): boolean {
// A provided kind may be a subset or superset of our filtered kind.
if (filter.kind && !filter.kind.intersects(providedKind)) {
return false;
}
// Don't return source actions unless they are explicitly requested
if (CodeActionKind.Source.contains(providedKind) && !filter.includeSourceActions) {
return false;
}
return true;
}
export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean {
const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined;
// Filter out actions by kind
if (filter.kind) {
if (!actionKind || !filter.kind.contains(actionKind)) {
return false;
}
}
// Don't return source actions unless they are explicitly requested
if (!filter.includeSourceActions) {
if (actionKind && CodeActionKind.Source.contains(actionKind)) {
return false;
}
}
if (filter.onlyIncludePreferredActions) {
if (!action.isPreferred) {
return false;
}
}
return true;
}
export interface CodeActionTrigger {

View File

@@ -5,7 +5,6 @@
import { getDomNodePagePosition } from 'vs/base/browser/dom';
import { Action } from 'vs/base/common/actions';
import { always } from 'vs/base/common/async';
import { canceled } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -27,48 +26,45 @@ export class CodeActionContextMenu {
private readonly _onApplyCodeAction: (action: CodeAction) => Promise<any>
) { }
show(fixes: Thenable<CodeAction[]>, at: { x: number; y: number } | Position) {
const actionsPromise = fixes ? fixes.then(value => {
return value.map(action => {
return new Action(action.command ? action.command.id : action.title, action.title, undefined, true, () => {
return always(
this._onApplyCodeAction(action),
() => this._onDidExecuteCodeAction.fire(undefined));
});
});
}).then(actions => {
if (!this._editor.getDomNode()) {
// cancel when editor went off-dom
return Promise.reject(canceled());
}
return actions;
}) : Promise.resolve([] as Action[]);
actionsPromise.then(actions => {
this._contextMenuService.showContextMenu({
getAnchor: () => {
if (Position.isIPosition(at)) {
at = this._toCoords(at);
}
return at;
},
getActions: () => actions,
onHide: () => {
this._visible = false;
this._editor.focus();
},
autoSelectFirstItem: true
});
async show(actionsToShow: Promise<CodeAction[]>, at?: { x: number; y: number } | Position): Promise<void> {
const codeActions = await actionsToShow;
if (!this._editor.getDomNode()) {
// cancel when editor went off-dom
return Promise.reject(canceled());
}
const actions = codeActions.map(action => this.codeActionToAction(action));
this._contextMenuService.showContextMenu({
getAnchor: () => {
if (Position.isIPosition(at)) {
at = this._toCoords(at);
}
return at || { x: 0, y: 0 };
},
getActions: () => actions,
onHide: () => {
this._visible = false;
this._editor.focus();
},
autoSelectFirstItem: true
});
}
private codeActionToAction(action: CodeAction): Action {
const id = action.command ? action.command.id : action.title;
const title = action.isPreferred ? `${action.title}` : action.title;
return new Action(id, title, undefined, true, () =>
this._onApplyCodeAction(action)
.finally(() => this._onDidExecuteCodeAction.fire(undefined)));
}
get isVisible(): boolean {
return this._visible;
}
private _toCoords(position: Position): { x: number, y: number } {
if (!this._editor.hasModel()) {
return { x: 0, y: 0 };
}
this._editor.revealPosition(position, ScrollType.Immediate);
this._editor.render();

View File

@@ -6,45 +6,49 @@
import * as dom from 'vs/base/browser/dom';
import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import 'vs/css!./lightBulbWidget';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { TextModel } from 'vs/editor/common/model/textModel';
import { CodeActionsComputeEvent } from './codeActionModel';
import { CodeActionsState } from './codeActionModel';
export class LightBulbWidget implements IDisposable, IContentWidget {
export class LightBulbWidget extends Disposable implements IContentWidget {
private static readonly _posPref = [ContentWidgetPositionPreference.EXACT];
private readonly _domNode: HTMLDivElement;
private readonly _editor: ICodeEditor;
private readonly _disposables: IDisposable[] = [];
private readonly _onClick = new Emitter<{ x: number, y: number }>();
readonly onClick: Event<{ x: number, y: number }> = this._onClick.event;
private readonly _onClick = this._register(new Emitter<{ x: number; y: number; state: CodeActionsState.Triggered }>());
public readonly onClick = this._onClick.event;
private _position: IContentWidgetPosition | null;
private _model: CodeActionsComputeEvent | null;
private _state: CodeActionsState.State = CodeActionsState.Empty;
private _futureFixes = new CancellationTokenSource();
constructor(editor: ICodeEditor) {
super();
this._domNode = document.createElement('div');
this._domNode.className = 'lightbulb-glyph';
this._editor = editor;
this._editor.addContentWidget(this);
this._disposables.push(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
this._disposables.push(this._editor.onDidChangeModelContent(_ => {
this._register(this._editor.onDidChangeModel(_ => this._futureFixes.cancel()));
this._register(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
this._register(this._editor.onDidChangeModelContent(_ => {
// cancel when the line in question has been removed
const editorModel = this._editor.getModel();
if (!this.model || !this.model.position || !editorModel || this.model.position.lineNumber >= editorModel.getLineCount()) {
if (this._state.type !== CodeActionsState.Type.Triggered || !editorModel || this._state.position.lineNumber >= editorModel.getLineCount()) {
this._futureFixes.cancel();
}
}));
this._disposables.push(dom.addStandardDisposableListener(this._domNode, 'click', e => {
this._register(dom.addStandardDisposableListener(this._domNode, 'click', e => {
if (this._state.type !== CodeActionsState.Type.Triggered) {
return;
}
// Make sure that focus / cursor location is not lost when clicking widget icon
this._editor.focus();
// a bit of extra work to make sure the menu
@@ -53,16 +57,17 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
const { lineHeight } = this._editor.getConfiguration();
let pad = Math.floor(lineHeight / 3);
if (this._position && this._model && this._model.position && this._position.position !== null && this._position.position.lineNumber < this._model.position.lineNumber) {
if (this._position && this._position.position !== null && this._position.position.lineNumber < this._state.position.lineNumber) {
pad += lineHeight;
}
this._onClick.fire({
x: e.posx,
y: top + height + pad
y: top + height + pad,
state: this._state
});
}));
this._disposables.push(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => {
if ((e.buttons & 1) !== 1) {
return;
}
@@ -75,7 +80,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
monitor.dispose();
});
}));
this._disposables.push(this._editor.onDidChangeConfiguration(e => {
this._register(this._editor.onDidChangeConfiguration(e => {
// hide when told to do so
if (e.contribInfo && !this._editor.getConfiguration().contribInfo.lightbulbEnabled) {
this.hide();
@@ -84,7 +89,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
}
dispose(): void {
dispose(this._disposables);
super.dispose();
this._editor.removeContentWidget(this);
}
@@ -100,9 +105,9 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
return this._position;
}
set model(value: CodeActionsComputeEvent | null) {
tryShow(newState: CodeActionsState.State) {
if (!value || this._position && (!value.position || this._position.position && this._position.position.lineNumber !== value.position.lineNumber)) {
if (newState.type !== CodeActionsState.Type.Triggered || this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) {
// hide when getting a 'hide'-request or when currently
// showing on another line
this.hide();
@@ -113,14 +118,14 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
this._futureFixes = new CancellationTokenSource();
const { token } = this._futureFixes;
this._model = value;
this._state = newState;
if (!this._model || !this._model.actions) {
if (this._state.type === CodeActionsState.Empty.type) {
return;
}
const selection = this._model.rangeOrSelection;
this._model.actions.then(fixes => {
const selection = this._state.rangeOrSelection;
this._state.actions.then(fixes => {
if (!token.isCancellationRequested && fixes && fixes.length > 0 && selection) {
this._show();
} else {
@@ -131,10 +136,6 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
});
}
get model(): CodeActionsComputeEvent | null {
return this._model;
}
set title(value: string) {
this._domNode.title = value;
}
@@ -148,10 +149,10 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
if (!config.contribInfo.lightbulbEnabled) {
return;
}
if (!this._model || !this._model.position) {
if (this._state.type !== CodeActionsState.Type.Triggered) {
return;
}
const { lineNumber, column } = this._model.position;
const { lineNumber, column } = this._state.position;
const model = this._editor.getModel();
if (!model) {
return;
@@ -188,7 +189,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
hide(): void {
this._position = null;
this._model = null;
this._state = CodeActionsState.Empty;
this._futureFixes.cancel();
this._editor.layoutContentWidget(this);
}

View File

@@ -11,6 +11,8 @@ import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRe
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModel } from 'vs/editor/common/model';
suite('CodeAction', () => {
@@ -115,7 +117,7 @@ suite('CodeAction', () => {
testData.tsLint.abc
];
const actions = await getCodeActions(model, new Range(1, 1, 2, 1));
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None);
assert.equal(actions.length, 6);
assert.deepEqual(actions, expected);
});
@@ -134,20 +136,20 @@ suite('CodeAction', () => {
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
{
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
assert.equal(actions.length, 2);
assert.strictEqual(actions[0].title, 'a');
assert.strictEqual(actions[1].title, 'a.b');
}
{
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None);
assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a.b');
}
{
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None);
assert.equal(actions.length, 0);
}
});
@@ -156,14 +158,14 @@ suite('CodeAction', () => {
const provider = new class implements CodeActionProvider {
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] {
return [
{ title: context.only, kind: context.only }
{ title: context.only || '', kind: context.only }
];
}
};
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a');
});
@@ -181,13 +183,13 @@ suite('CodeAction', () => {
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
{
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'b');
}
{
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } });
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None);
assert.equal(actions.length, 1);
assert.strictEqual(actions[0].title, 'a');
}
@@ -211,8 +213,53 @@ suite('CodeAction', () => {
filter: {
kind: CodeActionKind.QuickFix
}
});
}, CancellationToken.None);
assert.strictEqual(actions.length, 0);
assert.strictEqual(wasInvoked, false);
});
test('getCodeActions requests for source actions should expand source actions range to entire document #53525', async function () {
const provider = new class implements CodeActionProvider {
provideCodeActions(model: ITextModel, range: Range): CodeAction[] {
return [{
title: rangeToString(range),
kind: CodeActionKind.Source.value,
}];
}
};
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
{
const actions = await getCodeActions(model, new Range(1, 1, 1, 1), {
type: 'manual',
filter: {
kind: CodeActionKind.Source,
includeSourceActions: true,
}
}, CancellationToken.None);
assert.strictEqual(actions.length, 1);
assert.strictEqual(actions[0].title, rangeToString(model.getFullModelRange()));
}
{
const range = new Range(1, 1, 1, 2);
// But we should not expand for non-empty selections
const actions = await getCodeActions(model, range, {
type: 'manual',
filter: {
kind: CodeActionKind.Source,
includeSourceActions: true,
}
}, CancellationToken.None);
assert.strictEqual(actions.length, 1);
assert.strictEqual(actions[0].title, rangeToString(range));
}
});
});
function rangeToString(range: Range): string {
return `${range.startLineNumber},${range.startColumn} ${range.endLineNumber},${range.endColumn} `;
}

View File

@@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Selection } from 'vs/editor/common/core/selection';
import { TextModel } from 'vs/editor/common/model/textModel';
import { CodeActionProviderRegistry, LanguageIdentifier } from 'vs/editor/common/modes';
import { CodeActionOracle } from 'vs/editor/contrib/codeAction/codeActionModel';
import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { MarkerService } from 'vs/platform/markers/common/markerService';
@@ -47,11 +47,11 @@ suite('CodeAction', () => {
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
disposables.push(reg);
const oracle = new CodeActionOracle(editor, markerService, e => {
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
assert.equal(e.trigger.type, 'auto');
assert.ok(e.actions);
e.actions.then(fixes => {
e.actions!.then(fixes => {
oracle.dispose();
assert.equal(fixes.length, 1);
done();
@@ -85,10 +85,10 @@ suite('CodeAction', () => {
return new Promise((resolve, reject) => {
const oracle = new CodeActionOracle(editor, markerService, e => {
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
assert.equal(e.trigger.type, 'auto');
assert.ok(e.actions);
e.actions.then(fixes => {
e.actions!.then(fixes => {
oracle.dispose();
assert.equal(fixes.length, 1);
resolve(undefined);
@@ -107,7 +107,7 @@ suite('CodeAction', () => {
});
disposables.push(reg);
editor.getModel().setValue('// @ts-check\n2\ncon\n');
editor.getModel()!.setValue('// @ts-check\n2\ncon\n');
markerService.changeOne('fake', uri, [{
startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4,
@@ -120,7 +120,7 @@ suite('CodeAction', () => {
// case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker
await new Promise(resolve => {
let oracle = new CodeActionOracle(editor, markerService, e => {
let oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
assert.equal(e.trigger.type, 'auto');
const selection = <Selection>e.rangeOrSelection;
assert.deepEqual(selection.selectionStartLineNumber, 1);
@@ -130,7 +130,7 @@ suite('CodeAction', () => {
assert.deepEqual(e.position, { lineNumber: 3, column: 1 });
oracle.dispose();
resolve(null);
resolve(undefined);
}, 5);
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
@@ -142,7 +142,7 @@ suite('CodeAction', () => {
disposables.push(reg);
let triggerCount = 0;
const oracle = new CodeActionOracle(editor, markerService, e => {
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
assert.equal(e.trigger.type, 'auto');
++triggerCount;

View File

@@ -68,7 +68,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) {
const result: ICodeLensSymbol[] = [];
return getCodeLensData(model, CancellationToken.None).then(value => {
let resolve: Thenable<any>[] = [];
let resolve: Promise<any>[] = [];
for (const item of value) {
if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) {

View File

@@ -27,9 +27,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
private _globalToDispose: IDisposable[];
private _localToDispose: IDisposable[];
private _lenses: CodeLens[];
private _currentFindCodeLensSymbolsPromise: CancelablePromise<ICodeLensData[]>;
private _currentFindCodeLensSymbolsPromise: CancelablePromise<ICodeLensData[]> | null;
private _modelChangeCounter: number;
private _currentResolveCodeLensSymbolsPromise: CancelablePromise<any>;
private _currentResolveCodeLensSymbolsPromise: CancelablePromise<any> | null;
private _detectVisibleLenses: RunOnceScheduler;
constructor(
@@ -176,7 +176,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
scrollState.restore(this._editor);
} else {
// No accessors available
this._disposeAllLenses(null, null);
this._disposeAllLenses(undefined, undefined);
}
}));
this._localToDispose.push(this._editor.onDidChangeConfiguration(e => {
@@ -187,11 +187,11 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
}
}));
this._localToDispose.push(this._editor.onMouseUp(e => {
if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element.tagName === 'A') {
if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') {
for (const lens of this._lenses) {
let command = lens.getCommand(e.target.element as HTMLLinkElement);
if (command) {
this._commandService.executeCommand(command.id, ...command.arguments).catch(err => this._notificationService.error(err));
this._commandService.executeCommand(command.id, ...(command.arguments || [])).catch(err => this._notificationService.error(err));
break;
}
}
@@ -200,7 +200,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
scheduler.schedule();
}
private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void {
let helper = new CodeLensHelper();
this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor));
if (decChangeAccessor) {
@@ -210,13 +210,13 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
}
private _renderCodeLensSymbols(symbols: ICodeLensData[]): void {
if (!this._editor.getModel()) {
if (!this._editor.hasModel()) {
return;
}
let maxLineNumber = this._editor.getModel().getLineCount();
let groups: ICodeLensData[][] = [];
let lastGroup: ICodeLensData[];
let lastGroup: ICodeLensData[] | undefined;
for (let symbol of symbols) {
let line = symbol.symbol.range.startLineNumber;
@@ -307,7 +307,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
const promises = toResolve.map((request, i) => {
const resolvedSymbols = new Array<ICodeLensSymbol>(request.length);
const resolvedSymbols = new Array<ICodeLensSymbol | undefined | null>(request.length);
const promises = request.map((request, i) => {
if (typeof request.provider.resolveCodeLens === 'function') {
return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => {
@@ -315,7 +315,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution {
});
}
resolvedSymbols[i] = request.symbol;
return Promise.resolve(void 0);
return Promise.resolve(undefined);
});
return Promise.all(promises).then(() => {

View File

@@ -6,7 +6,7 @@
import 'vs/css!./codelensWidget';
import * as dom from 'vs/base/browser/dom';
import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays';
import { escape, format } from 'vs/base/common/strings';
import { escape } from 'vs/base/common/strings';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
@@ -83,8 +83,8 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
const { fontInfo, lineHeight } = this._editor.getConfiguration();
this._domNode.style.height = `${Math.round(lineHeight * 1.1)}px`;
this._domNode.style.lineHeight = `${lineHeight}px`;
this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * .9)}px`;
this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * .45)}px`;
this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`;
this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`;
this._domNode.innerHTML = '&nbsp;';
}
@@ -95,9 +95,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
}
}
withCommands(symbols: ICodeLensSymbol[]): void {
withCommands(inSymbols: Array<ICodeLensSymbol | undefined | null>): void {
this._commands = Object.create(null);
symbols = coalesce(symbols);
const symbols = coalesce(inSymbols);
if (isFalsyOrEmpty(symbols)) {
this._domNode.innerHTML = 'no commands';
return;
@@ -105,16 +105,18 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
let html: string[] = [];
for (let i = 0; i < symbols.length; i++) {
let command = symbols[i].command;
let title = escape(command.title);
let part: string;
if (command.id) {
part = format('<a id={0}>{1}</a>', i, title);
this._commands[i] = command;
} else {
part = format('<span>{0}</span>', title);
const command = symbols[i].command;
if (command) {
const title = escape(command.title);
let part: string;
if (command.id) {
part = `<a id=${i}>${title}</a>`;
this._commands[i] = command;
} else {
part = `<span>${title}</span>`;
}
html.push(part);
}
html.push(part);
}
this._domNode.innerHTML = html.join('<span>&nbsp;|&nbsp;</span>');
@@ -136,6 +138,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
}
setSymbolRange(range: Range): void {
if (!this._editor.hasModel()) {
return;
}
const lineNumber = range.startLineNumber;
const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(lineNumber);
this._widgetPosition = {
@@ -206,7 +211,7 @@ export class CodeLens {
this._data = data;
this._decorationIds = new Array<string>(this._data.length);
let range: Range;
let range: Range | undefined;
this._data.forEach((codeLensData, i) => {
helper.addDecoration({
@@ -222,16 +227,18 @@ export class CodeLens {
}
});
this._contentWidget = new CodeLensContentWidget(editor, range);
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
if (range) {
this._contentWidget = new CodeLensContentWidget(editor, range);
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
this._editor.addContentWidget(this._contentWidget);
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
this._editor.addContentWidget(this._contentWidget);
}
}
dispose(helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void {
while (this._decorationIds.length) {
helper.removeDecoration(this._decorationIds.pop());
helper.removeDecoration(this._decorationIds.pop()!);
}
if (viewZoneChangeAccessor) {
viewZoneChangeAccessor.removeZone(this._viewZoneId);
@@ -240,16 +247,20 @@ export class CodeLens {
}
isValid(): boolean {
if (!this._editor.hasModel()) {
return false;
}
const model = this._editor.getModel();
return this._decorationIds.some((id, i) => {
const range = this._editor.getModel().getDecorationRange(id);
const range = model.getDecorationRange(id);
const symbol = this._data[i].symbol;
return range && Range.isEmpty(symbol.range) === range.isEmpty();
return !!(range && Range.isEmpty(symbol.range) === range.isEmpty());
});
}
updateCodeLensSymbols(data: ICodeLensData[], helper: CodeLensHelper): void {
while (this._decorationIds.length) {
helper.removeDecoration(this._decorationIds.pop());
helper.removeDecoration(this._decorationIds.pop()!);
}
this._data = data;
this._decorationIds = new Array<string>(this._data.length);
@@ -261,7 +272,7 @@ export class CodeLens {
});
}
computeIfNecessary(model: ITextModel): ICodeLensData[] {
computeIfNecessary(model: ITextModel): ICodeLensData[] | null {
this._contentWidget.updateVisibility(); // trigger the fade in
if (!this._contentWidget.isVisible()) {
return null;
@@ -269,12 +280,15 @@ export class CodeLens {
// Read editor current state
for (let i = 0; i < this._decorationIds.length; i++) {
this._data[i].symbol.range = model.getDecorationRange(this._decorationIds[i]);
const range = model.getDecorationRange(this._decorationIds[i]);
if (range) {
this._data[i].symbol.range = range;
}
}
return this._data;
}
updateCommands(symbols: ICodeLensSymbol[]): void {
updateCommands(symbols: Array<ICodeLensSymbol | undefined | null>): void {
this._contentWidget.withCommands(symbols);
}
@@ -287,22 +301,25 @@ export class CodeLens {
}
getLineNumber(): number {
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
if (range) {
return range.startLineNumber;
if (this._editor.hasModel()) {
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
if (range) {
return range.startLineNumber;
}
}
return -1;
}
update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void {
if (this.isValid()) {
if (this.isValid() && this._editor.hasModel()) {
const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
if (range) {
this._viewZone.afterLineNumber = range.startLineNumber - 1;
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
this._viewZone.afterLineNumber = range.startLineNumber - 1;
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
this._contentWidget.setSymbolRange(range);
this._editor.layoutContentWidget(this._contentWidget);
this._contentWidget.setSymbolRange(range);
this._editor.layoutContentWidget(this._contentWidget);
}
}
}
}

View File

@@ -242,7 +242,7 @@ export class ColorDetector implements IEditorContribution {
return null;
}
return this._colorDatas.get(decorations[0].id);
return this._colorDatas.get(decorations[0].id)!;
}
}

View File

@@ -113,8 +113,8 @@ export class BlockCommentCommand implements editorCommon.ICommand {
this._usedEndToken = ops.length === 1 ? endToken : null;
}
for (let i = 0; i < ops.length; i++) {
builder.addTrackedEditOperation(ops[i].range, ops[i].text);
for (const op of ops) {
builder.addTrackedEditOperation(op.range, op.text);
}
}

View File

@@ -33,8 +33,8 @@ abstract class CommentLineAction extends EditorAction {
let selections = editor.getSelections();
let opts = model.getOptions();
for (let i = 0; i < selections.length; i++) {
commands.push(new LineCommentCommand(selections[i], opts.tabSize, this._type));
for (const selection of selections) {
commands.push(new LineCommentCommand(selection, opts.tabSize, this._type));
}
editor.pushUndoStop();
@@ -56,12 +56,13 @@ class ToggleCommentLineAction extends CommentLineAction {
primary: KeyMod.CtrlCmd | KeyCode.US_SLASH,
weight: KeybindingWeight.EditorContrib
},
menubarOpts: {
menuId: MenuId.MenubarEditMenu,
group: '5_insert',
title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"),
order: 1
}
// {{SQL CARBON EDIT}} - Remove from menu
// menubarOpts: {
// menuId: MenuId.MenubarEditMenu,
// group: '5_insert',
// title: nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"),
// order: 1
// }
});
}
}
@@ -112,12 +113,13 @@ class BlockCommentAction extends EditorAction {
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A },
weight: KeybindingWeight.EditorContrib
},
menubarOpts: {
menuId: MenuId.MenubarEditMenu,
group: '5_insert',
title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"),
order: 2
}
// {{SQL CARBON EDIT}} - Remove from menu
// menubarOpts: {
// menuId: MenuId.MenubarEditMenu,
// group: '5_insert',
// title: nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"),
// order: 2
// }
});
}
@@ -128,8 +130,8 @@ class BlockCommentAction extends EditorAction {
let commands: ICommand[] = [];
let selections = editor.getSelections();
for (let i = 0; i < selections.length; i++) {
commands.push(new BlockCommentCommand(selections[i]));
for (const selection of selections) {
commands.push(new BlockCommentCommand(selection));
}
editor.pushUndoStop();

View File

@@ -299,8 +299,8 @@ export class LineCommentCommand implements editorCommon.ICommand {
}
}
this._selectionId = builder.trackSelection(s);
for (let i = 0; i < ops.length; i++) {
builder.addEditOperation(ops[i].range, ops[i].text);
for (const op of ops) {
builder.addEditOperation(op.range, op.text);
}
}

View File

@@ -950,7 +950,9 @@ suite('Editor Contrib - Line Comment in mixed modes', () => {
this._register(modes.TokenizationRegistry.register(this.getLanguageIdentifier().language, {
getInitialState: (): modes.IState => NULL_STATE,
tokenize: undefined,
tokenize: () => {
throw new Error('not implemented');
},
tokenize2: (line: string, state: modes.IState): TokenizationResult2 => {
let languageId = (/^ /.test(line) ? INNER_LANGUAGE_ID : OUTER_LANGUAGE_ID);

View File

@@ -20,6 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ITextModel } from 'vs/editor/common/model';
export class ContextMenuController implements IEditorContribution {
@@ -45,7 +46,7 @@ export class ContextMenuController implements IEditorContribution {
this._toDispose.push(this._editor.onContextMenu((e: IEditorMouseEvent) => this._onContextMenu(e)));
this._toDispose.push(this._editor.onDidScrollChange((e: IScrollEvent) => {
if (this._contextMenuIsBeingShownCount > 0) {
if (this._contextMenuIsBeingShownCount > 0 && e.scrollTopChanged) {
this._contextViewService.hideContextView();
}
}));
@@ -60,6 +61,10 @@ export class ContextMenuController implements IEditorContribution {
}
private _onContextMenu(e: IEditorMouseEvent): void {
if (!this._editor.hasModel()) {
return;
}
if (!this._editor.getConfiguration().contribInfo.contextmenu) {
this._editor.focus();
// Ensure the cursor is at the position of the mouse click
@@ -88,7 +93,7 @@ export class ContextMenuController implements IEditorContribution {
}
// Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
let anchor: IAnchor;
let anchor: IAnchor | null = null;
if (e.target.type !== MouseTargetType.TEXTAREA) {
anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 };
}
@@ -97,10 +102,13 @@ export class ContextMenuController implements IEditorContribution {
this.showContextMenu(anchor);
}
public showContextMenu(anchor?: IAnchor): void {
public showContextMenu(anchor?: IAnchor | null): void {
if (!this._editor.getConfiguration().contribInfo.contextmenu) {
return; // Context menu is turned off through configuration
}
if (!this._editor.hasModel()) {
return;
}
if (!this._contextMenuService) {
this._editor.focus();
@@ -108,7 +116,7 @@ export class ContextMenuController implements IEditorContribution {
}
// Find actions available for menu
const menuActions = this._getMenuActions();
const menuActions = this._getMenuActions(this._editor.getModel());
// Show menu if we have actions to show
if (menuActions.length > 0) {
@@ -116,11 +124,11 @@ export class ContextMenuController implements IEditorContribution {
}
}
private _getMenuActions(): IAction[] {
private _getMenuActions(model: ITextModel): IAction[] {
const result: IAction[] = [];
let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService);
const groups = contextMenu.getActions({ arg: this._editor.getModel().uri });
const groups = contextMenu.getActions({ arg: model.uri });
contextMenu.dispose();
for (let group of groups) {
@@ -133,6 +141,9 @@ export class ContextMenuController implements IEditorContribution {
}
private _doShowContextMenu(actions: IAction[], anchor: IAnchor | null = null): void {
if (!this._editor.hasModel()) {
return;
}
// Disable hover
const oldHoverSetting = this._editor.getConfiguration().contribInfo.hover;
@@ -160,7 +171,7 @@ export class ContextMenuController implements IEditorContribution {
// Show menu
this._contextMenuIsBeingShownCount++;
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getAnchor: () => anchor!,
getActions: () => actions,
@@ -178,8 +189,8 @@ export class ContextMenuController implements IEditorContribution {
return new ActionItem(action, action, { icon: true, label: true, isMenu: true });
},
getKeyBinding: (action): ResolvedKeybinding => {
return this._keybindingFor(action);
getKeyBinding: (action): ResolvedKeybinding | undefined => {
return this._keybindingFor(action) || undefined;
},
onHide: (wasCancelled: boolean) => {
@@ -192,7 +203,7 @@ export class ContextMenuController implements IEditorContribution {
});
}
private _keybindingFor(action: IAction): ResolvedKeybinding {
private _keybindingFor(action: IAction): ResolvedKeybinding | undefined {
return this._keybindingService.lookupKeybinding(action.id);
}

View File

@@ -19,10 +19,6 @@
padding-right: 3px;
}
/* .monaco-tree.no-icons .outline-element .outline-element-icon {
display: none;
} */
.monaco-tree .outline-element .outline-element-label {
text-overflow: ellipsis;
overflow: hidden;

View File

@@ -20,12 +20,14 @@ export abstract class TreeElement {
abstract id: string;
abstract children: { [id: string]: TreeElement };
abstract parent: TreeElement;
abstract parent: TreeElement | undefined;
abstract adopt(newParent: TreeElement): TreeElement;
remove(): void {
delete this.parent.children[this.id];
if (this.parent) {
delete this.parent.children[this.id];
}
}
static findId(candidate: DocumentSymbol | string, container: TreeElement): string {
@@ -36,20 +38,20 @@ export abstract class TreeElement {
candidateId = `${container.id}/${candidate}`;
} else {
candidateId = `${container.id}/${candidate.name}`;
if (container.children[candidateId] !== void 0) {
if (container.children[candidateId] !== undefined) {
candidateId = `${container.id}/${candidate.name}_${candidate.range.startLineNumber}_${candidate.range.startColumn}`;
}
}
let id = candidateId;
for (let i = 0; container.children[id] !== void 0; i++) {
for (let i = 0; container.children[id] !== undefined; i++) {
id = `${candidateId}_${i}`;
}
return id;
}
static getElementById(id: string, element: TreeElement): TreeElement {
static getElementById(id: string, element: TreeElement): TreeElement | undefined {
if (!id) {
return undefined;
}
@@ -88,18 +90,18 @@ export abstract class TreeElement {
export class OutlineElement extends TreeElement {
children: { [id: string]: OutlineElement; } = Object.create(null);
score: FuzzyScore = [0, []];
marker: { count: number, topSev: MarkerSeverity };
score: FuzzyScore | undefined = FuzzyScore.Default;
marker: { count: number, topSev: MarkerSeverity } | undefined;
constructor(
readonly id: string,
public parent: OutlineModel | OutlineGroup | OutlineElement,
public parent: TreeElement | undefined,
readonly symbol: DocumentSymbol
) {
super();
}
adopt(parent: OutlineModel | OutlineGroup | OutlineElement): OutlineElement {
adopt(parent: TreeElement): OutlineElement {
let res = new OutlineElement(this.id, parent, this.symbol);
forEach(this.children, entry => res.children[entry.key] = entry.value.adopt(res));
return res;
@@ -112,33 +114,33 @@ export class OutlineGroup extends TreeElement {
constructor(
readonly id: string,
public parent: OutlineModel,
public parent: TreeElement | undefined,
readonly provider: DocumentSymbolProvider,
readonly providerIndex: number,
) {
super();
}
adopt(parent: OutlineModel): OutlineGroup {
adopt(parent: TreeElement): OutlineGroup {
let res = new OutlineGroup(this.id, parent, this.provider, this.providerIndex);
forEach(this.children, entry => res.children[entry.key] = entry.value.adopt(res));
return res;
}
updateMatches(pattern: string, topMatch: OutlineElement): OutlineElement {
updateMatches(pattern: string, topMatch: OutlineElement | undefined): OutlineElement | undefined {
for (const key in this.children) {
topMatch = this._updateMatches(pattern, this.children[key], topMatch);
}
return topMatch;
}
private _updateMatches(pattern: string, item: OutlineElement, topMatch: OutlineElement): OutlineElement {
private _updateMatches(pattern: string, item: OutlineElement, topMatch: OutlineElement | undefined): OutlineElement | undefined {
item.score = pattern
? fuzzyScore(pattern, pattern.toLowerCase(), 0, item.symbol.name, item.symbol.name.toLowerCase(), 0, true)
: [-100, []];
: FuzzyScore.Default;
if (item.score && (!topMatch || item.score[0] > topMatch.score[0])) {
if (item.score && (!topMatch || !topMatch.score || item.score[0] > topMatch.score[0])) {
topMatch = item;
}
for (const key in item.children) {
@@ -146,17 +148,17 @@ export class OutlineGroup extends TreeElement {
topMatch = this._updateMatches(pattern, child, topMatch);
if (!item.score && child.score) {
// don't filter parents with unfiltered children
item.score = [-100, []];
item.score = FuzzyScore.Default;
}
}
return topMatch;
}
getItemEnclosingPosition(position: IPosition): OutlineElement {
getItemEnclosingPosition(position: IPosition): OutlineElement | undefined {
return position ? this._getItemEnclosingPosition(position, this.children) : undefined;
}
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement {
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement | undefined {
for (let key in children) {
let item = children[key];
if (!item.symbol.range || !Range.containsPosition(item.symbol.range, position)) {
@@ -174,7 +176,6 @@ export class OutlineGroup extends TreeElement {
}
private _updateMarker(markers: IMarker[], item: OutlineElement): void {
item.marker = undefined;
// find the proper start index to check for item/marker overlap.
@@ -190,14 +191,14 @@ export class OutlineGroup extends TreeElement {
}
let myMarkers: IMarker[] = [];
let myTopSev: MarkerSeverity;
let myTopSev: MarkerSeverity | undefined;
for (; start < markers.length && Range.areIntersecting(item.symbol.range, markers[start]); start++) {
// remove markers intersecting with this outline element
// and store them in a 'private' array.
let marker = markers[start];
myMarkers.push(marker);
markers[start] = undefined;
(markers as Array<IMarker | undefined>)[start] = undefined;
if (!myTopSev || marker.severity > myTopSev) {
myTopSev = marker.severity;
}
@@ -224,7 +225,7 @@ export class OutlineGroup extends TreeElement {
export class OutlineModel extends TreeElement {
private static readonly _requests = new LRUCache<string, { promiseCnt: number, source: CancellationTokenSource, promise: Promise<any>, model: OutlineModel }>(9, .75);
private static readonly _requests = new LRUCache<string, { promiseCnt: number, source: CancellationTokenSource, promise: Promise<any>, model: OutlineModel | undefined }>(9, 0.75);
private static readonly _keys = new class {
private _counter = 1;
@@ -265,25 +266,25 @@ export class OutlineModel extends TreeElement {
OutlineModel._requests.set(key, data);
}
if (data.model) {
if (data!.model) {
// resolved -> return data
return Promise.resolve(data.model);
}
// increase usage counter
data.promiseCnt += 1;
data!.promiseCnt += 1;
token.onCancellationRequested(() => {
// last -> cancel provider request, remove cached promise
if (--data.promiseCnt === 0) {
data.source.cancel();
if (--data!.promiseCnt === 0) {
data!.source.cancel();
OutlineModel._requests.delete(key);
}
});
return new Promise((resolve, reject) => {
data.promise.then(model => {
data.model = model;
data!.promise.then(model => {
data!.model = model;
resolve(model);
}, err => {
OutlineModel._requests.delete(key);
@@ -331,7 +332,7 @@ export class OutlineModel extends TreeElement {
container.children[res.id] = res;
}
static get(element: TreeElement): OutlineModel {
static get(element: TreeElement | undefined): OutlineModel | undefined {
while (element) {
if (element instanceof OutlineModel) {
return element;
@@ -373,8 +374,8 @@ export class OutlineModel extends TreeElement {
} else {
// adopt all elements of the first group
let group = first(this._groups);
for (let key in group.children) {
let child = group.children[key];
for (let key in group!.children) {
let child = group!.children[key];
child.parent = this;
this.children[child.id] = child;
}
@@ -394,13 +395,13 @@ export class OutlineModel extends TreeElement {
return true;
}
private _matches: [string, OutlineElement];
private _matches: [string, OutlineElement | undefined];
updateMatches(pattern: string): OutlineElement {
updateMatches(pattern: string): OutlineElement | undefined {
if (this._matches && this._matches[0] === pattern) {
return this._matches[1];
}
let topMatch: OutlineElement;
let topMatch: OutlineElement | undefined;
for (const key in this._groups) {
topMatch = this._groups[key].updateMatches(pattern, topMatch);
}
@@ -408,9 +409,9 @@ export class OutlineModel extends TreeElement {
return topMatch;
}
getItemEnclosingPosition(position: IPosition, context?: OutlineElement): OutlineElement {
getItemEnclosingPosition(position: IPosition, context?: OutlineElement): OutlineElement | undefined {
let preferredGroup: OutlineGroup;
let preferredGroup: OutlineGroup | undefined;
if (context) {
let candidate = context.parent;
while (candidate && !preferredGroup) {
@@ -432,7 +433,7 @@ export class OutlineModel extends TreeElement {
return result;
}
getItemById(id: string): TreeElement {
getItemById(id: string): TreeElement | undefined {
return TreeElement.getElementById(id, this);
}

View File

@@ -161,7 +161,7 @@ export class OutlineRenderer implements IRenderer {
renderElement(tree: ITree, element: OutlineGroup | OutlineElement, templateId: string, template: OutlineTemplate): void {
if (element instanceof OutlineElement) {
template.icon.className = `outline-element-icon ${symbolKindToCssClass(element.symbol.kind)}`;
template.label.set(element.symbol.name, element.score ? createMatches(element.score[1]) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind]));
template.label.set(element.symbol.name, element.score ? createMatches(element.score) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind]));
template.detail.innerText = element.symbol.detail || '';
this._renderMarkerInfo(element, template);
@@ -240,9 +240,8 @@ export class OutlineRenderer implements IRenderer {
};
disposeTemplate(tree: ITree, templateId: string, template: OutlineTemplate): void {
template.label.dispose();
// noop
}
}
export class OutlineTreeState {

View File

@@ -82,16 +82,16 @@ suite('OutlineModel', function () {
}
function fakeMarker(range: Range): IMarker {
return { ...range, owner: 'ffff', message: 'test', severity: MarkerSeverity.Error, resource: null };
return { ...range, owner: 'ffff', message: 'test', severity: MarkerSeverity.Error, resource: null! };
}
test('OutlineElement - updateMarker', function () {
let e0 = new OutlineElement('foo1', null, fakeSymbolInformation(new Range(1, 1, 1, 10)));
let e1 = new OutlineElement('foo2', null, fakeSymbolInformation(new Range(2, 1, 5, 1)));
let e2 = new OutlineElement('foo3', null, fakeSymbolInformation(new Range(6, 1, 10, 10)));
let e0 = new OutlineElement('foo1', null!, fakeSymbolInformation(new Range(1, 1, 1, 10)));
let e1 = new OutlineElement('foo2', null!, fakeSymbolInformation(new Range(2, 1, 5, 1)));
let e2 = new OutlineElement('foo3', null!, fakeSymbolInformation(new Range(6, 1, 10, 10)));
let group = new OutlineGroup('group', null, null, 1);
let group = new OutlineGroup('group', null!, null!, 1);
group.children[e0.id] = e0;
group.children[e1.id] = e1;
group.children[e2.id] = e2;
@@ -113,11 +113,11 @@ suite('OutlineModel', function () {
test('OutlineElement - updateMarker, 2', function () {
let p = new OutlineElement('A', null, fakeSymbolInformation(new Range(1, 1, 11, 1)));
let c1 = new OutlineElement('A/B', null, fakeSymbolInformation(new Range(2, 4, 5, 4)));
let c2 = new OutlineElement('A/C', null, fakeSymbolInformation(new Range(6, 4, 9, 4)));
let p = new OutlineElement('A', null!, fakeSymbolInformation(new Range(1, 1, 11, 1)));
let c1 = new OutlineElement('A/B', null!, fakeSymbolInformation(new Range(2, 4, 5, 4)));
let c2 = new OutlineElement('A/C', null!, fakeSymbolInformation(new Range(6, 4, 9, 4)));
let group = new OutlineGroup('group', null, null, 1);
let group = new OutlineGroup('group', null!, null!, 1);
group.children[p.id] = p;
p.children[c1.id] = c1;
p.children[c2.id] = c2;
@@ -155,16 +155,16 @@ suite('OutlineModel', function () {
let model = new class extends OutlineModel {
constructor() {
super(null);
super(null!);
}
readyForTesting() {
this._groups = this.children as any;
}
};
model.children['g1'] = new OutlineGroup('g1', model, null, 1);
model.children['g1'] = new OutlineGroup('g1', model, null!, 1);
model.children['g1'].children['c1'] = new OutlineElement('c1', model.children['g1'], fakeSymbolInformation(new Range(1, 1, 11, 1)));
model.children['g2'] = new OutlineGroup('g2', model, null, 1);
model.children['g2'] = new OutlineGroup('g2', model, null!, 1);
model.children['g2'].children['c2'] = new OutlineElement('c2', model.children['g2'], fakeSymbolInformation(new Range(1, 1, 7, 1)));
model.children['g2'].children['c2'].children['c2.1'] = new OutlineElement('c2.1', model.children['g2'].children['c2'], fakeSymbolInformation(new Range(1, 3, 2, 19)));
model.children['g2'].children['c2'].children['c2.2'] = new OutlineElement('c2.2', model.children['g2'].children['c2'], fakeSymbolInformation(new Range(4, 1, 6, 10)));

View File

@@ -28,9 +28,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
const SEARCH_STRING_MAX_LENGTH = 524288;
export function getSelectionSearchString(editor: ICodeEditor): string {
let selection = editor.getSelection();
export function getSelectionSearchString(editor: ICodeEditor): string | null {
if (!editor.hasModel()) {
return null;
}
const selection = editor.getSelection();
// if selection spans multiple lines, default search string to empty
if (selection.startLineNumber === selection.endLineNumber) {
if (selection.isEmpty()) {
@@ -71,7 +74,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
private _findWidgetVisible: IContextKey<boolean>;
protected _state: FindReplaceState;
protected _updateHistoryDelayer: Delayer<void>;
private _model: FindModelBoundToEditorModel;
private _model: FindModelBoundToEditorModel | null;
private _storageService: IStorageService;
private _clipboardService: IClipboardService;
protected readonly _contextKeyService: IContextKeyService;
@@ -178,7 +181,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
}
public isFindInputFocused(): boolean {
return CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);
return !!CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);
}
public getState(): FindReplaceState {
@@ -218,12 +221,14 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
if (this._state.searchScope) {
this._state.change({ searchScope: null }, true);
} else {
let selection = this._editor.getSelection();
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
}
if (!selection.isEmpty()) {
this._state.change({ searchScope: selection }, true);
if (this._editor.hasModel()) {
let selection = this._editor.getSelection();
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._editor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
}
if (!selection.isEmpty()) {
this._state.change({ searchScope: selection }, true);
}
}
}
}
@@ -242,7 +247,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
protected _start(opts: IFindStartOptions): void {
this.disposeModel();
if (!this._editor.getModel()) {
if (!this._editor.hasModel()) {
// cannot do anything with an editor that doesn't have a model...
return;
}
@@ -338,6 +343,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
public getGlobalBufferTerm(): string {
if (this._editor.getConfiguration().contribInfo.find.globalFindClipboard
&& this._clipboardService
&& this._editor.hasModel()
&& !this._editor.getModel().isTooLargeForSyncing()
) {
return this._clipboardService.readFindText();
@@ -348,6 +354,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
public setGlobalBufferTerm(text: string) {
if (this._editor.getConfiguration().contribInfo.find.globalFindClipboard
&& this._clipboardService
&& this._editor.hasModel()
&& !this._editor.getModel().isTooLargeForSyncing()
) {
this._clipboardService.writeFindText(text);
@@ -430,7 +437,7 @@ export class StartFindAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
let controller = CommonFindController.get(editor);
if (controller) {
controller.start({
@@ -481,7 +488,7 @@ export class StartFindWithSelectionAction extends EditorAction {
}
}
export abstract class MatchFindAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
let controller = CommonFindController.get(editor);
if (controller && !this._run(controller)) {
controller.start({
@@ -544,7 +551,7 @@ export class PreviousMatchFindAction extends MatchFindAction {
}
export abstract class SelectionMatchFindAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
let controller = CommonFindController.get(editor);
if (!controller) {
return;
@@ -634,8 +641,8 @@ export class StartFindReplaceAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
if (editor.getConfiguration().readOnly) {
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
if (!editor.hasModel() || editor.getConfiguration().readOnly) {
return;
}

View File

@@ -80,8 +80,7 @@ export class FindDecorations implements IDisposable {
public getCurrentMatchesPosition(desiredRange: Range): number {
let candidates = this._editor.getModel().getDecorationsInRange(desiredRange);
for (let i = 0, len = candidates.length; i < len; i++) {
const candidate = candidates[i];
for (const candidate of candidates) {
const candidateOpts = candidate.options;
if (candidateOpts === FindDecorations._FIND_MATCH_DECORATION || candidateOpts === FindDecorations._CURRENT_FIND_MATCH_DECORATION) {
return this._getDecorationIndex(candidate.id);

View File

@@ -131,7 +131,7 @@ export class FindModelBoundToEditorModel {
// The find model is disposed during a find state changed event
return;
}
if (!this._editor.getModel()) {
if (!this._editor.hasModel()) {
// The find model will be disposed momentarily
return;
}

View File

@@ -46,7 +46,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('aria-hidden', 'true');
let inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder);
const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder) || undefined;
this.caseSensitive = this._register(new CaseSensitiveCheckbox({
appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
@@ -179,7 +179,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
}
private _applyTheme(theme: ITheme) {
let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) };
let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) || undefined };
this.caseSensitive.style(inputStyles);
this.wholeWords.style(inputStyles);
this.regex.style(inputStyles);

View File

@@ -41,7 +41,7 @@ export interface INewFindReplaceState {
wholeWordOverride?: FindOptionOverride;
matchCase?: boolean;
matchCaseOverride?: FindOptionOverride;
searchScope?: Range;
searchScope?: Range | null;
}
function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {

View File

@@ -111,7 +111,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _replaceFocusTracker: dom.IFocusTracker;
private _replaceInputFocused: IContextKey<boolean>;
private _viewZone: FindWidgetViewZone;
private _viewZoneId: number;
private _viewZoneId?: number;
private _resizeSash: Sash;
private _resized: boolean;
@@ -210,7 +210,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
return;
}
this._codeEditor.changeViewZones((accessor) => {
accessor.removeZone(this._viewZoneId);
if (this._viewZoneId) {
accessor.removeZone(this._viewZoneId);
}
this._viewZoneId = undefined;
});
}));
@@ -239,7 +241,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
return this._domNode;
}
public getPosition(): IOverlayWidgetPosition {
public getPosition(): IOverlayWidgetPosition | null {
if (this._isVisible) {
return {
preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER
@@ -401,8 +403,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (!this._isVisible) {
this._isVisible = true;
let selection = this._codeEditor.getSelection();
let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
const selection = this._codeEditor.getSelection();
const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
if (isSelection && this._codeEditor.getConfiguration().contribInfo.find.autoFindInSelection) {
this._toggleSelectionFind.checked = true;
} else {
@@ -425,24 +427,27 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
let adjustEditorScrollTop = true;
if (this._codeEditor.getConfiguration().contribInfo.find.seedSearchStringFromSelection && selection) {
let editorCoords = dom.getDomNodePagePosition(this._codeEditor.getDomNode());
let startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());
let startLeft = editorCoords.left + startCoords.left;
let startTop = startCoords.top;
const domNode = this._codeEditor.getDomNode();
if (domNode) {
const editorCoords = dom.getDomNodePagePosition(domNode);
const startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());
const startLeft = editorCoords.left + (startCoords ? startCoords.left : 0);
const startTop = startCoords ? startCoords.top : 0;
if (startTop < this._viewZone.heightInPx) {
if (selection.endLineNumber > selection.startLineNumber) {
adjustEditorScrollTop = false;
}
if (startTop < this._viewZone.heightInPx) {
if (selection.endLineNumber > selection.startLineNumber) {
adjustEditorScrollTop = false;
}
let leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;
if (startLeft > leftOfFindWidget) {
adjustEditorScrollTop = false;
}
let endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());
let endLeft = editorCoords.left + endCoords.left;
if (endLeft > leftOfFindWidget) {
adjustEditorScrollTop = false;
const leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;
if (startLeft > leftOfFindWidget) {
adjustEditorScrollTop = false;
}
const endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());
const endLeft = editorCoords.left + (endCoords ? endCoords.left : 0);
if (endLeft > leftOfFindWidget) {
adjustEditorScrollTop = false;
}
}
}
}
@@ -609,12 +614,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
private _updateSearchScope(): void {
if (!this._codeEditor.hasModel()) {
return;
}
if (this._toggleSelectionFind.checked) {
let selection = this._codeEditor.getSelection();
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
}
let currentMatch = this._state.currentMatch;
const currentMatch = this._state.currentMatch;
if (selection.startLineNumber !== selection.endLineNumber) {
if (!Range.equalsRange(selection, currentMatch)) {
// Reseed find scope
@@ -634,13 +643,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _onFindInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(KeyCode.Enter)) {
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(null, onUnexpectedError);
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
e.preventDefault();
return;
}
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(null, onUnexpectedError);
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
e.preventDefault();
return;
}
@@ -725,7 +734,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
validation: (value: string): InputBoxMessage => {
validation: (value: string): InputBoxMessage | null => {
if (value.length === 0) {
return null;
}
@@ -780,7 +789,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
className: 'previous',
onTrigger: () => {
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(null, onUnexpectedError);
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
}
}));
@@ -789,7 +798,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
className: 'next',
onTrigger: () => {
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(null, onUnexpectedError);
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
}
}));
@@ -806,15 +815,17 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
onChange: () => {
if (this._toggleSelectionFind.checked) {
let selection = this._codeEditor.getSelection();
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
}
if (!selection.isEmpty()) {
this._state.change({ searchScope: selection }, true);
if (this._codeEditor.hasModel()) {
let selection = this._codeEditor.getSelection();
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel().getLineMaxColumn(selection.endLineNumber - 1));
}
if (!selection.isEmpty()) {
this._state.change({ searchScope: selection }, true);
}
}
} else {
this._state.change({ searchScope: null }, true);
this._state.change({ searchScope: undefined }, true);
}
}
}));
@@ -824,7 +835,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
className: 'close-fw',
onTrigger: () => {
this._state.change({ isRevealed: false, searchScope: null }, false);
this._state.change({ isRevealed: false, searchScope: undefined }, false);
},
onKeyDown: (e) => {
if (e.equals(KeyCode.Tab)) {
@@ -850,7 +861,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
let replaceInput = document.createElement('div');
replaceInput.className = 'replace-input';
replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px';
this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, null, {
this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, undefined, {
ariaLabel: NLS_REPLACE_INPUT_LABEL,
placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
history: []
@@ -950,8 +961,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
return;
}
let inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH;
let maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH;
const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth!) || 0;
if (width > maxWidth) {
return;
}
@@ -1119,7 +1130,7 @@ export class SimpleButton extends Widget {
// theming
registerThemingParticipant((theme, collector) => {
const addBackgroundColorRule = (selector: string, color: Color): void => {
const addBackgroundColorRule = (selector: string, color: Color | null): void => {
if (color) {
collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`);
}

View File

@@ -57,8 +57,8 @@ export class ReplaceAllCommand implements editorCommon.ICommand {
}
resultOps.push(previousOp);
for (let i = 0; i < resultOps.length; i++) {
builder.addEditOperation(resultOps[i].range, resultOps[i].text);
for (const op of resultOps) {
builder.addEditOperation(op.range, op.text);
}
}

View File

@@ -18,7 +18,7 @@
.monaco-workbench .simple-find-part {
z-index: 10;
position: relative;
top: -40px;
top: -45px;
display: flex;
padding: 4px;
align-items: center;

View File

@@ -26,7 +26,7 @@ const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
export abstract class SimpleFindWidget extends Widget {
private _findInput: FindInput;
private _domNode: HTMLElement;
private _domNode?: HTMLElement;
private _innerDomNode: HTMLElement;
private _isVisible: boolean = false;
private _focusTracker: dom.IFocusTracker;
@@ -186,7 +186,7 @@ export abstract class SimpleFindWidget extends Widget {
}
}
public getDomNode(): HTMLElement {
public getDomNode() {
return this._domNode;
}

View File

@@ -51,8 +51,8 @@ export class TestFindController extends CommonFindController {
}
}
function fromRange(rng: Range): number[] {
return [rng.startLineNumber, rng.startColumn, rng.endLineNumber, rng.endColumn];
function fromSelection(slc: Selection): number[] {
return [slc.startLineNumber, slc.startColumn, slc.endLineNumber, slc.endColumn];
}
suite('FindController', () => {
@@ -67,8 +67,8 @@ suite('FindController', () => {
getBoolean: (key: string) => !!queryState[key],
getInteger: (key: string) => undefined,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
} as IStorageService);
remove: (key) => undefined
} as any);
if (platform.isMacintosh) {
serviceCollection.set(IClipboardService, <any>{
@@ -122,7 +122,7 @@ suite('FindController', () => {
nextMatchFindAction.run(null, editor);
assert.equal(findState.searchString, 'ABC');
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
findController.dispose();
});
@@ -175,14 +175,14 @@ suite('FindController', () => {
findState.change({ searchString: 'ABC' }, true);
// The first ABC is highlighted.
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
// I hit Esc to exit the Find dialog.
findController.closeFindWidget();
findController.hasFocus = false;
// The cursor is now at end of the first line, with ABC on that line highlighted.
assert.deepEqual(fromRange(editor.getSelection()), [1, 1, 1, 4]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 1, 1, 4]);
// I hit delete to remove it and change the text to XYZ.
editor.pushUndoStop();
@@ -195,10 +195,10 @@ suite('FindController', () => {
// ABC
// XYZ
// ABC
assert.equal(editor.getModel().getLineContent(1), 'XYZ');
assert.equal(editor.getModel()!.getLineContent(1), 'XYZ');
// The cursor is at end of the first line.
assert.deepEqual(fromRange(editor.getSelection()), [1, 4, 1, 4]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]);
// I hit F3 to "Find Next" to find the next occurrence of ABC, but instead it searches for XYZ.
nextMatchFindAction.run(null, editor);
@@ -224,10 +224,10 @@ suite('FindController', () => {
});
nextMatchFindAction.run(null, editor);
assert.deepEqual(fromRange(editor.getSelection()), [1, 26, 1, 29]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]);
nextMatchFindAction.run(null, editor);
assert.deepEqual(fromRange(editor.getSelection()), [1, 8, 1, 11]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]);
findController.dispose();
});
@@ -250,10 +250,10 @@ suite('FindController', () => {
startFindAction.run(null, editor);
nextMatchFindAction.run(null, editor);
assert.deepEqual(fromRange(editor.getSelection()), [2, 9, 2, 13]);
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]);
nextMatchFindAction.run(null, editor);
assert.deepEqual(fromRange(editor.getSelection()), [1, 9, 1, 13]);
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]);
findController.dispose();
});
@@ -330,7 +330,7 @@ suite('FindController', () => {
findController.getState().change({ searchString: '\\b\\s{3}\\b', replaceString: ' ', isRegex: true }, false);
findController.moveToNextMatch();
assert.deepEqual(editor.getSelections().map(fromRange), [
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
[1, 39, 1, 42]
]);
@@ -357,7 +357,7 @@ suite('FindController', () => {
findController.getState().change({ searchString: '^', replaceString: 'x', isRegex: true }, false);
findController.moveToNextMatch();
assert.deepEqual(editor.getSelections().map(fromRange), [
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
[2, 1, 2, 1]
]);
@@ -388,7 +388,7 @@ suite('FindController', () => {
// cmd+f3
nextSelectionMatchFindAction.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
[3, 1, 3, 9]
]);
@@ -419,7 +419,7 @@ suite('FindController', () => {
// cmd+f3
nextSelectionMatchFindAction.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
[3, 1, 3, 9]
]);
@@ -442,8 +442,8 @@ suite('FindController query options persistence', () => {
getBoolean: (key: string) => !!queryState[key],
getInteger: (key: string) => undefined,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
} as IStorageService);
remove: (key) => undefined
} as any);
test('matchCase', () => {
withTestCodeEditor([
@@ -464,7 +464,7 @@ suite('FindController query options persistence', () => {
// I type ABC.
findState.change({ searchString: 'ABC' }, true);
// The second ABC is highlighted as matchCase is true.
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 2, 4]);
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 4]);
findController.dispose();
});
@@ -491,7 +491,7 @@ suite('FindController query options persistence', () => {
// I type AB.
findState.change({ searchString: 'AB' }, true);
// The second AB is highlighted as wholeWord is true.
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 2, 3]);
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 1, 2, 3]);
findController.dispose();
});

View File

@@ -5,7 +5,7 @@
import * as assert from 'assert';
import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Cursor } from 'vs/editor/common/controller/cursor';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -18,7 +18,7 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
suite('FindModel', () => {
function findTest(testName: string, callback: (editor: ICodeEditor, cursor: Cursor) => void): void {
function findTest(testName: string, callback: (editor: IActiveCodeEditor, cursor: Cursor) => void): void {
test(testName, () => {
const textArr = [
'// my cool header',
@@ -34,7 +34,7 @@ suite('FindModel', () => {
'// blablablaciao',
''
];
withTestCodeEditor(textArr, {}, callback);
withTestCodeEditor(textArr, {}, (editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor));
const text = textArr.join('\n');
const ptBuilder = new PieceTreeTextBufferBuilder();
@@ -45,7 +45,9 @@ suite('FindModel', () => {
withTestCodeEditor([],
{
model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null)
}, callback);
},
(editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor)
);
});
}
@@ -54,7 +56,7 @@ suite('FindModel', () => {
}
function _getFindState(editor: ICodeEditor) {
let model = editor.getModel();
let model = editor.getModel()!;
let currentFindMatches: Range[] = [];
let allFindMatches: Range[] = [];
@@ -76,8 +78,8 @@ suite('FindModel', () => {
};
}
function assertFindState(editor: ICodeEditor, cursor: number[], highlighted: number[], findDecorations: number[][]): void {
assert.deepEqual(fromRange(editor.getSelection()), cursor, 'cursor');
function assertFindState(editor: ICodeEditor, cursor: number[], highlighted: number[] | null, findDecorations: number[][]): void {
assert.deepEqual(fromRange(editor.getSelection()!), cursor, 'cursor');
let expectedState = {
highlighted: highlighted ? [highlighted] : [],
@@ -1177,7 +1179,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1191,7 +1193,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1204,7 +1206,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, hi!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, hi!" << endl;');
findModel.replace();
assertFindState(
@@ -1216,7 +1218,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
findModel.replace();
assertFindState(
@@ -1227,7 +1229,7 @@ suite('FindModel', () => {
[6, 14, 6, 19]
]
);
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
findModel.replace();
assertFindState(
@@ -1236,7 +1238,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, hi!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;');
findModel.dispose();
findState.dispose();
@@ -1269,7 +1271,7 @@ suite('FindModel', () => {
[11, 10, 11, 13]
]
);
assert.equal(editor.getModel().getLineContent(11), '// blablablaciao');
assert.equal(editor.getModel()!.getLineContent(11), '// blablablaciao');
findModel.replace();
assertFindState(
@@ -1281,7 +1283,7 @@ suite('FindModel', () => {
[11, 11, 11, 14]
]
);
assert.equal(editor.getModel().getLineContent(11), '// ciaoblablaciao');
assert.equal(editor.getModel()!.getLineContent(11), '// ciaoblablaciao');
findModel.replace();
assertFindState(
@@ -1292,7 +1294,7 @@ suite('FindModel', () => {
[11, 12, 11, 15]
]
);
assert.equal(editor.getModel().getLineContent(11), '// ciaociaoblaciao');
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaoblaciao');
findModel.replace();
assertFindState(
@@ -1301,7 +1303,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(11), '// ciaociaociaociao');
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao');
findModel.dispose();
findState.dispose();
@@ -1338,7 +1340,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
findModel.replaceAll();
assertFindState(
@@ -1347,9 +1349,9 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, hi!" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, hi!" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
findModel.dispose();
findState.dispose();
@@ -1388,10 +1390,10 @@ suite('FindModel', () => {
[9, 1, 9, 3]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << "hello world again" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello world again" << endl;');
assert.equal(editor.getModel().getLineContent(9), ' cout << "helloworld again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "helloworld again" << endl;');
findModel.dispose();
findState.dispose();
@@ -1420,7 +1422,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(11), '// ciaociaociaociao');
assert.equal(editor.getModel()!.getLineContent(11), '// ciaociaociaociao');
findModel.dispose();
findState.dispose();
@@ -1449,10 +1451,10 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(11), '// <');
assert.equal(editor.getModel().getLineContent(12), '\t><');
assert.equal(editor.getModel().getLineContent(13), '\t><');
assert.equal(editor.getModel().getLineContent(14), '\t>ciao');
assert.equal(editor.getModel()!.getLineContent(11), '// <');
assert.equal(editor.getModel()!.getLineContent(12), '\t><');
assert.equal(editor.getModel()!.getLineContent(13), '\t><');
assert.equal(editor.getModel()!.getLineContent(14), '\t>ciao');
findModel.dispose();
findState.dispose();
@@ -1481,8 +1483,8 @@ suite('FindModel', () => {
[]
);
assert.equal(editor.getModel().getLineContent(2), '#bar "cool.h"');
assert.equal(editor.getModel().getLineContent(3), '#bar <iostream>');
assert.equal(editor.getModel()!.getLineContent(2), '#bar "cool.h"');
assert.equal(editor.getModel()!.getLineContent(3), '#bar <iostream>');
findModel.dispose();
findState.dispose();
@@ -1671,7 +1673,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1683,7 +1685,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1694,7 +1696,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
findModel.replace();
assertFindState(
@@ -1703,7 +1705,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
findModel.dispose();
findState.dispose();
@@ -1742,7 +1744,7 @@ suite('FindModel', () => {
]
);
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello world again" << endl;');
findModel.replace();
assertFindState(
@@ -1754,7 +1756,7 @@ suite('FindModel', () => {
[7, 14, 7, 19],
]
);
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
findModel.replace();
assertFindState(
@@ -1765,7 +1767,7 @@ suite('FindModel', () => {
[7, 14, 7, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1774,7 +1776,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
findModel.dispose();
findState.dispose();
@@ -1798,9 +1800,9 @@ suite('FindModel', () => {
findModel.replaceAll();
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hi world again" << endl;');
assertFindState(
editor,
@@ -1841,7 +1843,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1853,7 +1855,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hilo world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hilo world, Hello!" << endl;');
findModel.replace();
assertFindState(
@@ -1864,7 +1866,7 @@ suite('FindModel', () => {
[8, 14, 8, 19]
]
);
assert.equal(editor.getModel().getLineContent(7), ' cout << "hilo world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hilo world again" << endl;');
findModel.replace();
assertFindState(
@@ -1873,7 +1875,7 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(8), ' cout << "hilo world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "hilo world again" << endl;');
findModel.dispose();
findState.dispose();
@@ -1898,10 +1900,10 @@ suite('FindModel', () => {
findModel.replaceAll();
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << "hello girl again" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;');
assert.equal(editor.getModel().getLineContent(9), ' cout << "hellogirl again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hello girl again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;');
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hellogirl again" << endl;');
assertFindState(
editor,
@@ -1931,8 +1933,8 @@ suite('FindModel', () => {
findModel.replaceAll();
assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hello girl, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << "Hello girl again" << endl;');
assertFindState(
editor,
@@ -1969,9 +1971,9 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << " world, !" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << " world again" << endl;');
assert.equal(editor.getModel().getLineContent(8), ' cout << " world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << " world, !" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << " world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(8), ' cout << " world again" << endl;');
findModel.dispose();
findState.dispose();
@@ -2023,9 +2025,9 @@ suite('FindModel', () => {
null,
[]
);
assert.equal(editor.getModel().getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel().getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel().getLineContent(9), ' cout << "hiworld again" << endl;');
assert.equal(editor.getModel()!.getLineContent(6), ' cout << "hi world, Hello!" << endl;');
assert.equal(editor.getModel()!.getLineContent(7), ' cout << "hi world again" << endl;');
assert.equal(editor.getModel()!.getLineContent(9), ' cout << "hiworld again" << endl;');
findModel.dispose();
findState.dispose();

View File

@@ -36,7 +36,7 @@ export const ID = 'editor.contrib.folding';
export interface RangeProvider {
readonly id: string;
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions | null>;
compute(cancelationToken: CancellationToken): Promise<FoldingRegions | null>;
dispose(): void;
}
@@ -70,7 +70,7 @@ export class FoldingController implements IEditorContribution {
private foldingStateMemento: FoldingStateMemento | null;
private foldingModelPromise: Thenable<FoldingModel | null> | null;
private foldingModelPromise: Promise<FoldingModel | null> | null;
private updateScheduler: Delayer<FoldingModel | null> | null;
private globalToDispose: IDisposable[];
@@ -136,10 +136,10 @@ export class FoldingController implements IEditorContribution {
}
if (this.foldingModel) { // disposed ?
let collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel!.getMemento();
let provider = this.rangeProvider ? this.rangeProvider.id : void 0;
let provider = this.rangeProvider ? this.rangeProvider.id : undefined;
return { collapsedRegions, lineCount: model.getLineCount(), provider };
}
return void 0;
return undefined;
}
/**
@@ -432,7 +432,7 @@ abstract class FoldingAction<T> extends EditorAction {
abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T): void;
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Thenable<void> {
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise<void> {
let foldingController = FoldingController.get(editor);
if (!foldingController) {
return;

View File

@@ -153,7 +153,7 @@ export class FoldingModel {
if (collapsedRanges.length > 0) {
return collapsedRanges;
}
return void 0;
return undefined;
}
/**
@@ -291,7 +291,6 @@ export function setCollapseStateLevelsUp(foldingModel: FoldingModel, doCollapse:
* Folds or unfolds all regions that have a given level, except if they contain one of the blocked lines.
* @param foldLevel level. Level == 1 is the top level
* @param doCollapse Wheter to collase or expand
* @param blockedLineNumbers
*/
export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: number, doCollapse: boolean, blockedLineNumbers: number[]): void {
let filter = (region: FoldingRegion, level: number) => level === foldLevel && region.isCollapsed !== doCollapse && !blockedLineNumbers.some(line => region.containsLine(line));

View File

@@ -18,9 +18,9 @@ export class FoldingRegions {
private _endIndexes: Uint32Array;
private _collapseStates: Uint32Array;
private _parentsComputed: boolean;
private _types: (string | undefined)[] | undefined;
private _types: Array<string | undefined> | undefined;
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: (string | undefined)[]) {
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array<string | undefined>) {
if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {
throw new Error('invalid startIndexes or endIndexes size');
}
@@ -68,7 +68,7 @@ export class FoldingRegions {
}
public getType(index: number): string | undefined {
return this._types ? this._types[index] : void 0;
return this._types ? this._types[index] : undefined;
}
public hasTypes() {

View File

@@ -26,7 +26,7 @@ export class IndentRangeProvider implements RangeProvider {
dispose() {
}
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions> {
compute(cancelationToken: CancellationToken): Promise<FoldingRegions> {
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this.editorModel.getLanguageIdentifier().id);
let offSide = foldingRules && !!foldingRules.offSide;
let markers = foldingRules && foldingRules.markers;
@@ -113,7 +113,7 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol
const tabSize = model.getOptions().tabSize;
let result = new RangesCollector(foldingRangesLimit);
let pattern: RegExp | undefined = void 0;
let pattern: RegExp | undefined = undefined;
if (markers) {
pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
}

View File

@@ -40,15 +40,15 @@ export class InitializingRangeProvider implements RangeProvider {
dispose(): void {
if (this.decorationIds) {
this.editorModel.deltaDecorations(this.decorationIds, []);
this.decorationIds = void 0;
this.decorationIds = undefined;
}
if (typeof this.timeout === 'number') {
clearTimeout(this.timeout);
this.timeout = void 0;
this.timeout = undefined;
}
}
compute(cancelationToken: CancellationToken): Thenable<FoldingRegions> {
compute(cancelationToken: CancellationToken): Promise<FoldingRegions> {
let foldingRangeData: IFoldingRangeData[] = [];
if (this.decorationIds) {
for (let id of this.decorationIds) {

View File

@@ -28,7 +28,7 @@ export class SyntaxRangeProvider implements RangeProvider {
constructor(private editorModel: ITextModel, private providers: FoldingRangeProvider[], private limit = MAX_FOLDING_REGIONS) {
}
compute(cancellationToken: CancellationToken): Thenable<FoldingRegions | null> {
compute(cancellationToken: CancellationToken): Promise<FoldingRegions | null> {
return collectSyntaxRanges(this.providers, this.editorModel, cancellationToken).then(ranges => {
if (ranges) {
let res = sanitizeRanges(ranges, this.limit);
@@ -43,7 +43,7 @@ export class SyntaxRangeProvider implements RangeProvider {
}
function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Thenable<IFoldingRangeData[] | null> {
function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Promise<IFoldingRangeData[] | null> {
let rangeData: IFoldingRangeData[] | null = null;
let promises = providers.map((provider, i) => {
return Promise.resolve(provider.provideFoldingRanges(model, foldingContext, cancellationToken)).then(ranges => {
@@ -73,7 +73,7 @@ export class RangesCollector {
private _endIndexes: number[];
private _nestingLevels: number[];
private _nestingLevelCounts: number[];
private _types: (string | undefined)[];
private _types: Array<string | undefined>;
private _length: number;
private _foldingRangesLimit: number;
@@ -127,7 +127,7 @@ export class RangesCollector {
let startIndexes = new Uint32Array(this._foldingRangesLimit);
let endIndexes = new Uint32Array(this._foldingRangesLimit);
let types: (string | undefined)[] = [];
let types: Array<string | undefined> = [];
for (let i = 0, k = 0; i < this._length; i++) {
let level = this._nestingLevels[i];
if (level < maxLevel || (level === maxLevel && entries++ < this._foldingRangesLimit)) {
@@ -155,7 +155,7 @@ export function sanitizeRanges(rangeData: IFoldingRangeData[], limit: number): F
});
let collector = new RangesCollector(limit);
let top: IFoldingRangeData | undefined = void 0;
let top: IFoldingRangeData | undefined = undefined;
let previous: IFoldingRangeData[] = [];
for (let entry of sorted) {
if (!top) {

View File

@@ -96,7 +96,7 @@ suite('Folding Model', () => {
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
let r1 = r(1, 3, false);
@@ -135,7 +135,7 @@ suite('Folding Model', () => {
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
let r1 = r(1, 3, false);
@@ -181,7 +181,7 @@ suite('Folding Model', () => {
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
let r1 = r(1, 3, false);
@@ -193,7 +193,7 @@ suite('Folding Model', () => {
textModel.applyEdits([EditOperation.insert(new Position(4, 1), '//hello\n')]);
foldingModel.update(computeRanges(textModel, false, void 0));
foldingModel.update(computeRanges(textModel, false, undefined));
assertRanges(foldingModel, [r(1, 3, true), r(5, 8, false), r(6, 7, true)]);
} finally {
@@ -221,7 +221,7 @@ suite('Folding Model', () => {
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
let r1 = r(1, 12, false);
@@ -235,7 +235,7 @@ suite('Folding Model', () => {
textModel.applyEdits([EditOperation.delete(new Range(6, 11, 9, 0))]);
foldingModel.update(computeRanges(textModel, false, void 0));
foldingModel.update(computeRanges(textModel, false, undefined));
assertRanges(foldingModel, [r(1, 9, false), r(2, 8, false), r(3, 5, false), r(6, 8, false)]);
} finally {
@@ -258,7 +258,7 @@ suite('Folding Model', () => {
try {
let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
let r1 = r(1, 3, false);

View File

@@ -44,7 +44,7 @@ suite('Hidden Range Model', () => {
assert.equal(hiddenRangeModel.hasRanges(), false);
let ranges = computeRanges(textModel, false, void 0);
let ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!]);

View File

@@ -17,11 +17,15 @@ import { CancellationToken } from 'vs/base/common/cancellation';
export class NoProviderError extends Error {
static readonly Name = 'NOPRO';
static is(thing: any): thing is NoProviderError {
return thing instanceof Error && thing.name === NoProviderError._name;
}
private static readonly _name = 'NOPRO';
constructor(message?: string) {
super();
this.name = NoProviderError.Name;
this.name = NoProviderError._name;
if (message) {
this.message = message;
}

View File

@@ -3,30 +3,32 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution, IActionOptions } from 'vs/editor/browser/editorExtensions';
import { OnTypeFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { sequence } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import { Range } from 'vs/editor/common/core/range';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISingleEditOperation } from 'vs/editor/common/model';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, FormattingOptions, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { getOnTypeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
import * as nls from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CancellationToken } from 'vs/base/common/cancellation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
function alertFormattingEdits(edits: ISingleEditOperation[]): void {
@@ -55,6 +57,134 @@ function alertFormattingEdits(edits: ISingleEditOperation[]): void {
}
}
export const enum FormatRangeType {
Full,
Selection,
}
export function formatDocumentRange(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, rangeOrRangeType: Range | FormatRangeType, options: FormattingOptions, token: CancellationToken): Promise<void> {
const provider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel());
if (provider.length === 0) {
return Promise.reject(new NoProviderError());
}
// Know how often multiple providers clash and (for now)
// continue picking the 'first' provider
if (provider.length !== 1) {
/* __GDPR__
"manyformatters" : {
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
telemetryService.publicLog('manyformatters', {
type: 'range',
language: editor.getModel().getLanguageIdentifier().language,
count: provider.length,
});
provider.length = 1;
}
let allEdits: ISingleEditOperation[] = [];
editor.pushUndoStop();
return sequence(provider.map(provider => {
// create a formatting task per provider. they run sequentially,
// potentially undoing the working of a previous formatter
return () => {
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const model = editor.getModel();
let range: Range;
if (rangeOrRangeType === FormatRangeType.Full) {
// full
range = model.getFullModelRange();
} else if (rangeOrRangeType === FormatRangeType.Selection) {
// selection or line (when empty)
range = editor.getSelection();
if (range.isEmpty()) {
range = new Range(range.startLineNumber, 1, range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
}
} else {
// as is
range = rangeOrRangeType;
}
return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).then(edits => {
// break edits into smaller edits
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
}).then(edits => {
// make edit only when the editor didn't change while
// computing and only when there are edits
if (state.validate(editor) && isNonEmptyArray(edits)) {
FormattingEdit.execute(editor, edits);
allEdits = allEdits.concat(edits);
}
});
};
})).then(() => {
alertFormattingEdits(allEdits);
editor.pushUndoStop();
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
});
}
export function formatDocument(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, options: FormattingOptions, token: CancellationToken): Promise<void> {
const provider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel());
if (provider.length === 0) {
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Full, options, token);
}
// Know how often multiple providers clash and (for now)
// continue picking the 'first' provider
if (provider.length !== 1) {
/* __GDPR__
"manyformatters" : {
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
}
*/
telemetryService.publicLog('manyformatters', {
type: 'document',
language: editor.getModel().getLanguageIdentifier().language,
count: provider.length,
});
provider.length = 1;
}
let allEdits: ISingleEditOperation[] = [];
editor.pushUndoStop();
return sequence(provider.map(provider => {
// create a formatting task per provider. they run sequentially,
// potentially undoing the working of a previous formatter
return () => {
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const model = editor.getModel();
return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).then(edits => {
// break edits into smaller edits
return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits);
}).then(edits => {
// make edit only when the editor didn't change while
// computing and only when there are edits
if (state.validate(editor) && isNonEmptyArray(edits)) {
FormattingEdit.execute(editor, edits);
allEdits = allEdits.concat(edits);
}
});
};
})).then(() => {
alertFormattingEdits(allEdits);
editor.pushUndoStop();
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
});
}
class FormatOnType implements editorCommon.IEditorContribution {
private static readonly ID = 'editor.contrib.autoFormat';
@@ -87,7 +217,7 @@ class FormatOnType implements editorCommon.IEditorContribution {
}
// no model
if (!this.editor.getModel()) {
if (!this.editor.hasModel()) {
return;
}
@@ -113,6 +243,9 @@ class FormatOnType implements editorCommon.IEditorContribution {
}
private trigger(ch: string): void {
if (!this.editor.hasModel()) {
return;
}
if (this.editor.getSelections().length > 1) {
return;
@@ -157,12 +290,14 @@ class FormatOnType implements editorCommon.IEditorContribution {
unbind.dispose();
if (canceled || isFalsyOrEmpty(edits)) {
if (canceled) {
return;
}
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
if (isNonEmptyArray(edits)) {
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
}
}, (err) => {
unbind.dispose();
@@ -184,14 +319,14 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
private static readonly ID = 'editor.contrib.formatOnPaste';
private editor: ICodeEditor;
private workerService: IEditorWorkerService;
private callOnDispose: IDisposable[];
private callOnModel: IDisposable[];
constructor(editor: ICodeEditor, @IEditorWorkerService workerService: IEditorWorkerService) {
this.editor = editor;
this.workerService = workerService;
constructor(
private readonly editor: ICodeEditor,
@IEditorWorkerService private readonly workerService: IEditorWorkerService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
this.callOnDispose = [];
this.callOnModel = [];
@@ -212,15 +347,14 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
}
// no model
if (!this.editor.getModel()) {
if (!this.editor.hasModel()) {
return;
}
let model = this.editor.getModel();
// no support
let [support] = DocumentRangeFormattingEditProviderRegistry.ordered(model);
if (!support || !support.provideDocumentRangeFormattingEdits) {
if (!DocumentRangeFormattingEditProviderRegistry.has(model)) {
return;
}
@@ -230,23 +364,17 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
}
private trigger(range: Range): void {
if (!this.editor.hasModel()) {
return;
}
if (this.editor.getSelections().length > 1) {
return;
}
const model = this.editor.getModel();
const { tabSize, insertSpaces } = model.getOptions();
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }, CancellationToken.None).then(edits => {
return this.workerService.computeMoreMinimalEdits(model.uri, edits);
}).then(edits => {
if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) {
return;
}
FormattingEdit.execute(this.editor, edits);
alertFormattingEdits(edits);
});
formatDocumentRange(this.telemetryService, this.workerService, this.editor, range, { tabSize, insertSpaces }, CancellationToken.None);
}
public getId(): string {
@@ -259,48 +387,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution {
}
}
export abstract class AbstractFormatAction extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const workerService = accessor.get(IEditorWorkerService);
const notificationService = accessor.get(INotificationService);
const formattingPromise = this._getFormattingEdits(editor, CancellationToken.None);
if (!formattingPromise) {
return Promise.resolve(void 0);
}
// Capture the state of the editor
const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
// Receive formatted value from worker
return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits)).then(edits => {
if (!state.validate(editor) || isFalsyOrEmpty(edits)) {
return;
}
FormattingEdit.execute(editor, edits);
alertFormattingEdits(edits);
editor.focus();
editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate);
}, err => {
if (err instanceof Error && err.name === NoProviderError.Name) {
this._notifyNoProviderError(notificationService, editor.getModel().getLanguageIdentifier().language);
} else {
throw err;
}
});
}
protected abstract _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]>;
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
notificationService.info(nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", language));
}
}
export class FormatDocumentAction extends AbstractFormatAction {
export class FormatDocumentAction extends EditorAction {
constructor() {
super({
@@ -323,18 +410,23 @@ export class FormatDocumentAction extends AbstractFormatAction {
});
}
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
const model = editor.getModel();
const { tabSize, insertSpaces } = model.getOptions();
return getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token);
}
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", language));
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
if (!editor.hasModel()) {
return;
}
const notificationService = accessor.get(INotificationService);
const workerService = accessor.get(IEditorWorkerService);
const telemetryService = accessor.get(ITelemetryService);
const { tabSize, insertSpaces } = editor.getModel().getOptions();
return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
if (NoProviderError.is(err)) {
notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
}
});
}
}
export class FormatSelectionAction extends AbstractFormatAction {
export class FormatSelectionAction extends EditorAction {
constructor() {
super({
@@ -355,20 +447,19 @@ export class FormatSelectionAction extends AbstractFormatAction {
});
}
protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
const model = editor.getModel();
let selection = editor.getSelection();
if (selection.isEmpty()) {
const maxColumn = model.getLineMaxColumn(selection.startLineNumber);
selection = selection.setStartPosition(selection.startLineNumber, 1);
selection = selection.setEndPosition(selection.endLineNumber, maxColumn);
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | void {
if (!editor.hasModel()) {
return;
}
const { tabSize, insertSpaces } = model.getOptions();
return getDocumentRangeFormattingEdits(model, selection, { tabSize, insertSpaces }, token);
}
protected _notifyNoProviderError(notificationService: INotificationService, language: string): void {
notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", language));
const notificationService = accessor.get(INotificationService);
const workerService = accessor.get(IEditorWorkerService);
const telemetryService = accessor.get(ITelemetryService);
const { tabSize, insertSpaces } = editor.getModel().getOptions();
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None).catch(err => {
if (NoProviderError.is(err)) {
notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language));
}
});
}
}
@@ -381,21 +472,51 @@ registerEditorAction(FormatSelectionAction);
// and we keep it here such that existing keybinding configurations etc will still work
CommandsRegistry.registerCommand('editor.action.format', accessor => {
const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
if (editor) {
return new class extends AbstractFormatAction {
constructor() {
super({} as IActionOptions);
}
_getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise<ISingleEditOperation[]> {
const model = editor.getModel();
const editorSelection = editor.getSelection();
const { tabSize, insertSpaces } = model.getOptions();
return editorSelection.isEmpty()
? getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token)
: getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces }, token);
}
}().run(accessor, editor);
if (!editor || !editor.hasModel()) {
return undefined;
}
const { tabSize, insertSpaces } = editor.getModel().getOptions();
const workerService = accessor.get(IEditorWorkerService);
const telemetryService = accessor.get(ITelemetryService);
if (editor.getSelection().isEmpty()) {
return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None);
} else {
return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None);
}
return undefined;
});
CommandsRegistry.registerCommand('editor.action.formatInspect', accessor => {
const editor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (!editor || !editor.hasModel()) {
return;
}
console.log(`Available Formatters for: ${editor.getModel().uri.toString(true)}`);
// range formatters
const documentRangeProvider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel());
console.group('Range Formatters');
if (documentRangeProvider.length === 0) {
console.log('none');
} else {
documentRangeProvider.forEach(value => console.log(value.displayName));
}
console.groupEnd();
// whole document formatters
const documentProvider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel());
console.group('Document Formatters');
if (documentProvider.length === 0) {
console.log('none');
} else {
documentProvider.forEach(value => console.log(value.displayName));
}
console.groupEnd();
});
MenuRegistry.addCommand({
id: 'editor.action.formatInspect',
category: nls.localize('cat', "Developer"),
title: nls.localize('title', "Print Available Formatters..."),
});

View File

@@ -9,7 +9,7 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
import { DefinitionLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry } from 'vs/editor/common/modes';
import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult } from 'vs/editor/common/modes';
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
@@ -17,12 +17,12 @@ function getDefinitions<T>(
model: ITextModel,
position: Position,
registry: LanguageFeatureRegistry<T>,
provide: (provider: T, model: ITextModel, position: Position) => DefinitionLink | DefinitionLink[] | null | undefined | Thenable<DefinitionLink | DefinitionLink[] | null | undefined>
): Thenable<DefinitionLink[]> {
provide: (provider: T, model: ITextModel, position: Position) => ProviderResult<LocationLink | LocationLink[]>
): Promise<LocationLink[]> {
const provider = registry.ordered(model);
// get results
const promises = provider.map((provider): Thenable<DefinitionLink | DefinitionLink[] | null | undefined> => {
const promises = provider.map((provider): Promise<LocationLink | LocationLink[] | null | undefined> => {
return Promise.resolve(provide(provider, model, position)).then(undefined, err => {
onUnexpectedExternalError(err);
return null;
@@ -34,25 +34,25 @@ function getDefinitions<T>(
}
export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
export function getDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position) => {
return provider.provideDefinition(model, position, token);
});
}
export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
export function getDeclarationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getDefinitions(model, position, DeclarationProviderRegistry, (provider, model, position) => {
return provider.provideDeclaration(model, position, token);
});
}
export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
export function getImplementationsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position) => {
return provider.provideImplementation(model, position, token);
});
}
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Thenable<DefinitionLink[]> {
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position) => {
return provider.provideTypeDefinition(model, position, token);
});

View File

@@ -12,10 +12,10 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, IActionOptions, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import * as corePosition from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Range, IRange } from 'vs/editor/common/core/range';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
import { DefinitionLink, Location } from 'vs/editor/common/modes';
import { LocationLink, Location, isLocationLink } from 'vs/editor/common/modes';
import { MessageController } from 'vs/editor/contrib/message/messageController';
import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController';
@@ -29,7 +29,6 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
export class DefinitionActionConfig {
constructor(
@@ -44,14 +43,14 @@ export class DefinitionActionConfig {
export class DefinitionAction extends EditorAction {
private _configuration: DefinitionActionConfig;
private readonly _configuration: DefinitionActionConfig;
constructor(configuration: DefinitionActionConfig, opts: IActionOptions) {
super(opts);
this._configuration = configuration;
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Thenable<void> {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const notificationService = accessor.get(INotificationService);
const editorService = accessor.get(ICodeEditorService);
const progressService = accessor.get(IProgressService);
@@ -59,7 +58,7 @@ export class DefinitionAction extends EditorAction {
const model = editor.getModel();
const pos = editor.getPosition();
const definitionPromise = this._getTargetLocationForPosition(model, pos, CancellationToken.None).then(references => {
const definitionPromise = this._getTargetLocationForPosition(model, pos, CancellationToken.None).then(async references => {
if (model.isDisposed() || editor.getModel() !== model) {
// new model, no more model
@@ -69,20 +68,15 @@ export class DefinitionAction extends EditorAction {
// * remove falsy references
// * find reference at the current pos
let idxOfCurrent = -1;
const result: DefinitionLink[] = [];
for (let i = 0; i < references.length; i++) {
let reference = references[i];
const result: LocationLink[] = [];
for (const reference of references) {
if (!reference || !reference.range) {
continue;
}
let { uri, range } = reference;
let newLen = result.push({
uri,
range
});
const newLen = result.push(reference);
if (this._configuration.filterCurrent
&& uri.toString() === model.uri.toString()
&& Range.containsPosition(range, pos)
&& reference.uri.toString() === model.uri.toString()
&& Range.containsPosition(reference.range, pos)
&& idxOfCurrent === -1
) {
idxOfCurrent = newLen - 1;
@@ -98,11 +92,11 @@ export class DefinitionAction extends EditorAction {
} else if (result.length === 1 && idxOfCurrent !== -1) {
// only the position at which we are -> adjust selection
let [current] = result;
this._openReference(editor, editorService, current, false);
return this._openReference(editor, editorService, current, false).then(() => undefined);
} else {
// handle multile results
this._onResult(editorService, editor, new ReferencesModel(result));
return this._onResult(editorService, editor, new ReferencesModel(result));
}
}, (err) => {
@@ -114,7 +108,7 @@ export class DefinitionAction extends EditorAction {
return definitionPromise;
}
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
return getDefinitionsAtPosition(model, position, token);
}
@@ -128,7 +122,7 @@ export class DefinitionAction extends EditorAction {
return model.references.length > 1 && nls.localize('meta.title', " {0} definitions", model.references.length);
}
private _onResult(editorService: ICodeEditorService, editor: ICodeEditor, model: ReferencesModel) {
private async _onResult(editorService: ICodeEditorService, editor: ICodeEditor, model: ReferencesModel): Promise<void> {
const msg = model.getAriaMessage();
alert(msg);
@@ -136,22 +130,31 @@ export class DefinitionAction extends EditorAction {
if (this._configuration.openInPeek) {
this._openInPeek(editorService, editor, model);
} else {
let next = model.nearestReference(editor.getModel().uri, editor.getPosition());
this._openReference(editor, editorService, next, this._configuration.openToSide).then(editor => {
if (editor && model.references.length > 1) {
this._openInPeek(editorService, editor, model);
} else {
model.dispose();
}
});
const next = model.nearestReference(editor.getModel().uri, editor.getPosition());
const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide);
if (targetEditor && model.references.length > 1) {
this._openInPeek(editorService, targetEditor, model);
} else {
model.dispose();
}
}
}
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location, sideBySide: boolean): Thenable<ICodeEditor> {
private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise<ICodeEditor> {
// range is the target-selection-range when we have one
// and the the fallback is the 'full' range
let range: IRange = undefined;
if (isLocationLink(reference)) {
range = reference.targetSelectionRange;
}
if (!range) {
range = reference.range;
}
return editorService.openCodeEditor({
resource: reference.uri,
options: {
selection: Range.collapseToStart(reference.range),
selection: Range.collapseToStart(range),
revealIfOpened: true,
revealInCenterIfOutsideViewport: true
}
@@ -233,7 +236,7 @@ export class PeekDefinitionAction extends DefinitionAction {
static readonly id = 'editor.action.peekDefinition';
constructor() {
super(new DefinitionActionConfig(void 0, true, false), {
super(new DefinitionActionConfig(undefined, true, false), {
id: PeekDefinitionAction.id,
label: nls.localize('actions.previewDecl.label', "Peek Definition"),
alias: 'Peek Definition',
@@ -258,7 +261,7 @@ export class PeekDefinitionAction extends DefinitionAction {
export class DeclarationAction extends DefinitionAction {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
return getDeclarationsAtPosition(model, position, token);
}
@@ -305,7 +308,7 @@ export class GoToDeclarationAction extends DeclarationAction {
export class PeekDeclarationAction extends DeclarationAction {
constructor() {
super(new DefinitionActionConfig(void 0, true, false), {
super(new DefinitionActionConfig(undefined, true, false), {
id: 'editor.action.peekDeclaration',
label: nls.localize('actions.peekDecl.label', "Peek Declaration"),
alias: 'Peek Declaration',
@@ -322,7 +325,7 @@ export class PeekDeclarationAction extends DeclarationAction {
}
export class ImplementationAction extends DefinitionAction {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
return getImplementationsAtPosition(model, position, token);
}
@@ -380,7 +383,7 @@ export class PeekImplementationAction extends ImplementationAction {
}
export class TypeDefinitionAction extends DefinitionAction {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Thenable<DefinitionLink[]> {
protected _getTargetLocationForPosition(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<LocationLink[]> {
return getTypeDefinitionsAtPosition(model, position, token);
}
@@ -452,29 +455,31 @@ registerEditorAction(GoToTypeDefinitionAction);
registerEditorAction(PeekTypeDefinitionAction);
// Go to menu
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToDeclaration',
title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
},
order: 4
});
// {{SQL CARBON EDIT}} - Disable unused menu items
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
// group: '4_symbol_nav',
// command: {
// id: 'editor.action.goToDeclaration',
// title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
// },
// order: 2
// });
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToTypeDefinition',
title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
},
order: 5
});
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
// group: '4_symbol_nav',
// command: {
// id: 'editor.action.goToTypeDefinition',
// title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
// },
// order: 3
// });
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: 'z_go_to',
command: {
id: 'editor.action.goToImplementation',
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
},
order: 6
});
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
// group: '4_symbol_nav',
// command: {
// id: 'editor.action.goToImplementation',
// title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
// },
// order: 4
// });
// {{SQL CARBON EDIT - End}}

View File

@@ -12,7 +12,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { DefinitionProviderRegistry, DefinitionLink } from 'vs/editor/common/modes';
import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes';
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { getDefinitionsAtPosition } from './goToDefinition';
@@ -35,12 +35,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
private toUnhook: IDisposable[];
private decorations: string[];
private currentWordUnderMouse: IWordAtPosition;
private previousPromise: CancelablePromise<DefinitionLink[]>;
private previousPromise: CancelablePromise<LocationLink[]>;
constructor(
editor: ICodeEditor,
@ITextModelService private textModelResolverService: ITextModelService,
@IModeService private modeService: IModeService
@ITextModelService private readonly textModelResolverService: ITextModelService,
@IModeService private readonly modeService: IModeService
) {
this.toUnhook = [];
this.decorations = [];
@@ -152,8 +152,8 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
const previewValue = this.getPreviewValue(textEditorModel, startLineNumber);
let wordRange: Range;
if (result.origin) {
wordRange = Range.lift(result.origin);
if (result.originSelectionRange) {
wordRange = Range.lift(result.originSelectionRange);
} else {
wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
}
@@ -281,7 +281,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
DefinitionProviderRegistry.has(this.editor.getModel());
}
private findDefinition(target: IMouseTarget, token: CancellationToken): Thenable<DefinitionLink[]> {
private findDefinition(target: IMouseTarget, token: CancellationToken): Promise<LocationLink[] | null> {
const model = this.editor.getModel();
if (!model) {
return Promise.resolve(null);
@@ -290,7 +290,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
return getDefinitionsAtPosition(model, target.position, token);
}
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Thenable<any> {
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise<any> {
this.editor.setPosition(target.position);
const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: undefined, label: undefined, id: undefined, precondition: undefined });
return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor));

View File

@@ -23,6 +23,7 @@ import { compare } from 'vs/base/common/strings';
import { binarySearch } from 'vs/base/common/arrays';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { onUnexpectedError } from 'vs/base/common/errors';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
class MarkerModel {
@@ -31,7 +32,7 @@ class MarkerModel {
private _nextIdx: number;
private _toUnbind: IDisposable[];
private _ignoreSelectionChange: boolean;
private readonly _onCurrentMarkerChanged: Emitter<IMarker>;
private readonly _onCurrentMarkerChanged: Emitter<IMarker | undefined>;
private readonly _onMarkerSetChanged: Emitter<MarkerModel>;
constructor(editor: ICodeEditor, markers: IMarker[]) {
@@ -320,30 +321,30 @@ class MarkerNavigationAction extends EditorAction {
this._multiFile = multiFile;
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Thenable<void> {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const markerService = accessor.get(IMarkerService);
const editorService = accessor.get(ICodeEditorService);
const controller = MarkerController.get(editor);
if (!controller) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
const model = controller.getOrCreateModel();
const atEdge = model.move(this._isNext, !this._multiFile);
if (!atEdge || !this._multiFile) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
// try with the next/prev file
let markers = markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }).sort(MarkerNavigationAction.compareMarker);
if (markers.length === 0) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
let editorModel = editor.getModel();
if (!editorModel) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
let oldMarker = model.currentMarker || <IMarker>{ resource: editorModel!.uri, severity: MarkerSeverity.Error, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 };
@@ -363,7 +364,7 @@ class MarkerNavigationAction extends EditorAction {
// the next `resource` is this resource which
// means we cycle within this file
model.move(this._isNext, true);
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
// close the widget for this editor-instance, open the resource
@@ -468,3 +469,22 @@ registerEditorCommand(new MarkerCommand({
secondary: [KeyMod.Shift | KeyCode.Escape]
}
}));
// Go to menu
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '6_problem_nav',
command: {
id: 'editor.action.marker.nextInFiles',
title: nls.localize({ key: 'miGotoNextProblem', comment: ['&& denotes a mnemonic'] }, "Next &&Problem")
},
order: 1
});
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '6_problem_nav',
command: {
id: 'editor.action.marker.prevInFiles',
title: nls.localize({ key: 'miGotoPreviousProblem', comment: ['&& denotes a mnemonic'] }, "Previous &&Problem")
},
order: 2
});

View File

@@ -6,7 +6,7 @@
/* marker zone */
.monaco-editor .marker-widget {
padding: 6px 12px;
padding: 3px 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -35,6 +35,11 @@
.monaco-editor .marker-widget .descriptioncontainer .message {
display: flex;
flex-direction: column;
}
.monaco-editor .marker-widget .descriptioncontainer .message .details {
padding-left: 6px;
}
.monaco-editor .marker-widget .descriptioncontainer .message .source,

View File

@@ -80,22 +80,43 @@ class MessageWidget {
update({ source, message, relatedInformation, code }: IMarker): void {
if (source) {
const lines = message.split(/\r\n|\r|\n/g);
this._lines = lines.length;
this._longestLineLength = 0;
for (const line of lines) {
this._longestLineLength = Math.max(line.length, this._longestLineLength);
const lines = message.split(/\r\n|\r|\n/g);
this._lines = lines.length;
this._longestLineLength = 0;
for (const line of lines) {
this._longestLineLength = Math.max(line.length, this._longestLineLength);
}
dom.clearNode(this._messageBlock);
let lastLineElement = this._messageBlock;
for (const line of lines) {
lastLineElement = document.createElement('div');
lastLineElement.innerText = line;
this._editor.applyFontInfo(lastLineElement);
this._messageBlock.appendChild(lastLineElement);
}
if (source || code) {
const detailsElement = document.createElement('span');
dom.addClass(detailsElement, 'details');
lastLineElement.appendChild(detailsElement);
if (source) {
const sourceElement = document.createElement('span');
sourceElement.innerText = source;
dom.addClass(sourceElement, 'source');
detailsElement.appendChild(sourceElement);
}
if (code) {
const codeElement = document.createElement('span');
codeElement.innerText = `(${code})`;
dom.addClass(codeElement, 'code');
detailsElement.appendChild(codeElement);
}
} else {
this._lines = 1;
this._longestLineLength = message.length;
}
dom.clearNode(this._relatedBlock);
if (isNonEmptyArray(relatedInformation)) {
this._relatedBlock.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * .66)}px`;
const relatedInformationNode = this._relatedBlock.appendChild(document.createElement('div'));
relatedInformationNode.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * 0.66)}px`;
this._lines += 1;
for (const related of relatedInformation) {
@@ -116,30 +137,10 @@ class MessageWidget {
container.appendChild(relatedMessage);
this._lines += 1;
this._relatedBlock.appendChild(container);
relatedInformationNode.appendChild(container);
}
}
dom.clearNode(this._messageBlock);
if (source) {
const sourceElement = document.createElement('div');
sourceElement.innerText = `[${source}] `;
dom.addClass(sourceElement, 'source');
this._editor.applyFontInfo(sourceElement);
this._messageBlock.appendChild(sourceElement);
}
const messageElement = document.createElement('div');
messageElement.innerText = message;
this._editor.applyFontInfo(messageElement);
this._messageBlock.appendChild(messageElement);
if (code) {
const codeElement = document.createElement('div');
codeElement.innerText = ` [${code}]`;
dom.addClass(codeElement, 'code');
this._editor.applyFontInfo(codeElement);
this._messageBlock.appendChild(codeElement);
}
const fontInfo = this._editor.getConfiguration().fontInfo;
const scrollWidth = Math.ceil(fontInfo.typicalFullwidthCharacterWidth * this._longestLineLength * 0.75);
const scrollHeight = fontInfo.lineHeight * this._lines;

View File

@@ -36,6 +36,14 @@
margin: 8px 0;
}
.monaco-editor-hover hr {
margin-top: 4px;
margin-bottom: -6px;
margin-left: -10px;
margin-right: -10px;
height: 1px;
}
.monaco-editor-hover p:first-child,
.monaco-editor-hover ul:first-child {
margin-top: 0;

View File

@@ -8,7 +8,6 @@ import * as nls from 'vs/nls';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
@@ -25,6 +24,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
export class ModesHoverController implements IEditorContribution {
@@ -62,6 +62,7 @@ export class ModesHoverController implements IEditorContribution {
constructor(private readonly _editor: ICodeEditor,
@IOpenerService private readonly _openerService: IOpenerService,
@IModeService private readonly _modeService: IModeService,
@IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService,
@IThemeService private readonly _themeService: IThemeService
) {
this._toUnhook = [];
@@ -146,18 +147,17 @@ export class ModesHoverController implements IEditorContribution {
private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {
// const this._editor.getConfiguration().contribInfo.hover.sticky;
let targetType = mouseEvent.target.type;
const hasStopKey = (platform.isMacintosh ? mouseEvent.event.metaKey : mouseEvent.event.ctrlKey);
if (this._isMouseDown && this._hoverClicked && this.contentWidget.isColorPickerVisible()) {
return;
}
if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID && !hasStopKey) {
if (this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID) {
// mouse moved on top of content hover widget
return;
}
if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID && !hasStopKey) {
if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) {
// mouse moved on top of overlay hover widget
return;
}
@@ -174,13 +174,13 @@ export class ModesHoverController implements IEditorContribution {
if (targetType === MouseTargetType.CONTENT_TEXT) {
this.glyphWidget.hide();
if (this._isHoverEnabled) {
if (this._isHoverEnabled && mouseEvent.target.range) {
this.contentWidget.startShowingAt(mouseEvent.target.range, HoverStartMode.Delayed, false);
}
} else if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN) {
this.contentWidget.hide();
if (this._isHoverEnabled) {
if (this._isHoverEnabled && mouseEvent.target.position) {
this.glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber);
}
} else {
@@ -206,7 +206,7 @@ export class ModesHoverController implements IEditorContribution {
private _createHoverWidget() {
const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService);
this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._themeService);
this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService, this._openerService);
this._glyphWidget = new ModesGlyphHoverWidget(this._editor, renderer);
}
@@ -224,11 +224,9 @@ export class ModesHoverController implements IEditorContribution {
if (this._glyphWidget) {
this._glyphWidget.dispose();
this._glyphWidget = null;
}
if (this._contentWidget) {
this._contentWidget.dispose();
this._contentWidget = null;
}
}
}
@@ -256,6 +254,9 @@ class ShowHoverAction extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
if (!editor.hasModel()) {
return;
}
let controller = ModesHoverController.get(editor);
if (!controller) {
return;
@@ -283,6 +284,8 @@ registerThemingParticipant((theme, collector) => {
if (hoverBorder) {
collector.addRule(`.monaco-editor .monaco-editor-hover { border: 1px solid ${hoverBorder}; }`);
collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
collector.addRule(`.monaco-editor .monaco-editor-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
collector.addRule(`.monaco-editor .monaco-editor-hover hr { border-bottom: 0px solid ${hoverBorder.transparent(0.5)}; }`);
}
const link = theme.getColor(textLinkForeground);
if (link) {

View File

@@ -84,7 +84,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent
return this._containerDomNode;
}
public showAt(position: Position, range: Range, focus: boolean): void {
public showAt(position: Position, range: Range | null, focus: boolean): void {
// Position has changed
this._showAtPosition = position;
this._showAtRange = range;

View File

@@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { DocumentColorProvider, Hover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
@@ -23,6 +23,13 @@ import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contri
import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays';
import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers';
import { basename } from 'vs/base/common/paths';
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
const $ = dom.$;
class ColorHover {
@@ -34,7 +41,15 @@ class ColorHover {
) { }
}
type HoverPart = Hover | ColorHover;
class MarkerHover {
constructor(
public readonly range: IRange,
public readonly marker: IMarker,
) { }
}
type HoverPart = MarkdownHover | ColorHover | MarkerHover;
class ModesContentComputer implements IHoverComputer<HoverPart[]> {
@@ -42,7 +57,10 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
private _result: HoverPart[];
private _range: Range | null;
constructor(editor: ICodeEditor) {
constructor(
editor: ICodeEditor,
private _markerDecorationsService: IMarkerDecorationsService
) {
this._editor = editor;
this._range = null;
}
@@ -78,6 +96,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
return [];
}
const model = this._editor.getModel();
const lineNumber = this._range.startLineNumber;
if (lineNumber > this._editor.getModel().getLineCount()) {
@@ -86,19 +105,25 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
}
const colorDetector = ColorDetector.get(this._editor);
const maxColumn = this._editor.getModel().getLineMaxColumn(lineNumber);
const maxColumn = model.getLineMaxColumn(lineNumber);
const lineDecorations = this._editor.getLineDecorations(lineNumber);
let didFindColor = false;
const result = lineDecorations.map(d => {
const hoverRange = this._range;
const result = lineDecorations.map((d): HoverPart | null => {
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
if (startColumn > this._range.startColumn || this._range.endColumn > endColumn) {
if (startColumn > hoverRange.startColumn || hoverRange.endColumn > endColumn) {
return null;
}
const range = new Range(this._range.startLineNumber, startColumn, this._range.startLineNumber, endColumn);
const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn);
const marker = this._markerDecorationsService.getMarker(model, d);
if (marker) {
return new MarkerHover(range, marker);
}
const colorData = colorDetector.getColorData(d.range.getStartPosition());
if (!didFindColor && colorData) {
@@ -111,7 +136,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
return null;
}
let contents: IMarkdownString[];
let contents: IMarkdownString[] = [];
if (d.options.hoverMessage) {
if (Array.isArray(d.options.hoverMessage)) {
@@ -125,7 +150,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
}
});
return result.filter(d => !!d);
return coalesce(result);
}
onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void {
@@ -154,7 +179,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
private _getLoadingMessage(): HoverPart {
return {
range: this._range,
range: this._range || undefined,
contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]
};
}
@@ -179,13 +204,15 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
constructor(
editor: ICodeEditor,
markdownRenderer: MarkdownRenderer,
private readonly _themeService: IThemeService
markerDecorationsService: IMarkerDecorationsService,
private readonly _themeService: IThemeService,
private readonly _openerService: IOpenerService | null = NullOpenerService,
) {
super(ModesContentHoverWidget.ID, editor);
this._messages = [];
this._lastRange = null;
this._computer = new ModesContentComputer(this._editor);
this._computer = new ModesContentComputer(this._editor, markerDecorationsService);
this._highlightDecorations = [];
this._isChangingDecorations = false;
@@ -248,14 +275,14 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
// The range might have changed, but the hover is visible
// Instead of hiding it completely, filter out messages that are still in the new range and
// kick off a new computation
if (this._showAtPosition.lineNumber !== range.startLineNumber) {
if (!this._showAtPosition || this._showAtPosition.lineNumber !== range.startLineNumber) {
this.hide();
} else {
let filteredMessages: HoverPart[] = [];
for (let i = 0, len = this._messages.length; i < len; i++) {
const msg = this._messages[i];
const rng = msg.range;
if (rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
if (rng && rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
filteredMessages.push(msg);
}
}
@@ -311,7 +338,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
// update column from which to show
let renderColumn = Number.MAX_VALUE;
let highlightRange = Range.lift(messages[0].range);
let highlightRange: Range | null = messages[0].range ? Range.lift(messages[0].range) : null;
let fragment = document.createDocumentFragment();
let isEmptyHoverContent = true;
@@ -323,24 +350,19 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
}
renderColumn = Math.min(renderColumn, msg.range.startColumn);
highlightRange = Range.plusRange(highlightRange, msg.range);
highlightRange = highlightRange ? Range.plusRange(highlightRange, msg.range) : Range.lift(msg.range);
if (!(msg instanceof ColorHover)) {
msg.contents
.filter(contents => !isEmptyMarkdownString(contents))
.forEach(contents => {
const renderedContents = this._markdownRenderer.render(contents);
markdownDisposeable = renderedContents;
fragment.appendChild($('div.hover-row', null, renderedContents.element));
isEmptyHoverContent = false;
});
} else {
if (msg instanceof ColorHover) {
containColorPicker = true;
const { red, green, blue, alpha } = msg.color;
const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
const color = new Color(rgba);
if (!this._editor.hasModel()) {
return;
}
const editorModel = this._editor.getModel();
let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
let colorInfo = { range: msg.range, color: msg.color };
@@ -350,7 +372,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio, this._themeService);
getColorPresentations(editorModel, colorInfo, msg.provider, CancellationToken.None).then(colorPresentations => {
model.colorPresentations = colorPresentations;
model.colorPresentations = colorPresentations || [];
if (!this._editor.hasModel()) {
// gone...
return;
}
const originalText = this._editor.getModel().getValueInRange(msg.range);
model.guessColorPresentation(color, originalText);
@@ -393,7 +419,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
alpha: color.rgba.a
}
}, msg.provider, CancellationToken.None).then((colorPresentations) => {
model.colorPresentations = colorPresentations;
model.colorPresentations = colorPresentations || [];
});
};
@@ -409,6 +435,20 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, markdownDisposeable]);
});
} else {
if (msg instanceof MarkerHover) {
isEmptyHoverContent = false;
fragment.appendChild($('div.hover-row', undefined, this.renderMarkerHover(msg)));
} else {
msg.contents
.filter(contents => !isEmptyMarkdownString(contents))
.forEach(contents => {
const renderedContents = this._markdownRenderer.render(contents);
markdownDisposeable = renderedContents;
fragment.appendChild($('div.hover-row', undefined, renderedContents.element));
isEmptyHoverContent = false;
});
}
}
});
@@ -420,13 +460,50 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
}
this._isChangingDecorations = true;
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, [{
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{
range: highlightRange,
options: ModesContentHoverWidget._DECORATION_OPTIONS
}]);
}] : []);
this._isChangingDecorations = false;
}
private renderMarkerHover(markerHover: MarkerHover): HTMLElement {
const hoverElement = $('div');
const { source, message, code, relatedInformation } = markerHover.marker;
const messageElement = dom.append(hoverElement, $('span'));
messageElement.style.whiteSpace = 'pre-wrap';
messageElement.innerText = message.trim();
this._editor.applyFontInfo(messageElement);
if (source || code) {
const detailsElement = dom.append(hoverElement, $('span'));
detailsElement.style.opacity = '0.6';
detailsElement.style.paddingLeft = '6px';
detailsElement.innerText = source && code ? `${source}(${code})` : `(${code})`;
}
if (isNonEmptyArray(relatedInformation)) {
const listElement = dom.append(hoverElement, $('ul'));
for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
const item = dom.append(listElement, $('li'));
const a = dom.append(item, $('a'));
a.innerText = `${basename(resource.path)}(${startLineNumber}, ${startColumn})`;
a.style.cursor = 'pointer';
a.onclick = e => {
e.stopPropagation();
e.preventDefault();
if (this._openerService) {
this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError);
}
};
const messageElement = dom.append<HTMLAnchorElement>(item, $('span'));
messageElement.innerText = `: ${message}`;
}
}
return hoverElement;
}
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
className: 'hoverHighlight'
});
@@ -439,10 +516,13 @@ function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean {
for (let i = 0; i < first.length; i++) {
const firstElement = first[i];
const secondElement = second[i];
if (firstElement instanceof ColorHover) {
if (firstElement instanceof MarkerHover && secondElement instanceof MarkerHover) {
return IMarkerData.makeKey(firstElement.marker) === IMarkerData.makeKey(secondElement.marker);
}
if (firstElement instanceof ColorHover || secondElement instanceof ColorHover) {
return false;
}
if (secondElement instanceof ColorHover) {
if (firstElement instanceof MarkerHover || secondElement instanceof MarkerHover) {
return false;
}
if (!markedStringsEquals(firstElement.contents, secondElement.contents)) {

View File

@@ -43,28 +43,25 @@ class MarginComputer implements IHoverComputer<IHoverMessage[]> {
};
};
let lineDecorations = this._editor.getLineDecorations(this._lineNumber);
const lineDecorations = this._editor.getLineDecorations(this._lineNumber);
let result: IHoverMessage[] = [];
const result: IHoverMessage[] = [];
if (!lineDecorations) {
return result;
}
for (let i = 0, len = lineDecorations.length; i < len; i++) {
let d = lineDecorations[i];
for (const d of lineDecorations) {
if (!d.options.glyphMarginClassName) {
continue;
}
const hoverMessage = d.options.glyphMarginHoverMessage;
if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
continue;
}
if (Array.isArray(hoverMessage)) {
result = result.concat(hoverMessage.map(toHoverMessage));
result.push(...hoverMessage.map(toHoverMessage));
} else {
result.push(toHoverMessage(hoverMessage));
}

View File

@@ -76,7 +76,7 @@ class InPlaceReplaceController implements IEditorContribution {
const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
const modelURI = model.uri;
if (!this.editorWorkerService.canNavigateValueSet(modelURI)) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
this.currentRequest = createCancelablePromise(token => this.editorWorkerService.navigateValueSet(modelURI, selection!, up));
@@ -152,7 +152,7 @@ class InPlaceReplaceUp extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
const controller = InPlaceReplaceController.get(editor);
if (!controller) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
return controller.run(this.id, true);
}
@@ -177,7 +177,7 @@ class InPlaceReplaceDown extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> | undefined {
const controller = InPlaceReplaceController.get(editor);
if (!controller) {
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
return controller.run(this.id, false);
}

View File

@@ -461,7 +461,7 @@ export class AutoIndentOnPaste implements IEditorContribution {
}
// no model
if (!this.editor.getModel()) {
if (!this.editor.hasModel()) {
return;
}

View File

@@ -38,11 +38,11 @@ abstract class AbstractCopyLinesAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
let commands: ICommand[] = [];
let selections = editor.getSelections() || [];
const commands: ICommand[] = [];
const selections = editor.getSelections() || [];
for (let i = 0; i < selections.length; i++) {
commands.push(new CopyLinesCommand(selections[i], this.down));
for (const selection of selections) {
commands.push(new CopyLinesCommand(selection, this.down));
}
editor.pushUndoStop();
@@ -114,8 +114,8 @@ abstract class AbstractMoveLinesAction extends EditorAction {
let selections = editor.getSelections() || [];
let autoIndent = editor.getConfiguration().autoIndent;
for (let i = 0; i < selections.length; i++) {
commands.push(new MoveLinesCommand(selections[i], this.down, autoIndent));
for (const selection of selections) {
commands.push(new MoveLinesCommand(selection, this.down, autoIndent));
}
editor.pushUndoStop();
@@ -181,8 +181,7 @@ export abstract class AbstractSortLinesAction extends EditorAction {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
const selections = editor.getSelections() || [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
for (const selection of selections) {
if (!SortLinesCommand.canRun(editor.getModel(), selection, this.descending)) {
return;
}

View File

@@ -21,18 +21,18 @@ suite('Editor Contrib - Line Operations', () => {
'omicron',
'beta',
'alpha'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let sortLinesAscendingAction = new SortLinesAscendingAction();
editor.setSelection(new Selection(1, 1, 3, 5));
sortLinesAscendingAction.run(null, editor);
sortLinesAscendingAction.run(null!, editor);
assert.deepEqual(model.getLinesContent(), [
'alpha',
'beta',
'omicron'
]);
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 3, 7).toString());
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 3, 7).toString());
});
});
@@ -46,12 +46,12 @@ suite('Editor Contrib - Line Operations', () => {
'omicron',
'beta',
'alpha'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let sortLinesAscendingAction = new SortLinesAscendingAction();
editor.setSelections([new Selection(1, 1, 3, 5), new Selection(5, 1, 7, 5)]);
sortLinesAscendingAction.run(null, editor);
sortLinesAscendingAction.run(null!, editor);
assert.deepEqual(model.getLinesContent(), [
'alpha',
'beta',
@@ -65,7 +65,7 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(1, 1, 3, 7),
new Selection(5, 1, 7, 7)
];
editor.getSelections().forEach((actualSelection, index) => {
editor.getSelections()!.forEach((actualSelection, index) => {
assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString());
});
});
@@ -79,18 +79,18 @@ suite('Editor Contrib - Line Operations', () => {
'alpha',
'beta',
'omicron'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let sortLinesDescendingAction = new SortLinesDescendingAction();
editor.setSelection(new Selection(1, 1, 3, 7));
sortLinesDescendingAction.run(null, editor);
sortLinesDescendingAction.run(null!, editor);
assert.deepEqual(model.getLinesContent(), [
'omicron',
'beta',
'alpha'
]);
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 3, 5).toString());
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 3, 5).toString());
});
});
@@ -104,12 +104,12 @@ suite('Editor Contrib - Line Operations', () => {
'alpha',
'beta',
'omicron'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let sortLinesDescendingAction = new SortLinesDescendingAction();
editor.setSelections([new Selection(1, 1, 3, 7), new Selection(5, 1, 7, 7)]);
sortLinesDescendingAction.run(null, editor);
sortLinesDescendingAction.run(null!, editor);
assert.deepEqual(model.getLinesContent(), [
'omicron',
'beta',
@@ -123,7 +123,7 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(1, 1, 3, 5),
new Selection(5, 1, 7, 5)
];
editor.getSelections().forEach((actualSelection, index) => {
editor.getSelections()!.forEach((actualSelection, index) => {
assert.deepEqual(actualSelection.toString(), expectedSelections[index].toString());
});
});
@@ -138,16 +138,16 @@ suite('Editor Contrib - Line Operations', () => {
'one',
'two',
'three'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelection(new Selection(1, 2, 1, 2));
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'ne', '001');
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(3, 2, 3, 2)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(2), 'wo', '002');
assert.equal(model.getLineContent(3), 'hree', '003');
});
@@ -159,21 +159,21 @@ suite('Editor Contrib - Line Operations', () => {
'one',
'two',
'three'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelection(new Selection(2, 1, 2, 1));
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'onetwo', '001');
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLinesContent()[0], 'onetwothree');
assert.equal(model.getLinesContent().length, 1);
editor.setSelection(new Selection(1, 1, 1, 1));
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLinesContent()[0], 'onetwothree');
});
});
@@ -187,8 +187,8 @@ suite('Editor Contrib - Line Operations', () => {
'my wife doesnt believe in me',
'nonononono',
'bitconneeeect'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let deleteAllLeftAction = new DeleteAllLeftAction();
const beforeSecondWasoSelection = new Selection(3, 5, 3, 5);
@@ -198,7 +198,7 @@ suite('Editor Contrib - Line Operations', () => {
editor.setSelections([beforeSecondWasoSelection, endOfBCCSelection, endOfNonono]);
let selections;
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
selections = editor.getSelections();
assert.equal(model.getLineContent(2), '');
@@ -226,7 +226,7 @@ suite('Editor Contrib - Line Operations', () => {
selections[2].endColumn
], [5, 1, 5, 1]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
selections = editor.getSelections();
assert.equal(model.getLineContent(1), 'hi my name is Carlos Matos waso waso');
@@ -259,28 +259,28 @@ suite('Editor Contrib - Line Operations', () => {
'hola',
'world',
'hello world',
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelections([new Selection(1, 2, 1, 2), new Selection(1, 4, 1, 4)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'lo', '001');
editor.setSelections([new Selection(2, 2, 2, 2), new Selection(2, 4, 2, 5)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(2), 'ord', '002');
editor.setSelections([new Selection(3, 2, 3, 5), new Selection(3, 7, 3, 7)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(3), 'world', '003');
editor.setSelections([new Selection(4, 3, 4, 3), new Selection(4, 5, 5, 4)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(4), 'lljour', '004');
editor.setSelections([new Selection(5, 3, 6, 3), new Selection(6, 5, 7, 5), new Selection(7, 7, 7, 7)]);
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(5), 'horlworld', '005');
});
});
@@ -291,8 +291,8 @@ suite('Editor Contrib - Line Operations', () => {
'one',
'two',
'three'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let deleteAllLeftAction = new DeleteAllLeftAction();
editor.setSelection(new Selection(1, 1, 1, 1));
@@ -301,7 +301,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'Typing some text here on line one');
assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31));
deleteAllLeftAction.run(null, editor);
deleteAllLeftAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'one');
assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1));
@@ -327,34 +327,34 @@ suite('Editor Contrib - Line Operations', () => {
'',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let joinLinesAction = new JoinLinesAction();
editor.setSelection(new Selection(1, 2, 1, 2));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello world', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 6, 1, 6).toString(), '002');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 6, 1, 6).toString(), '002');
editor.setSelection(new Selection(2, 2, 2, 2));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(2), 'hello world', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 7, 2, 7).toString(), '004');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 7, 2, 7).toString(), '004');
editor.setSelection(new Selection(3, 2, 3, 2));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(3), 'hello world', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 7, 3, 7).toString(), '006');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(3, 7, 3, 7).toString(), '006');
editor.setSelection(new Selection(4, 2, 5, 3));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(4), 'hello world', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 8).toString(), '008');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 2, 4, 8).toString(), '008');
editor.setSelection(new Selection(5, 1, 7, 3));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(5), 'hello world', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(5, 1, 5, 3).toString(), '010');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(5, 1, 5, 3).toString(), '010');
});
});
@@ -363,15 +363,15 @@ suite('Editor Contrib - Line Operations', () => {
[
'hello',
'world'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let joinLinesAction = new JoinLinesAction();
editor.setSelection(new Selection(2, 1, 2, 1));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello', '001');
assert.equal(model.getLineContent(2), 'world', '002');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 6, 2, 6).toString(), '003');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 6, 2, 6).toString(), '003');
});
});
@@ -389,8 +389,8 @@ suite('Editor Contrib - Line Operations', () => {
'',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let joinLinesAction = new JoinLinesAction();
editor.setSelections([
@@ -403,9 +403,9 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(10, 1, 10, 1)
]);
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world', '001');
assert.deepEqual(editor.getSelections().toString(), [
assert.deepEqual(editor.getSelections()!.toString(), [
/** primary cursor */
new Selection(3, 4, 3, 8),
new Selection(1, 6, 1, 6),
@@ -415,7 +415,7 @@ suite('Editor Contrib - Line Operations', () => {
].toString(), '002');
/** primary cursor */
assert.deepEqual(editor.getSelection().toString(), new Selection(3, 4, 3, 8).toString(), '003');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(3, 4, 3, 8).toString(), '003');
});
});
@@ -424,8 +424,8 @@ suite('Editor Contrib - Line Operations', () => {
[
'hello',
'world'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let joinLinesAction = new JoinLinesAction();
editor.setSelection(new Selection(1, 6, 1, 6));
@@ -434,7 +434,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'hello my dear');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
joinLinesAction.run(null, editor);
joinLinesAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello my dear world');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
@@ -452,34 +452,34 @@ suite('Editor Contrib - Line Operations', () => {
'',
'',
' ',
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let transposeAction = new TransposeAction();
editor.setSelection(new Selection(1, 1, 1, 1));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello world', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 2, 1, 2).toString(), '002');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 2, 1, 2).toString(), '002');
editor.setSelection(new Selection(1, 6, 1, 6));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hell oworld', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 7, 1, 7).toString(), '004');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 7, 1, 7).toString(), '004');
editor.setSelection(new Selection(1, 12, 1, 12));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hell oworl', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '006');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '006');
editor.setSelection(new Selection(3, 1, 3, 1));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(3), '', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 1, 4, 1).toString(), '008');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 1, 4, 1).toString(), '008');
editor.setSelection(new Selection(4, 2, 4, 2));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(4), ' ', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 3, 4, 3).toString(), '010');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 3, 4, 3).toString(), '010');
}
);
@@ -494,29 +494,29 @@ suite('Editor Contrib - Line Operations', () => {
'hello world',
'',
'hello world'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let transposeAction = new TransposeAction();
editor.setSelection(new Selection(1, 1, 1, 1));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(2), '', '011');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 1).toString(), '012');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 1).toString(), '012');
editor.setSelection(new Selection(3, 6, 3, 6));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(4), 'oworld', '013');
assert.deepEqual(editor.getSelection().toString(), new Selection(4, 2, 4, 2).toString(), '014');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(4, 2, 4, 2).toString(), '014');
editor.setSelection(new Selection(6, 12, 6, 12));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(7), 'd', '015');
assert.deepEqual(editor.getSelection().toString(), new Selection(7, 2, 7, 2).toString(), '016');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(7, 2, 7, 2).toString(), '016');
editor.setSelection(new Selection(8, 12, 8, 12));
transposeAction.run(null, editor);
transposeAction.run(null!, editor);
assert.equal(model.getLineContent(8), 'hello world', '019');
assert.deepEqual(editor.getSelection().toString(), new Selection(8, 12, 8, 12).toString(), '020');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(8, 12, 8, 12).toString(), '020');
}
);
});
@@ -526,40 +526,40 @@ suite('Editor Contrib - Line Operations', () => {
[
'hello world',
'öçşğü'
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let uppercaseAction = new UpperCaseAction();
let lowercaseAction = new LowerCaseAction();
editor.setSelection(new Selection(1, 1, 1, 12));
uppercaseAction.run(null, editor);
uppercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'HELLO WORLD', '001');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '002');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 12).toString(), '002');
editor.setSelection(new Selection(1, 1, 1, 12));
lowercaseAction.run(null, editor);
lowercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello world', '003');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 12).toString(), '004');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 12).toString(), '004');
editor.setSelection(new Selection(1, 3, 1, 3));
uppercaseAction.run(null, editor);
uppercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'HELLO world', '005');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 3, 1, 3).toString(), '006');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 3, 1, 3).toString(), '006');
editor.setSelection(new Selection(1, 4, 1, 4));
lowercaseAction.run(null, editor);
lowercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), 'hello world', '007');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 4, 1, 4).toString(), '008');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 4, 1, 4).toString(), '008');
editor.setSelection(new Selection(2, 1, 2, 6));
uppercaseAction.run(null, editor);
uppercaseAction.run(null!, editor);
assert.equal(model.getLineContent(2), 'ÖÇŞĞÜ', '009');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '010');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 6).toString(), '010');
editor.setSelection(new Selection(2, 1, 2, 6));
lowercaseAction.run(null, editor);
lowercaseAction.run(null!, editor);
assert.equal(model.getLineContent(2), 'öçşğü', '011');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 1, 2, 6).toString(), '012');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 1, 2, 6).toString(), '012');
}
);
@@ -567,51 +567,51 @@ suite('Editor Contrib - Line Operations', () => {
[
'',
' '
], {}, (editor, cursor) => {
let model = editor.getModel();
], {}, (editor) => {
let model = editor.getModel()!;
let uppercaseAction = new UpperCaseAction();
let lowercaseAction = new LowerCaseAction();
editor.setSelection(new Selection(1, 1, 1, 1));
uppercaseAction.run(null, editor);
uppercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), '', '013');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '014');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 1).toString(), '014');
editor.setSelection(new Selection(1, 1, 1, 1));
lowercaseAction.run(null, editor);
lowercaseAction.run(null!, editor);
assert.equal(model.getLineContent(1), '', '015');
assert.deepEqual(editor.getSelection().toString(), new Selection(1, 1, 1, 1).toString(), '016');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(1, 1, 1, 1).toString(), '016');
editor.setSelection(new Selection(2, 2, 2, 2));
uppercaseAction.run(null, editor);
uppercaseAction.run(null!, editor);
assert.equal(model.getLineContent(2), ' ', '017');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '018');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '018');
editor.setSelection(new Selection(2, 2, 2, 2));
lowercaseAction.run(null, editor);
lowercaseAction.run(null!, editor);
assert.equal(model.getLineContent(2), ' ', '019');
assert.deepEqual(editor.getSelection().toString(), new Selection(2, 2, 2, 2).toString(), '020');
assert.deepEqual(editor.getSelection()!.toString(), new Selection(2, 2, 2, 2).toString(), '020');
}
);
});
suite('DeleteAllRightAction', () => {
test('should be noop on empty', () => {
withTestCodeEditor([''], {}, (editor, cursor) => {
const model = editor.getModel();
withTestCodeEditor([''], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelection(new Selection(1, 1, 1, 1));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1)]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
});
@@ -621,22 +621,22 @@ suite('Editor Contrib - Line Operations', () => {
withTestCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 2, 1, 5));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['ho', 'world']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 2, 1, 2)]);
editor.setSelection(new Selection(1, 1, 2, 4));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['ld']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
editor.setSelection(new Selection(1, 1, 1, 3));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 1, 1, 1)]);
});
@@ -646,17 +646,17 @@ suite('Editor Contrib - Line Operations', () => {
withTestCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 3, 1, 3));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['he', 'world']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 3, 1, 3)]);
editor.setSelection(new Selection(2, 1, 2, 1));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['he', '']);
assert.deepEqual(editor.getSelections(), [new Selection(2, 1, 2, 1)]);
});
@@ -666,22 +666,22 @@ suite('Editor Contrib - Line Operations', () => {
withTestCodeEditor([
'hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['helloworld']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['hello']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
editor.setSelection(new Selection(1, 6, 1, 6));
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['hello']);
assert.deepEqual(editor.getSelections(), [new Selection(1, 6, 1, 6)]);
});
@@ -692,8 +692,8 @@ suite('Editor Contrib - Line Operations', () => {
'hello',
'there',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
editor.setSelections([
@@ -701,34 +701,34 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['he', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['hewor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6)
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['he']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3)
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['he']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3)
@@ -741,8 +741,8 @@ suite('Editor Contrib - Line Operations', () => {
'hello',
'there',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel();
], {}, (editor) => {
const model = editor.getModel()!;
const action = new DeleteAllRightAction();
editor.setSelections([
@@ -750,7 +750,7 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(model.getLinesContent(), ['hethere', 'wor']);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
@@ -783,8 +783,8 @@ suite('Editor Contrib - Line Operations', () => {
editor.setPosition(new Position(lineNumber, column));
let insertLineBeforeAction = new InsertLineBeforeAction();
insertLineBeforeAction.run(null, editor);
callback(editor.getModel(), cursor);
insertLineBeforeAction.run(null!, editor);
callback(editor.getModel()!, cursor);
});
}
@@ -824,8 +824,8 @@ suite('Editor Contrib - Line Operations', () => {
editor.setPosition(new Position(lineNumber, column));
let insertLineAfterAction = new InsertLineAfterAction();
insertLineAfterAction.run(null, editor);
callback(editor.getModel(), cursor);
insertLineAfterAction.run(null!, editor);
callback(editor.getModel()!, cursor);
});
}
@@ -865,11 +865,11 @@ suite('Editor Contrib - Line Operations', () => {
}
);
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
withTestCodeEditor(null, { model: model }, (editor) => {
let indentLinesAction = new IndentLinesAction();
editor.setPosition(new Position(1, 2));
indentLinesAction.run(null, editor);
indentLinesAction.run(null!, editor);
assert.equal(model.getLineContent(1), '\tfunction baz() {');
assert.deepEqual(editor.getSelection(), new Selection(1, 3, 1, 3));
@@ -887,14 +887,14 @@ suite('Editor Contrib - Line Operations', () => {
'too',
'c',
];
withTestCodeEditor(TEXT, {}, (editor, cursor) => {
withTestCodeEditor(TEXT, {}, (editor) => {
editor.setSelections([
new Selection(2, 4, 2, 4),
new Selection(2, 8, 2, 8),
new Selection(3, 4, 3, 4),
]);
const deleteLinesAction = new DeleteLinesAction();
deleteLinesAction.run(null, editor);
deleteLinesAction.run(null!, editor);
assert.equal(editor.getValue(), 'a\nc');
});

View File

@@ -330,7 +330,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
test('move line should still work as before if there is no indentation rules', () => {
testMoveLinesUpWithIndentCommand(
null,
null!,
[
'if (true) {',
' var task = new Task(() => {',

View File

@@ -37,7 +37,7 @@ export class Link implements ILink {
return this._link.url;
}
resolve(token: CancellationToken): Thenable<URI> {
resolve(token: CancellationToken): Promise<URI> {
if (this._link.url) {
try {
return Promise.resolve(URI.parse(this._link.url));

View File

@@ -270,8 +270,8 @@ class LinkDetector implements editorCommon.IEditorContribution {
let newDecorations: IModelDeltaDecoration[] = [];
if (links) {
// Not sure why this is sometimes null
for (let i = 0; i < links.length; i++) {
newDecorations.push(LinkOccurrence.decoration(links[i], useMetaKey));
for (const link of links) {
newDecorations.push(LinkOccurrence.decoration(link, useMetaKey));
}
}
@@ -361,8 +361,7 @@ class LinkDetector implements editorCommon.IEditorContribution {
endColumn: position.column
}, 0, true);
for (let i = 0; i < decorations.length; i++) {
const decoration = decorations[i];
for (const decoration of decorations) {
const currentOccurrence = this.currentOccurrences[decoration.id];
if (currentOccurrence) {
return currentOccurrence;

View File

@@ -77,7 +77,7 @@ export class MarkdownRenderer {
};
}
render(markdown: IMarkdownString): IMarkdownRenderResult {
render(markdown: IMarkdownString | undefined): IMarkdownRenderResult {
let disposeables: IDisposable[] = [];
let element: HTMLElement;

View File

@@ -54,6 +54,10 @@ export class InsertCursorAbove extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
if (!editor.hasModel()) {
return;
}
const useLogicalLine = (args && args.logicalLine === true);
const cursors = editor._getCursors();
const context = cursors.context;
@@ -99,6 +103,10 @@ export class InsertCursorBelow extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
if (!editor.hasModel()) {
return;
}
const useLogicalLine = (args && args.logicalLine === true);
const cursors = editor._getCursors();
const context = cursors.context;
@@ -154,6 +162,10 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
if (!editor.hasModel()) {
return;
}
const model = editor.getModel();
const selections = editor.getSelections();
let newSelections: Selection[] = [];
@@ -177,10 +189,14 @@ class InsertCursorAtEndOfLineSelected extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
if (!editor.hasModel()) {
return;
}
const selections = editor.getSelections();
const lineCount = editor.getModel().getLineCount();
let newSelections = [];
let newSelections: Selection[] = [];
for (let i = selections[0].startLineNumber; i <= lineCount; i++) {
newSelections.push(new Selection(i, selections[0].startColumn, i, selections[0].endColumn));
}
@@ -203,9 +219,13 @@ class InsertCursorAtTopOfLineSelected extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
if (!editor.hasModel()) {
return;
}
const selections = editor.getSelections();
let newSelections = [];
let newSelections: Selection[] = [];
for (let i = selections[0].startLineNumber; i >= 1; i--) {
newSelections.push(new Selection(i, selections[0].startColumn, i, selections[0].endColumn));
}
@@ -226,7 +246,10 @@ export class MultiCursorSessionResult {
export class MultiCursorSession {
public static create(editor: ICodeEditor, findController: CommonFindController): MultiCursorSession {
public static create(editor: ICodeEditor, findController: CommonFindController): MultiCursorSession | null {
if (!editor.hasModel()) {
return null;
}
const findState = findController.getState();
// Find widget owns entirely what we search for if:
@@ -281,10 +304,14 @@ export class MultiCursorSession {
public readonly searchText: string,
public readonly wholeWord: boolean,
public readonly matchCase: boolean,
public currentMatch: Selection
public currentMatch: Selection | null
) { }
public addSelectionToNextFindMatch(): MultiCursorSessionResult {
public addSelectionToNextFindMatch(): MultiCursorSessionResult | null {
if (!this._editor.hasModel()) {
return null;
}
const nextMatch = this._getNextMatch();
if (!nextMatch) {
return null;
@@ -294,7 +321,11 @@ export class MultiCursorSession {
return new MultiCursorSessionResult(allSelections.concat(nextMatch), nextMatch, ScrollType.Smooth);
}
public moveSelectionToNextFindMatch(): MultiCursorSessionResult {
public moveSelectionToNextFindMatch(): MultiCursorSessionResult | null {
if (!this._editor.hasModel()) {
return null;
}
const nextMatch = this._getNextMatch();
if (!nextMatch) {
return null;
@@ -304,7 +335,11 @@ export class MultiCursorSession {
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(nextMatch), nextMatch, ScrollType.Smooth);
}
private _getNextMatch(): Selection {
private _getNextMatch(): Selection | null {
if (!this._editor.hasModel()) {
return null;
}
if (this.currentMatch) {
const result = this.currentMatch;
this.currentMatch = null;
@@ -323,7 +358,11 @@ export class MultiCursorSession {
return new Selection(nextMatch.range.startLineNumber, nextMatch.range.startColumn, nextMatch.range.endLineNumber, nextMatch.range.endColumn);
}
public addSelectionToPreviousFindMatch(): MultiCursorSessionResult {
public addSelectionToPreviousFindMatch(): MultiCursorSessionResult | null {
if (!this._editor.hasModel()) {
return null;
}
const previousMatch = this._getPreviousMatch();
if (!previousMatch) {
return null;
@@ -333,7 +372,11 @@ export class MultiCursorSession {
return new MultiCursorSessionResult(allSelections.concat(previousMatch), previousMatch, ScrollType.Smooth);
}
public moveSelectionToPreviousFindMatch(): MultiCursorSessionResult {
public moveSelectionToPreviousFindMatch(): MultiCursorSessionResult | null {
if (!this._editor.hasModel()) {
return null;
}
const previousMatch = this._getPreviousMatch();
if (!previousMatch) {
return null;
@@ -343,7 +386,11 @@ export class MultiCursorSession {
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(previousMatch), previousMatch, ScrollType.Smooth);
}
private _getPreviousMatch(): Selection {
private _getPreviousMatch(): Selection | null {
if (!this._editor.hasModel()) {
return null;
}
if (this.currentMatch) {
const result = this.currentMatch;
this.currentMatch = null;
@@ -363,6 +410,10 @@ export class MultiCursorSession {
}
public selectAll(): FindMatch[] {
if (!this._editor.hasModel()) {
return [];
}
this.findController.highlightFindOptions();
return this._editor.getModel().findMatches(this.searchText, true, false, this.matchCase, this.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false, Constants.MAX_SAFE_SMALL_INTEGER);
@@ -375,7 +426,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
private readonly _editor: ICodeEditor;
private _ignoreSelectionChange: boolean;
private _session: MultiCursorSession;
private _session: MultiCursorSession | null;
private _sessionDispose: IDisposable[];
public static get(editor: ICodeEditor): MultiCursorSelectionController {
@@ -466,7 +517,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
return new Selection(selection.startLineNumber, word.startColumn, selection.startLineNumber, word.endColumn);
}
private _applySessionResult(result: MultiCursorSessionResult): void {
private _applySessionResult(result: MultiCursorSessionResult | null): void {
if (!result) {
return;
}
@@ -476,11 +527,14 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
}
}
public getSession(findController: CommonFindController): MultiCursorSession {
public getSession(findController: CommonFindController): MultiCursorSession | null {
return this._session;
}
public addSelectionToNextFindMatch(findController: CommonFindController): void {
if (!this._editor.hasModel()) {
return;
}
if (!this._session) {
// If there are multiple cursors, handle the case where they do not all select the same text.
const allSelections = this._editor.getSelections();
@@ -527,6 +581,10 @@ export class MultiCursorSelectionController extends Disposable implements IEdito
}
public selectAll(findController: CommonFindController): void {
if (!this._editor.hasModel()) {
return;
}
let matches: FindMatch[] | null = null;
const findState = findController.getState();
@@ -578,7 +636,7 @@ export abstract class MultiCursorSelectionControllerAction extends EditorAction
}
const findController = CommonFindController.get(editor);
if (!findController) {
return null;
return;
}
this._run(multiCursorController, findController);
}
@@ -715,9 +773,9 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction {
class SelectionHighlighterState {
public readonly searchText: string;
public readonly matchCase: boolean;
public readonly wordSeparators: string;
public readonly wordSeparators: string | null;
constructor(searchText: string, matchCase: boolean, wordSeparators: string) {
constructor(searchText: string, matchCase: boolean, wordSeparators: string | null) {
this.searchText = searchText;
this.matchCase = matchCase;
this.wordSeparators = wordSeparators;
@@ -726,7 +784,7 @@ class SelectionHighlighterState {
/**
* Everything equals except for `lastWordUnderCursor`
*/
public static softEquals(a: SelectionHighlighterState, b: SelectionHighlighterState): boolean {
public static softEquals(a: SelectionHighlighterState | null, b: SelectionHighlighterState | null): boolean {
if (!a && !b) {
return true;
}
@@ -748,7 +806,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
private _isEnabled: boolean;
private decorations: string[];
private updateSoon: RunOnceScheduler;
private state: SelectionHighlighterState;
private state: SelectionHighlighterState | null;
constructor(editor: ICodeEditor) {
super();
@@ -800,12 +858,11 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
this._setState(SelectionHighlighter._createState(this._isEnabled, this.editor));
}
private static _createState(isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState {
private static _createState(isEnabled: boolean, editor: ICodeEditor): SelectionHighlighterState | null {
if (!isEnabled) {
return null;
}
const model = editor.getModel();
if (!model) {
if (!editor.hasModel()) {
return null;
}
const s = editor.getSelection();
@@ -877,7 +934,7 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
return new SelectionHighlighterState(r.searchText, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null);
}
private _setState(state: SelectionHighlighterState): void {
private _setState(state: SelectionHighlighterState | null): void {
if (SelectionHighlighterState.softEquals(this.state, state)) {
this.state = state;
return;
@@ -889,6 +946,10 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut
return;
}
if (!this.editor.hasModel()) {
return;
}
const model = this.editor.getModel();
if (model.isTooLargeForTokenization()) {
// the file is too large, so searching word under cursor in the whole document takes is blocking the UI.

View File

@@ -24,7 +24,7 @@ suite('Multicursor', () => {
let addCursorUpAction = new InsertCursorAbove();
editor.setSelection(new Selection(2, 1, 2, 1));
addCursorUpAction.run(null, editor, {});
addCursorUpAction.run(null!, editor, {});
assert.equal(cursor.getSelections().length, 2);
editor.trigger('test', Handler.Paste, {
@@ -35,8 +35,8 @@ suite('Multicursor', () => {
]
});
// cursorCommand(cursor, H.Paste, { text: '1\n2' });
assert.equal(editor.getModel().getLineContent(1), '1abc');
assert.equal(editor.getModel().getLineContent(2), '2def');
assert.equal(editor.getModel()!.getLineContent(1), '1abc');
assert.equal(editor.getModel()!.getLineContent(2), '2def');
});
});
@@ -45,7 +45,7 @@ suite('Multicursor', () => {
'abc'
], {}, (editor, cursor) => {
let addCursorDownAction = new InsertCursorBelow();
addCursorDownAction.run(null, editor, {});
addCursorDownAction.run(null!, editor, {});
assert.equal(cursor.getSelections().length, 1);
});
});
@@ -65,9 +65,9 @@ suite('Multicursor selection', () => {
onWillSaveState: Event.None,
get: (key: string) => queryState[key],
getBoolean: (key: string) => !!queryState[key],
getInteger: (key: string) => undefined,
getInteger: (key: string) => undefined!,
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
remove: (key) => void 0
remove: (key) => undefined
} as IStorageService);
test('issue #8817: Cursor position changes when you cancel multicursor', () => {
@@ -83,8 +83,8 @@ suite('Multicursor selection', () => {
editor.setSelection(new Selection(2, 9, 2, 16));
selectHighlightsAction.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
selectHighlightsAction.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[2, 9, 2, 16],
[1, 9, 1, 16],
[3, 9, 3, 16],
@@ -92,7 +92,7 @@ suite('Multicursor selection', () => {
editor.trigger('test', 'removeSecondaryCursors', null);
assert.deepEqual(fromRange(editor.getSelection()), [2, 9, 2, 16]);
assert.deepEqual(fromRange(editor.getSelection()!), [2, 9, 2, 16]);
multiCursorSelectController.dispose();
findController.dispose();
@@ -114,8 +114,8 @@ suite('Multicursor selection', () => {
editor.setSelection(new Selection(1, 1, 1, 1));
findController.getState().change({ searchString: 'some+thing', isRegex: true, isRevealed: true }, false);
selectHighlightsAction.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
selectHighlightsAction.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[1, 1, 1, 10],
[2, 1, 2, 11],
[3, 1, 3, 12],
@@ -147,15 +147,15 @@ suite('Multicursor selection', () => {
editor.setSelection(new Selection(2, 1, 3, 4));
addSelectionToNextFindMatch.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
addSelectionToNextFindMatch.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[2, 1, 3, 4],
[8, 1, 9, 4]
]);
editor.trigger('test', 'removeSecondaryCursors', null);
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
multiCursorSelectController.dispose();
findController.dispose();
@@ -175,16 +175,16 @@ suite('Multicursor selection', () => {
editor.setSelection(new Selection(1, 1, 1, 4));
addSelectionToNextFindMatch.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
addSelectionToNextFindMatch.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[1, 1, 1, 4],
[1, 4, 1, 7]
]);
addSelectionToNextFindMatch.run(null, editor);
addSelectionToNextFindMatch.run(null, editor);
addSelectionToNextFindMatch.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
addSelectionToNextFindMatch.run(null!, editor);
addSelectionToNextFindMatch.run(null!, editor);
addSelectionToNextFindMatch.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[1, 1, 1, 4],
[1, 4, 1, 7],
[2, 1, 2, 4],
@@ -193,7 +193,7 @@ suite('Multicursor selection', () => {
]);
editor.trigger('test', Handler.Type, { text: 'z' });
assert.deepEqual(editor.getSelections().map(fromRange), [
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[1, 2, 1, 2],
[1, 3, 1, 3],
[2, 2, 2, 2],
@@ -224,7 +224,7 @@ suite('Multicursor selection', () => {
'rty'
], { serviceCollection: serviceCollection }, (editor, cursor) => {
editor.getModel().setEOL(EndOfLineSequence.CRLF);
editor.getModel()!.setEOL(EndOfLineSequence.CRLF);
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
@@ -232,15 +232,15 @@ suite('Multicursor selection', () => {
editor.setSelection(new Selection(2, 1, 3, 4));
addSelectionToNextFindMatch.run(null, editor);
assert.deepEqual(editor.getSelections().map(fromRange), [
addSelectionToNextFindMatch.run(null!, editor);
assert.deepEqual(editor.getSelections()!.map(fromRange), [
[2, 1, 3, 4],
[8, 1, 9, 4]
]);
editor.trigger('test', 'removeSecondaryCursors', null);
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
assert.deepEqual(fromRange(editor.getSelection()!), [2, 1, 3, 4]);
multiCursorSelectController.dispose();
findController.dispose();
@@ -277,25 +277,25 @@ suite('Multicursor selection', () => {
new Selection(1, 2, 1, 2),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
new Selection(3, 1, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
@@ -316,20 +316,20 @@ suite('Multicursor selection', () => {
new Selection(2, 2, 2, 2),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
new Selection(3, 1, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
@@ -350,20 +350,20 @@ suite('Multicursor selection', () => {
new Selection(2, 1, 2, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
new Selection(3, 1, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
@@ -385,14 +385,14 @@ suite('Multicursor selection', () => {
new Selection(3, 1, 3, 1),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
new Selection(3, 1, 3, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
@@ -414,14 +414,14 @@ suite('Multicursor selection', () => {
new Selection(3, 6, 3, 6),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 5, 1, 10),
new Selection(2, 5, 2, 10),
new Selection(3, 5, 3, 8),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 5, 1, 10),
new Selection(2, 5, 2, 10),
@@ -443,20 +443,20 @@ suite('Multicursor selection', () => {
new Selection(1, 1, 1, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(2, 1, 2, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(2, 1, 2, 5),
new Selection(3, 1, 3, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(2, 1, 2, 5),
@@ -464,7 +464,7 @@ suite('Multicursor selection', () => {
new Selection(4, 1, 4, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(2, 1, 2, 5),
@@ -473,7 +473,7 @@ suite('Multicursor selection', () => {
new Selection(5, 1, 5, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 5),
new Selection(2, 1, 2, 5),
@@ -501,18 +501,18 @@ suite('Multicursor selection', () => {
new Selection(1, 2, 1, 2),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(4, 1, 4, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(4, 1, 4, 4),
@@ -527,12 +527,12 @@ suite('Multicursor selection', () => {
new Selection(1, 2, 1, 2),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(4, 1, 4, 4),
@@ -543,7 +543,7 @@ suite('Multicursor selection', () => {
new Selection(1, 1, 1, 4),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(2, 1, 2, 4),
@@ -558,14 +558,14 @@ suite('Multicursor selection', () => {
new Selection(1, 2, 1, 2),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(4, 1, 4, 4),
new Selection(6, 2, 6, 5),
]);
action.run(null, editor);
action.run(null!, editor);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 1, 1, 4),
new Selection(4, 1, 4, 4),

View File

@@ -12,10 +12,11 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ParameterHintsWidget, TriggerContext } from './parameterHintsWidget';
import { ParameterHintsWidget } from './parameterHintsWidget';
import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import * as modes from 'vs/editor/common/modes';
import { TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
class ParameterHintsController implements IEditorContribution {
@@ -97,7 +98,7 @@ registerEditorCommand(new ParameterHintsCommand({
handler: x => x.cancel(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.editorTextFocus,
kbExpr: EditorContextKeys.focus,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape]
}
@@ -108,7 +109,7 @@ registerEditorCommand(new ParameterHintsCommand({
handler: x => x.previous(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.editorTextFocus,
kbExpr: EditorContextKeys.focus,
primary: KeyCode.UpArrow,
secondary: [KeyMod.Alt | KeyCode.UpArrow],
mac: { primary: KeyCode.UpArrow, secondary: [KeyMod.Alt | KeyCode.UpArrow, KeyMod.WinCtrl | KeyCode.KEY_P] }
@@ -120,7 +121,7 @@ registerEditorCommand(new ParameterHintsCommand({
handler: x => x.next(),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.editorTextFocus,
kbExpr: EditorContextKeys.focus,
primary: KeyCode.DownArrow,
secondary: [KeyMod.Alt | KeyCode.DownArrow],
mac: { primary: KeyCode.DownArrow, secondary: [KeyMod.Alt | KeyCode.DownArrow, KeyMod.WinCtrl | KeyCode.KEY_N] }

View File

@@ -0,0 +1,269 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import * as modes from 'vs/editor/common/modes';
import { provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
export interface TriggerContext {
readonly triggerKind: modes.SignatureHelpTriggerKind;
readonly triggerCharacter?: string;
}
namespace ParameterHintState {
export const enum Type {
Default,
Active,
Pending,
}
export const Default = new class { readonly type = Type.Default; };
export const Pending = new class { readonly type = Type.Pending; };
export class Active {
readonly type = Type.Active;
constructor(
readonly hints: modes.SignatureHelp
) { }
}
export type State = typeof Default | typeof Pending | Active;
}
export class ParameterHintsModel extends Disposable {
private static readonly DEFAULT_DELAY = 120; // ms
private readonly _onChangedHints = this._register(new Emitter<modes.SignatureHelp | undefined>());
public readonly onChangedHints = this._onChangedHints.event;
private editor: ICodeEditor;
private enabled: boolean;
private state: ParameterHintState.State = ParameterHintState.Default;
private triggerChars = new CharacterSet();
private retriggerChars = new CharacterSet();
private throttledDelayer: Delayer<boolean>;
private provideSignatureHelpRequest?: CancelablePromise<any>;
private triggerId = 0;
constructor(
editor: ICodeEditor,
delay: number = ParameterHintsModel.DEFAULT_DELAY
) {
super();
this.editor = editor;
this.enabled = false;
this.throttledDelayer = new Delayer(delay);
this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
this._register(this.editor.onDidType(text => this.onDidType(text)));
this.onEditorConfigurationChange();
this.onModelChanged();
}
cancel(silent: boolean = false): void {
this.state = ParameterHintState.Default;
this.throttledDelayer.cancel();
if (!silent) {
this._onChangedHints.fire(undefined);
}
if (this.provideSignatureHelpRequest) {
this.provideSignatureHelpRequest.cancel();
this.provideSignatureHelpRequest = undefined;
}
}
trigger(context: TriggerContext, delay?: number): void {
const model = this.editor.getModel();
if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) {
return;
}
const triggerId = ++this.triggerId;
this.throttledDelayer.trigger(
() => this.doTrigger({
triggerKind: context.triggerKind,
triggerCharacter: context.triggerCharacter,
isRetrigger: this.state.type === ParameterHintState.Type.Active || this.state.type === ParameterHintState.Type.Pending,
activeSignatureHelp: this.state.type === ParameterHintState.Type.Active ? this.state.hints : undefined
}, triggerId), delay).then(undefined, onUnexpectedError);
}
public next(): void {
if (this.state.type !== ParameterHintState.Type.Active) {
return;
}
const length = this.state.hints.signatures.length;
const activeSignature = this.state.hints.activeSignature;
const last = (activeSignature % length) === (length - 1);
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on last signature of list
if ((length < 2 || last) && !cycle) {
this.cancel();
return;
}
this.updateActiveSignature(last && cycle ? 0 : activeSignature + 1);
}
public previous(): void {
if (this.state.type !== ParameterHintState.Type.Active) {
return;
}
const length = this.state.hints.signatures.length;
const activeSignature = this.state.hints.activeSignature;
const first = activeSignature === 0;
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on first signature of list
if ((length < 2 || first) && !cycle) {
this.cancel();
return;
}
this.updateActiveSignature(first && cycle ? length - 1 : activeSignature - 1);
}
private updateActiveSignature(activeSignature: number) {
if (this.state.type !== ParameterHintState.Type.Active) {
return;
}
this.state = new ParameterHintState.Active({ ...this.state.hints, activeSignature });
this._onChangedHints.fire(this.state.hints);
}
private doTrigger(triggerContext: modes.SignatureHelpContext, triggerId: number): Promise<boolean> {
this.cancel(true);
if (!this.editor.hasModel()) {
return Promise.resolve(false);
}
const model = this.editor.getModel();
const position = this.editor.getPosition();
this.state = ParameterHintState.Pending;
this.provideSignatureHelpRequest = createCancelablePromise(token =>
provideSignatureHelp(model, position, triggerContext, token));
return this.provideSignatureHelpRequest.then(result => {
// Check that we are still resolving the correct signature help
if (triggerId !== this.triggerId) {
return false;
}
if (!result || !result.signatures || result.signatures.length === 0) {
this.cancel();
return false;
} else {
this.state = new ParameterHintState.Active(result);
this._onChangedHints.fire(this.state.hints);
return true;
}
}).catch(error => {
this.state = ParameterHintState.Default;
onUnexpectedError(error);
return false;
});
}
private get isTriggered(): boolean {
return this.state.type === ParameterHintState.Type.Active
|| this.state.type === ParameterHintState.Type.Pending
|| this.throttledDelayer.isTriggered();
}
private onModelChanged(): void {
this.cancel();
// Update trigger characters
this.triggerChars = new CharacterSet();
this.retriggerChars = new CharacterSet();
const model = this.editor.getModel();
if (!model) {
return;
}
for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
for (const ch of support.signatureHelpTriggerCharacters || []) {
this.triggerChars.add(ch.charCodeAt(0));
// All trigger characters are also considered retrigger characters
this.retriggerChars.add(ch.charCodeAt(0));
}
for (const ch of support.signatureHelpRetriggerCharacters || []) {
this.retriggerChars.add(ch.charCodeAt(0));
}
}
}
private onDidType(text: string) {
if (!this.enabled) {
return;
}
const lastCharIndex = text.length - 1;
const triggerCharCode = text.charCodeAt(lastCharIndex);
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
this.trigger({
triggerKind: modes.SignatureHelpTriggerKind.TriggerCharacter,
triggerCharacter: text.charAt(lastCharIndex),
});
}
}
private onCursorChange(e: ICursorSelectionChangedEvent): void {
if (e.source === 'mouse') {
this.cancel();
} else if (this.isTriggered) {
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
}
}
private onModelContentChange(): void {
if (this.isTriggered) {
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
}
}
private onEditorConfigurationChange(): void {
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
if (!this.enabled) {
this.cancel();
}
}
dispose(): void {
this.cancel(true);
super.dispose();
}
}

View File

@@ -3,232 +3,28 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./parameterHints';
import * as nls from 'vs/nls';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import * as aria from 'vs/base/browser/ui/aria/aria';
import * as modes from 'vs/editor/common/modes';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event, Emitter, chain } from 'vs/base/common/event';
import { domEvent, stop } from 'vs/base/browser/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Context, provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
import { Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./parameterHints';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import * as modes from 'vs/editor/common/modes';
import { IModeService } from 'vs/editor/common/services/modeService';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { Context } from 'vs/editor/contrib/parameterHints/provideSignatureHelp';
import * as nls from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
const $ = dom.$;
export interface TriggerContext {
readonly triggerKind: modes.SignatureHelpTriggerKind;
readonly triggerCharacter?: string;
}
export interface IHintEvent {
hints: modes.SignatureHelp;
}
export class ParameterHintsModel extends Disposable {
private static readonly DEFAULT_DELAY = 120; // ms
private readonly _onHint = this._register(new Emitter<IHintEvent>());
public readonly onHint: Event<IHintEvent> = this._onHint.event;
private readonly _onCancel = this._register(new Emitter<void>());
public readonly onCancel: Event<void> = this._onCancel.event;
private editor: ICodeEditor;
private enabled: boolean;
private triggerCharactersListeners: IDisposable[];
private active: boolean = false;
private pending: boolean = false;
private triggerChars = new CharacterSet();
private retriggerChars = new CharacterSet();
private throttledDelayer: Delayer<boolean>;
private provideSignatureHelpRequest?: CancelablePromise<modes.SignatureHelp | null | undefined>;
constructor(
editor: ICodeEditor,
delay: number = ParameterHintsModel.DEFAULT_DELAY
) {
super();
this.editor = editor;
this.enabled = false;
this.triggerCharactersListeners = [];
this.throttledDelayer = new Delayer(delay);
this._register(this.editor.onDidChangeConfiguration(() => this.onEditorConfigurationChange()));
this._register(this.editor.onDidChangeModel(e => this.onModelChanged()));
this._register(this.editor.onDidChangeModelLanguage(_ => this.onModelChanged()));
this._register(this.editor.onDidChangeCursorSelection(e => this.onCursorChange(e)));
this._register(this.editor.onDidChangeModelContent(e => this.onModelContentChange()));
this._register(modes.SignatureHelpProviderRegistry.onDidChange(this.onModelChanged, this));
this._register(this.editor.onDidType(text => this.onDidType(text)));
this.onEditorConfigurationChange();
this.onModelChanged();
}
cancel(silent: boolean = false): void {
this.active = false;
this.pending = false;
this.throttledDelayer.cancel();
if (!silent) {
this._onCancel.fire(void 0);
}
if (this.provideSignatureHelpRequest) {
this.provideSignatureHelpRequest.cancel();
this.provideSignatureHelpRequest = undefined;
}
}
trigger(context: TriggerContext, delay?: number): void {
const model = this.editor.getModel();
if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) {
return;
}
this.throttledDelayer.trigger(
() => this.doTrigger({
triggerKind: context.triggerKind,
triggerCharacter: context.triggerCharacter,
isRetrigger: this.isTriggered,
}), delay).then(undefined, onUnexpectedError);
}
private doTrigger(triggerContext: modes.SignatureHelpContext): Promise<boolean> {
this.cancel(true);
if (!this.editor.hasModel()) {
return Promise.resolve(false);
}
const model = this.editor.getModel();
const position = this.editor.getPosition();
this.pending = true;
this.provideSignatureHelpRequest = createCancelablePromise(token =>
provideSignatureHelp(model!, position!, triggerContext, token));
return this.provideSignatureHelpRequest.then(result => {
this.pending = false;
if (!result || !result.signatures || result.signatures.length === 0) {
this.cancel();
this._onCancel.fire(void 0);
return false;
}
this.active = true;
const event: IHintEvent = { hints: result };
this._onHint.fire(event);
return true;
}).catch(error => {
this.pending = false;
onUnexpectedError(error);
return false;
});
}
private get isTriggered(): boolean {
return this.active || this.pending || this.throttledDelayer.isTriggered();
}
private onModelChanged(): void {
this.cancel();
// Update trigger characters
this.triggerChars = new CharacterSet();
this.retriggerChars = new CharacterSet();
const model = this.editor.getModel();
if (!model) {
return;
}
for (const support of modes.SignatureHelpProviderRegistry.ordered(model)) {
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
for (const ch of support.signatureHelpTriggerCharacters) {
this.triggerChars.add(ch.charCodeAt(0));
// All trigger characters are also considered retrigger characters
this.retriggerChars.add(ch.charCodeAt(0));
}
}
if (Array.isArray(support.signatureHelpRetriggerCharacters)) {
for (const ch of support.signatureHelpRetriggerCharacters) {
this.retriggerChars.add(ch.charCodeAt(0));
}
}
}
}
private onDidType(text: string) {
if (!this.enabled) {
return;
}
const lastCharIndex = text.length - 1;
const triggerCharCode = text.charCodeAt(lastCharIndex);
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
this.trigger({
triggerKind: modes.SignatureHelpTriggerKind.TriggerCharacter,
triggerCharacter: text.charAt(lastCharIndex),
});
}
}
private onCursorChange(e: ICursorSelectionChangedEvent): void {
if (e.source === 'mouse') {
this.cancel();
} else if (this.isTriggered) {
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
}
}
private onModelContentChange(): void {
if (this.isTriggered) {
this.trigger({ triggerKind: modes.SignatureHelpTriggerKind.ContentChange });
}
}
private onEditorConfigurationChange(): void {
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
if (!this.enabled) {
this.cancel();
}
}
dispose(): void {
this.cancel(true);
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
super.dispose();
}
}
export class ParameterHintsWidget implements IContentWidget, IDisposable {
private static readonly ID = 'editor.widget.parameterHintsWidget';
@@ -242,9 +38,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
private signature: HTMLElement;
private docs: HTMLElement;
private overloads: HTMLElement;
private currentSignature: number;
private visible: boolean;
private hints: modes.SignatureHelp | null;
private announcedLabel: string | null;
private scrollbar: DomScrollableElement;
private disposables: IDisposable[];
@@ -265,15 +59,13 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
this.visible = false;
this.disposables = [];
this.disposables.push(this.model.onHint(e => {
this.show();
this.hints = e.hints;
this.currentSignature = e.hints.activeSignature;
this.render();
}));
this.disposables.push(this.model.onCancel(() => {
this.hide();
this.disposables.push(this.model.onChangedHints(newParameterHints => {
if (newParameterHints) {
this.show();
this.render(newParameterHints);
} else {
this.hide();
}
}));
}
@@ -302,8 +94,6 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
this.docs = dom.append(body, $('.docs'));
this.currentSignature = 0;
this.editor.addContentWidget(this);
this.hide();
@@ -321,7 +111,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
updateFont();
chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
Event.chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
.filter(e => e.fontInfo)
.on(updateFont, null, this.disposables);
@@ -355,7 +145,6 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
this.keyVisible.reset();
this.visible = false;
this.hints = null;
this.announcedLabel = null;
dom.removeClass(this.element, 'visible');
this.editor.layoutContentWidget(this);
@@ -371,19 +160,15 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
return null;
}
private render(): void {
if (!this.hints) {
return;
}
const multiple = this.hints.signatures.length > 1;
private render(hints: modes.SignatureHelp): void {
const multiple = hints.signatures.length > 1;
dom.toggleClass(this.element, 'multiple', multiple);
this.keyMultipleSignatures.set(multiple);
this.signature.innerHTML = '';
this.docs.innerHTML = '';
const signature = this.hints.signatures[this.currentSignature];
const signature = hints.signatures[hints.activeSignature];
if (!signature) {
return;
@@ -401,13 +186,13 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
label.textContent = signature.label;
} else {
this.renderParameters(code, signature, this.hints.activeParameter);
this.renderParameters(code, signature, hints.activeParameter);
}
dispose(this.renderDisposeables);
this.renderDisposeables = [];
const activeParameter = signature.parameters[this.hints.activeParameter];
const activeParameter = signature.parameters[hints.activeParameter];
if (activeParameter && activeParameter.documentation) {
const documentation = $('span.documentation');
@@ -434,16 +219,16 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
dom.append(this.docs, renderedContents.element);
}
let currentOverload = String(this.currentSignature + 1);
let currentOverload = String(hints.activeSignature + 1);
if (this.hints.signatures.length < 10) {
currentOverload += `/${this.hints.signatures.length}`;
if (hints.signatures.length < 10) {
currentOverload += `/${hints.signatures.length}`;
}
this.overloads.textContent = currentOverload;
if (activeParameter) {
const labelToAnnounce = this.getParameterLabel(signature, this.hints.activeParameter);
const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter);
// Select method gets called on every user type while parameter hints are visible.
// We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
@@ -459,16 +244,16 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
let [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
const [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
let beforeSpan = document.createElement('span');
const beforeSpan = document.createElement('span');
beforeSpan.textContent = signature.label.substring(0, start);
let paramSpan = document.createElement('span');
const paramSpan = document.createElement('span');
paramSpan.textContent = signature.label.substring(start, end);
paramSpan.className = 'parameter active';
let afterSpan = document.createElement('span');
const afterSpan = document.createElement('span');
afterSpan.textContent = signature.label.substring(end);
dom.append(parent, beforeSpan, paramSpan, afterSpan);
@@ -497,85 +282,18 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
}
}
// private select(position: number): void {
// const signature = this.signatureViews[position];
// if (!signature) {
// return;
// }
// this.signatures.style.height = `${ signature.height }px`;
// this.signatures.scrollTop = signature.top;
// let overloads = '' + (position + 1);
// if (this.signatureViews.length < 10) {
// overloads += '/' + this.signatureViews.length;
// }
// this.overloads.textContent = overloads;
// if (this.hints && this.hints.signatures[position].parameters[this.hints.activeParameter]) {
// const labelToAnnounce = this.hints.signatures[position].parameters[this.hints.activeParameter].label;
// // Select method gets called on every user type while parameter hints are visible.
// // We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
// if (this.announcedLabel !== labelToAnnounce) {
// aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
// this.announcedLabel = labelToAnnounce;
// }
// }
// this.editor.layoutContentWidget(this);
// }
next(): boolean {
if (!this.hints) {
return false;
next(): void {
if (this.model) {
this.editor.focus();
this.model.next();
}
const length = this.hints.signatures.length;
const last = (this.currentSignature % length) === (length - 1);
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on last signature of list
if ((length < 2 || last) && !cycle) {
this.cancel();
return false;
}
if (last && cycle) {
this.currentSignature = 0;
} else {
this.currentSignature++;
}
this.render();
return true;
}
previous(): boolean {
if (!this.hints) {
return false;
previous(): void {
if (this.model) {
this.editor.focus();
this.model.previous();
}
const length = this.hints.signatures.length;
const first = this.currentSignature === 0;
const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on first signature of list
if ((length < 2 || first) && !cycle) {
this.cancel();
return false;
}
if (first && cycle) {
this.currentSignature = length - 1;
} else {
this.currentSignature--;
}
this.render();
return true;
}
cancel(): void {
@@ -617,11 +335,10 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable {
registerThemingParticipant((theme, collector) => {
const border = theme.getColor(editorHoverBorder);
if (border) {
let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
const borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
collector.addRule(`.monaco-editor .parameter-hints-widget { border: ${borderWidth}px solid ${border}; }`);
collector.addRule(`.monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid ${border.transparent(0.5)}; }`);
collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`);
}
const background = theme.getColor(editorHoverBackground);
if (background) {

View File

@@ -26,8 +26,9 @@ export function provideSignatureHelp(model: ITextModel, position: Position, cont
}));
}
registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position) =>
registerDefaultLanguageCommand('_executeSignatureHelpProvider', (model, position, args) =>
provideSignatureHelp(model, position, {
triggerKind: modes.SignatureHelpTriggerKind.Invoke,
isRetrigger: false
isRetrigger: false,
triggerCharacter: args['triggerCharacter']
}, CancellationToken.None));

View File

@@ -17,7 +17,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ParameterHintsModel } from '../parameterHintsWidget';
import { ParameterHintsModel } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
const mockFile = URI.parse('test:somefile.ttt');
const mockFileSelector = { scheme: 'test' };
@@ -62,7 +62,7 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext) {
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
done();
@@ -84,18 +84,23 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
++invokeCount;
if (invokeCount === 1) {
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
assert.strictEqual(context.isRetrigger, false);
assert.strictEqual(context.activeSignatureHelp, undefined);
// Retrigger
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50);
} else {
assert.strictEqual(invokeCount, 2);
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
assert.ok(context.isRetrigger);
assert.strictEqual(context.isRetrigger, true);
assert.strictEqual(context.triggerCharacter, triggerChar);
assert.strictEqual(context.activeSignatureHelp, emptySigHelpResult);
done();
}
return emptySigHelpResult;
@@ -117,11 +122,13 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
++invokeCount;
if (invokeCount === 1) {
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
assert.strictEqual(context.isRetrigger, false);
assert.strictEqual(context.activeSignatureHelp, undefined);
// Cancel and retrigger
hintModel.cancel();
@@ -130,6 +137,8 @@ suite('ParameterHintsModel', () => {
assert.strictEqual(invokeCount, 2);
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
assert.strictEqual(context.triggerCharacter, triggerChar);
assert.strictEqual(context.isRetrigger, false);
assert.strictEqual(context.activeSignatureHelp, undefined);
done();
}
return emptySigHelpResult;
@@ -148,7 +157,7 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = ['a', 'b', 'c'];
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext) {
++invokeCount;
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
@@ -179,7 +188,7 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = ['a', 'b'];
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
++invokeCount;
if (invokeCount === 1) {
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
@@ -214,7 +223,7 @@ suite('ParameterHintsModel', () => {
signatureHelpRetriggerCharacters = [];
provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelp | Promise<modes.SignatureHelp> {
const count = invokeCount++;
token.onCancellationRequested(() => { didRequestCancellationOf = count; });
@@ -244,10 +253,10 @@ suite('ParameterHintsModel', () => {
assert.strictEqual(-1, didRequestCancellationOf);
return new Promise((resolve, reject) =>
hintsModel.onHint(e => {
hintsModel.onChangedHints(newParamterHints => {
try {
assert.strictEqual(0, didRequestCancellationOf);
assert.strictEqual('1', e.hints.signatures[0].label);
assert.strictEqual('1', newParamterHints!.signatures[0].label);
resolve();
} catch (e) {
reject(e);
@@ -267,7 +276,7 @@ suite('ParameterHintsModel', () => {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [retriggerChar];
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Thenable<modes.SignatureHelp> {
provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelp | Promise<modes.SignatureHelp> {
++invokeCount;
if (invokeCount === 1) {
assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter);
@@ -294,4 +303,82 @@ suite('ParameterHintsModel', () => {
// But a trigger character should
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
});
test('should use first result from multiple providers', async () => {
const triggerChar = 'a';
const firstProviderId = 'firstProvider';
const secondProviderId = 'secondProvider';
const paramterLabel = 'parameter';
const editor = createMockEditor('');
const model = new ParameterHintsModel(editor, 5);
disposables.push(model);
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];
async provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): Promise<modes.SignatureHelp | undefined> {
if (!context.isRetrigger) {
// retrigger after delay for widget to show up
setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50);
return {
activeParameter: 0,
activeSignature: 0,
signatures: [{
label: firstProviderId,
parameters: [
{ label: paramterLabel }
]
}]
};
}
return undefined;
}
}));
disposables.push(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider {
signatureHelpTriggerCharacters = [triggerChar];
signatureHelpRetriggerCharacters = [];
async provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): Promise<modes.SignatureHelp | undefined> {
if (context.isRetrigger) {
return {
activeParameter: 0,
activeSignature: context.activeSignatureHelp ? context.activeSignatureHelp.activeSignature + 1 : 0,
signatures: [{
label: secondProviderId,
parameters: context.activeSignatureHelp ? context.activeSignatureHelp.signatures[0].parameters : []
}]
};
}
return undefined;
}
}));
editor.trigger('keyboard', Handler.Type, { text: triggerChar });
const firstHint = await getNextHint(model);
assert.strictEqual(firstHint!.signatures[0].label, firstProviderId);
assert.strictEqual(firstHint!.activeSignature, 0);
assert.strictEqual(firstHint!.signatures[0].parameters[0].label, paramterLabel);
const secondHint = await getNextHint(model);
assert.strictEqual(secondHint!.signatures[0].label, secondProviderId);
assert.strictEqual(secondHint!.activeSignature, 1);
assert.strictEqual(secondHint!.signatures[0].parameters[0].label, paramterLabel);
});
});
function getNextHint(model: ParameterHintsModel) {
return new Promise<modes.SignatureHelp | undefined>(resolve => {
const sub = model.onChangedHints(e => {
sub.dispose();
return resolve(e);
});
});
}

View File

@@ -12,7 +12,7 @@ import { DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common
import { IModelService } from 'vs/editor/common/services/modelService';
import { CancellationToken } from 'vs/base/common/cancellation';
export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Thenable<DocumentSymbol[]> {
export function getDocumentSymbols(model: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
let roots: DocumentSymbol[] = [];

View File

@@ -25,7 +25,7 @@ export namespace PeekContext {
export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated();
}
export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor {
export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null {
let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
if (editor instanceof EmbeddedCodeEditorWidget) {
return editor.getParentEditor();
@@ -34,13 +34,12 @@ export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor {
}
export interface IPeekViewStyles extends IStyles {
headerBackgroundColor?: Color;
primaryHeadingColor?: Color;
secondaryHeadingColor?: Color;
headerBackgroundColor?: Color | null;
primaryHeadingColor?: Color | null;
secondaryHeadingColor?: Color | null;
}
export interface IPeekViewOptions extends IOptions, IPeekViewStyles {
}
export type IPeekViewOptions = IOptions & IPeekViewStyles;
const defaultOptions: IPeekViewOptions = {
headerBackgroundColor: Color.white,
@@ -92,16 +91,16 @@ export abstract class PeekViewWidget extends ZoneWidget {
protected _applyStyles(): void {
super._applyStyles();
let options = <IPeekViewOptions>this.options;
if (this._headElement) {
if (this._headElement && options.headerBackgroundColor) {
this._headElement.style.backgroundColor = options.headerBackgroundColor.toString();
}
if (this._primaryHeading) {
if (this._primaryHeading && options.primaryHeadingColor) {
this._primaryHeading.style.color = options.primaryHeadingColor.toString();
}
if (this._secondaryHeading) {
if (this._secondaryHeading && options.secondaryHeadingColor) {
this._secondaryHeading.style.color = options.secondaryHeadingColor.toString();
}
if (this._bodyElement) {
if (this._bodyElement && options.frameColor) {
this._bodyElement.style.borderColor = options.frameColor.toString();
}
}
@@ -138,7 +137,7 @@ export abstract class PeekViewWidget extends ZoneWidget {
this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'close-peekview-action', true, () => {
this.dispose();
return null;
return Promise.resolve();
}), { label: false, icon: true });
}
@@ -186,11 +185,11 @@ export abstract class PeekViewWidget extends ZoneWidget {
}
protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void {
this._headElement.style.height = strings.format('{0}px', heightInPixel);
this._headElement.style.height = `${heightInPixel}px`;
this._headElement.style.lineHeight = this._headElement.style.height;
}
protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
this._bodyElement.style.height = strings.format('{0}px', heightInPixel);
this._bodyElement.style.height = `${heightInPixel}px`;
}
}

View File

@@ -30,7 +30,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
export const defaultReferenceSearchOptions: RequestOptions = {
getMetaTitle(model) {
return model.references.length > 1 && nls.localize('meta.titleReference', " {0} references", model.references.length);
return model.references.length > 1 ? nls.localize('meta.titleReference', " {0} references", model.references.length) : '';
}
};
@@ -78,15 +78,17 @@ export class ReferenceAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
let controller = ReferencesController.get(editor);
if (!controller) {
return;
}
let range = editor.getSelection();
let model = editor.getModel();
let references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references)));
controller.toggleWidget(range, references, defaultReferenceSearchOptions);
if (editor.hasModel()) {
const range = editor.getSelection();
const model = editor.getModel();
const references = createCancelablePromise(token => provideReferences(model, range.getStartPosition(), token).then(references => new ReferencesModel(references)));
controller.toggleWidget(range, references, defaultReferenceSearchOptions);
}
}
}
@@ -104,7 +106,7 @@ let findReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour
const codeEditorService = accessor.get(ICodeEditorService);
return codeEditorService.openCodeEditor({ resource }, codeEditorService.getFocusedCodeEditor()).then(control => {
if (!isCodeEditor(control)) {
if (!isCodeEditor(control) || !control.hasModel()) {
return undefined;
}
@@ -139,10 +141,11 @@ let showReferencesCommand: ICommandHandler = (accessor: ServicesAccessor, resour
return undefined;
}
return Promise.resolve(controller.toggleWidget(
return controller.toggleWidget(
new Range(position.lineNumber, position.column, position.lineNumber, position.column),
createCancelablePromise(_ => Promise.resolve(new ReferencesModel(references))),
defaultReferenceSearchOptions)).then(() => true);
defaultReferenceSearchOptions
);
});
};

View File

@@ -25,7 +25,7 @@ export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSe
export interface RequestOptions {
getMetaTitle(model: ReferencesModel): string;
onGoto?: (reference: Location) => Thenable<any>;
onGoto?: (reference: Location) => Promise<any>;
}
export abstract class ReferencesController implements editorCommon.IEditorContribution {
@@ -192,7 +192,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
this._requestIdPool += 1; // Cancel pending requests
}
private _gotoReference(ref: Location): Thenable<any> {
private _gotoReference(ref: Location): Promise<any> {
this._widget.hide();
this._ignoreModelChangeEvent = true;

View File

@@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { Range, IRange } from 'vs/editor/common/core/range';
import { Location } from 'vs/editor/common/modes';
import { Location, LocationLink } from 'vs/editor/common/modes';
import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { Position } from 'vs/editor/common/core/position';
@@ -170,7 +170,7 @@ export class ReferencesModel implements IDisposable {
readonly _onDidChangeReferenceRange = new Emitter<OneReference>();
readonly onDidChangeReferenceRange: Event<OneReference> = this._onDidChangeReferenceRange.event;
constructor(references: Location[]) {
constructor(references: LocationLink[]) {
this._disposables = [];
// grouping and sorting
references.sort(ReferencesModel._compareReferences);
@@ -187,7 +187,7 @@ export class ReferencesModel implements IDisposable {
if (current.children.length === 0
|| !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) {
let oneRef = new OneReference(current, ref.range);
let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range);
this._disposables.push(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e)));
this.references.push(oneRef);
current.children.push(oneRef);

View File

@@ -5,9 +5,8 @@
import { ReferencesModel, FileReferences, OneReference } from './referencesModel';
import { IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -21,24 +20,22 @@ import { escape } from 'vs/base/common/strings';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { basename } from 'vs/base/common/paths';
import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters';
//#region data source
export type TreeElement = FileReferences | OneReference;
export class DataSource implements IDataSource<TreeElement> {
export class DataSource implements IAsyncDataSource<ReferencesModel | FileReferences, TreeElement> {
root: ReferencesModel | FileReferences;
constructor(@ITextModelService private readonly _resolverService: ITextModelService) { }
constructor(
@ITextModelService private readonly _resolverService: ITextModelService,
) {
//
}
hasChildren(element: TreeElement): boolean {
if (!element) {
hasChildren(element: ReferencesModel | FileReferences | TreeElement): boolean {
if (element instanceof ReferencesModel) {
return true;
}
if (element instanceof FileReferences && !element.failure) {
@@ -47,10 +44,11 @@ export class DataSource implements IDataSource<TreeElement> {
return false;
}
getChildren(element: TreeElement): Thenable<TreeElement[]> {
if (!element && this.root instanceof FileReferences) {
element = this.root;
getChildren(element: ReferencesModel | FileReferences | TreeElement): TreeElement[] | Promise<TreeElement[]> {
if (element instanceof ReferencesModel) {
return element.groups;
}
if (element instanceof FileReferences) {
return element.resolve(this._resolverService).then(val => {
// if (element.failure) {
@@ -61,9 +59,7 @@ export class DataSource implements IDataSource<TreeElement> {
return val.children;
});
}
if (this.root instanceof ReferencesModel) {
return Promise.resolve(this.root.groups);
}
throw new Error('bad tree');
}
}
@@ -83,6 +79,28 @@ export class Delegate implements IListVirtualDelegate<TreeElement> {
}
}
export class StringRepresentationProvider implements IKeyboardNavigationLabelProvider<TreeElement> {
constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { }
getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } {
// todo@joao `OneReference` elements are lazy and their "real" label
// isn't known yet
return basename(element.uri.path);
}
mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
return this._keybindingService.mightProducePrintableCharacter(event);
}
}
export class IdentityProvider implements IIdentityProvider<TreeElement> {
getId(element: TreeElement): { toString(): string; } {
return element.id;
}
}
//#region render: File
class FileReferencesTemplate extends Disposable {
@@ -98,7 +116,7 @@ class FileReferencesTemplate extends Disposable {
super();
const parent = document.createElement('div');
dom.addClass(parent, 'reference-file');
this.file = this._register(new IconLabel(parent));
this.file = this._register(new IconLabel(parent, { supportHighlights: true }));
this.badge = new CountBadge(dom.append(parent, dom.$('.count')));
this._register(attachBadgeStyler(this.badge, themeService));
@@ -106,9 +124,9 @@ class FileReferencesTemplate extends Disposable {
container.appendChild(parent);
}
set(element: FileReferences) {
set(element: FileReferences, matches: IMatch[]) {
let parent = dirname(element.uri);
this.file.setValue(getBaseLabel(element.uri), parent ? this._uriLabel.getUriLabel(parent, { relative: true }) : undefined, { title: this._uriLabel.getUriLabel(element.uri) });
this.file.setLabel(getBaseLabel(element.uri), parent ? this._uriLabel.getUriLabel(parent, { relative: true }) : undefined, { title: this._uriLabel.getUriLabel(element.uri), matches });
const len = element.children.length;
this.badge.setCount(len);
if (element.failure) {
@@ -121,7 +139,7 @@ class FileReferencesTemplate extends Disposable {
}
}
export class FileReferencesRenderer implements ITreeRenderer<FileReferences, void, FileReferencesTemplate> {
export class FileReferencesRenderer implements ITreeRenderer<FileReferences, FuzzyScore, FileReferencesTemplate> {
static readonly id = 'FileReferencesRenderer';
@@ -132,11 +150,8 @@ export class FileReferencesRenderer implements ITreeRenderer<FileReferences, voi
renderTemplate(container: HTMLElement): FileReferencesTemplate {
return this._instantiationService.createInstance(FileReferencesTemplate, container);
}
renderElement(node: ITreeNode<FileReferences, void>, index: number, template: FileReferencesTemplate): void {
template.set(node.element);
}
disposeElement(element: ITreeNode<FileReferences, void>, index: number, templateData: FileReferencesTemplate): void {
//
renderElement(node: ITreeNode<FileReferences, FuzzyScore>, index: number, template: FileReferencesTemplate): void {
template.set(node.element, createMatches(node.filterData));
}
disposeTemplate(templateData: FileReferencesTemplate): void {
templateData.dispose();
@@ -177,7 +192,7 @@ class OneReferenceTemplate {
}
}
export class OneReferenceRenderer implements ITreeRenderer<OneReference, void, OneReferenceTemplate> {
export class OneReferenceRenderer implements ITreeRenderer<OneReference, FuzzyScore, OneReferenceTemplate> {
static readonly id = 'OneReferenceRenderer';
@@ -186,12 +201,9 @@ export class OneReferenceRenderer implements ITreeRenderer<OneReference, void, O
renderTemplate(container: HTMLElement): OneReferenceTemplate {
return new OneReferenceTemplate(container);
}
renderElement(element: ITreeNode<OneReference, void>, index: number, templateData: OneReferenceTemplate): void {
renderElement(element: ITreeNode<OneReference, FuzzyScore>, index: number, templateData: OneReferenceTemplate): void {
templateData.set(element.element);
}
disposeElement(): void {
//
}
disposeTemplate(): void {
//
}

View File

@@ -21,7 +21,7 @@ import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/
import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel';
import { Location } from 'vs/editor/common/modes';
import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement } from 'vs/editor/contrib/referenceSearch/referencesTree';
import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/referenceSearch/referencesTree';
import * as nls from 'vs/nls';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -31,6 +31,10 @@ import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform
import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PeekViewWidget } from './peekViewWidget';
import { FileReferences, OneReference, ReferencesModel } from './referencesModel';
import { ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { FuzzyScore } from 'vs/base/common/filters';
class DecorationsManager implements IDisposable {
@@ -69,6 +73,9 @@ class DecorationsManager implements IDisposable {
}
private _addDecorations(reference: FileReferences): void {
if (!this._editor.hasModel()) {
return;
}
this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged()));
const newDecorations: IModelDeltaDecoration[] = [];
@@ -95,8 +102,13 @@ class DecorationsManager implements IDisposable {
private _onDecorationChanged(): void {
const toRemove: string[] = [];
const model = this._editor.getModel();
if (!model) {
return;
}
this._decorations.forEach((reference, decorationId) => {
const newRange = this._editor.getModel().getDecorationRange(decorationId);
const newRange = model.getDecorationRange(decorationId);
if (!newRange) {
return;
@@ -217,7 +229,7 @@ export interface LayoutData {
export interface SelectionEvent {
kind: 'goto' | 'show' | 'side' | 'open';
source: 'editor' | 'tree' | 'title';
element: Location;
element?: Location;
}
export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey<boolean>('referenceSearchTreeFocused', true);
@@ -227,15 +239,14 @@ export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey<boolean>('r
*/
export class ReferenceWidget extends PeekViewWidget {
private _model: ReferencesModel;
private _model: ReferencesModel | undefined;
private _decorationsManager: DecorationsManager;
private _disposeOnNewModel: IDisposable[] = [];
private _callOnDispose: IDisposable[] = [];
private _onDidSelectReference = new Emitter<SelectionEvent>();
private _treeDataSource: DataSource;
private _tree: WorkbenchAsyncDataTree<TreeElement>;
private _tree: WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>;
private _treeContainer: HTMLElement;
private _sash: VSash;
private _preview: ICodeEditor;
@@ -243,6 +254,8 @@ export class ReferenceWidget extends PeekViewWidget {
private _previewNotAvailableMessage: TextModel;
private _previewContainer: HTMLElement;
private _messageContainer: HTMLElement;
private height: number | undefined;
private width: number | undefined;
constructor(
editor: ICodeEditor,
@@ -272,7 +285,7 @@ export class ReferenceWidget extends PeekViewWidget {
}
public dispose(): void {
this.setModel(null);
this.setModel(undefined);
this._callOnDispose = dispose(this._callOnDispose);
dispose<IDisposable>(this._preview, this._previewNotAvailableMessage, this._tree, this._sash, this._previewModelReference);
super.dispose();
@@ -330,38 +343,42 @@ export class ReferenceWidget extends PeekViewWidget {
this._previewNotAvailableMessage = TextModel.createFromString(nls.localize('missingPreviewMessage', "no preview available"));
// sash
this._sash = new VSash(containerElement, this.layoutData.ratio || .8);
this._sash = new VSash(containerElement, this.layoutData.ratio || 0.8);
this._sash.onDidChangePercentages(() => {
let [left, right] = this._sash.percentages;
this._previewContainer.style.width = left;
this._treeContainer.style.width = right;
this._preview.layout();
this._tree.layout();
this._tree.layout(this.height, this.width && this.width * (1 - this._sash.ratio));
this.layoutData.ratio = this._sash.ratio;
});
// tree
this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline'));
const renderer = [
const renderers = [
this._instantiationService.createInstance(FileReferencesRenderer),
this._instantiationService.createInstance(OneReferenceRenderer),
];
const treeOptions = {
const treeOptions: IAsyncDataTreeOptions<TreeElement, FuzzyScore> = {
ariaLabel: nls.localize('treeAriaLabel', "References"),
keyboardSupport: this._defaultTreeKeyboardSupport,
accessibilityProvider: new AriaProvider()
accessibilityProvider: new AriaProvider(),
keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider),
identityProvider: new IdentityProvider()
};
this._treeDataSource = this._instantiationService.createInstance(DataSource);
const treeDataSource = this._instantiationService.createInstance(DataSource);
this._tree = this._instantiationService.createInstance(
WorkbenchAsyncDataTree, this._treeContainer, new Delegate(),
renderer as any,
this._treeDataSource,
this._tree = this._instantiationService.createInstance<HTMLElement, IListVirtualDelegate<TreeElement>, ITreeRenderer<any, FuzzyScore, any>[], IAsyncDataSource<ReferencesModel | FileReferences, TreeElement>, IAsyncDataTreeOptions<TreeElement, FuzzyScore>, WorkbenchAsyncDataTree<ReferencesModel | FileReferences, TreeElement, FuzzyScore>>(
WorkbenchAsyncDataTree,
this._treeContainer,
new Delegate(),
renderers,
treeDataSource,
treeOptions
) as any as WorkbenchAsyncDataTree<TreeElement>;
);
ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService);
@@ -385,7 +402,7 @@ export class ReferenceWidget extends PeekViewWidget {
goto = true;
} else if (e.browserEvent instanceof MouseEvent) {
aside = e.browserEvent.metaKey || e.browserEvent.metaKey || e.browserEvent.altKey;
aside = e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey;
goto = e.browserEvent.detail === 2;
}
if (aside) {
@@ -403,6 +420,9 @@ export class ReferenceWidget extends PeekViewWidget {
protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
super._doLayoutBody(heightInPixel, widthInPixel);
this.height = heightInPixel;
this.width = widthInPixel;
const height = heightInPixel + 'px';
this._sash.height = heightInPixel;
this._sash.width = widthInPixel;
@@ -414,12 +434,12 @@ export class ReferenceWidget extends PeekViewWidget {
this._treeContainer.style.height = height;
this._treeContainer.style.width = right;
// forward
this._tree.layout(heightInPixel);
this._tree.layout(heightInPixel, widthInPixel * (1 - this._sash.ratio));
this._preview.layout();
// store layout data
this.layoutData = {
heightInLines: this._viewZone.heightInLines,
heightInLines: this._viewZone ? this._viewZone.heightInLines : 0,
ratio: this._sash.ratio
};
}
@@ -441,7 +461,7 @@ export class ReferenceWidget extends PeekViewWidget {
});
}
public setModel(newModel: ReferencesModel): Thenable<any> {
public setModel(newModel: ReferencesModel | undefined): Promise<any> | undefined {
// clean up
this._disposeOnNewModel = dispose(this._disposeOnNewModel);
this._model = newModel;
@@ -451,13 +471,16 @@ export class ReferenceWidget extends PeekViewWidget {
return undefined;
}
private _onNewModel(): Thenable<any> {
private _onNewModel(): Promise<any> {
if (!this._model) {
return Promise.resolve(undefined);
}
if (this._model.empty) {
this.setTitle('');
this._messageContainer.innerHTML = nls.localize('noResults', "No results");
dom.show(this._messageContainer);
return Promise.resolve(void 0);
return Promise.resolve(undefined);
}
dom.hide(this._messageContainer);
@@ -478,7 +501,7 @@ export class ReferenceWidget extends PeekViewWidget {
return;
}
this._onDidSelectReference.fire({
element: { uri: element.uri, range: target.range },
element: { uri: element.uri, range: target.range! },
kind: (event.ctrlKey || event.metaKey || event.altKey) ? 'side' : 'open',
source: 'editor'
});
@@ -493,11 +516,10 @@ export class ReferenceWidget extends PeekViewWidget {
this.focus();
// pick input and a reference to begin with
this._treeDataSource.root = this._model.groups.length === 1 ? this._model.groups[0] : this._model;
return this._tree.refresh(null);
return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model);
}
private _getFocusedReference(): OneReference {
private _getFocusedReference(): OneReference | undefined {
const [element] = this._tree.getFocus();
if (element instanceof OneReference) {
return element;
@@ -521,14 +543,14 @@ export class ReferenceWidget extends PeekViewWidget {
// Update widget header
if (reference.uri.scheme !== Schemas.inMemory) {
this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri)));
this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri)!));
} else {
this.setTitle(nls.localize('peekView.alternateTitle', "References"));
}
const promise = this._textModelResolverService.createModelReference(reference.uri);
if (this._treeDataSource.root === reference.parent) {
if (this._tree.getInput() === reference.parent) {
this._tree.reveal(reference);
} else {
if (revealParent) {

View File

@@ -23,16 +23,16 @@ suite('references', function () {
}]);
let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1));
assert.equal(ref.uri.path, '/src/can');
assert.equal(ref!.uri.path, '/src/can');
ref = model.nearestReference(URI.file('/src/someOtherFileInSrc'), new Position(1, 1));
assert.equal(ref.uri.path, '/src/can');
assert.equal(ref!.uri.path, '/src/can');
ref = model.nearestReference(URI.file('/out/someOtherFile'), new Position(1, 1));
assert.equal(ref.uri.path, '/out/obj/can');
assert.equal(ref!.uri.path, '/out/obj/can');
ref = model.nearestReference(URI.file('/out/obj/can2222'), new Position(1, 1));
assert.equal(ref.uri.path, '/out/obj/can2');
assert.equal(ref!.uri.path, '/out/obj/can2');
});
});
});

View File

@@ -6,14 +6,14 @@
import * as nls from 'vs/nls';
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import RenameInputField from './renameInputField';
import { RenameInputField, CONTEXT_RENAME_INPUT_VISIBLE } from './renameInputField';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation, Rejection } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
@@ -27,35 +27,39 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable } from 'vs/base/common/lifecycle';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
class RenameSkeleton {
private _provider: RenameProvider[];
private readonly _providers: RenameProvider[];
constructor(
readonly model: ITextModel,
readonly position: Position
private readonly model: ITextModel,
private readonly position: Position
) {
this._provider = RenameProviderRegistry.ordered(model);
this._providers = RenameProviderRegistry.ordered(model);
}
hasProvider() {
return this._provider.length > 0;
return this._providers.length > 0;
}
async resolveRenameLocation(token: CancellationToken): Promise<RenameLocation & Rejection | null | undefined> {
const firstProvider = this._providers[0];
if (!firstProvider) {
return undefined;
}
let [provider] = this._provider;
let res: RenameLocation & Rejection | null | undefined;
if (provider.resolveRenameLocation) {
res = await provider.resolveRenameLocation(this.model, this.position, token);
if (firstProvider.resolveRenameLocation) {
res = await firstProvider.resolveRenameLocation(this.model, this.position, token);
}
if (!res) {
let word = this.model.getWordAtPosition(this.position);
const word = this.model.getWordAtPosition(this.position);
if (word) {
res = {
return {
range: new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn),
text: word.word
};
@@ -65,17 +69,16 @@ class RenameSkeleton {
return res;
}
async provideRenameEdits(newName: string, i: number = 0, rejects: string[] = [], token: CancellationToken): Promise<WorkspaceEdit & Rejection> {
if (i >= this._provider.length) {
async provideRenameEdits(newName: string, i: number, rejects: string[], token: CancellationToken): Promise<WorkspaceEdit & Rejection> {
const provider = this._providers[i];
if (!provider) {
return {
edits: undefined,
edits: [],
rejectReason: rejects.join('\n')
};
}
let provider = this._provider[i];
let result = await provider.provideRenameEdits(this.model, this.position, newName, token);
const result = await provider.provideRenameEdits(this.model, this.position, newName, token);
if (!result) {
return this.provideRenameEdits(newName, i + 1, rejects.concat(nls.localize('no result', "No result.")), token);
} else if (result.rejectReason) {
@@ -86,15 +89,13 @@ class RenameSkeleton {
}
export async function rename(model: ITextModel, position: Position, newName: string): Promise<WorkspaceEdit & Rejection> {
return new RenameSkeleton(model, position).provideRenameEdits(newName, undefined, undefined, CancellationToken.None);
return new RenameSkeleton(model, position).provideRenameEdits(newName, 0, [], CancellationToken.None);
}
// --- register actions and commands
const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
// {{SQL CARBON EDIT}}
export class RenameController implements IEditorContribution {
export class RenameController extends Disposable implements IEditorContribution {
private static readonly ID = 'editor.contrib.renameController';
@@ -102,31 +103,53 @@ export class RenameController implements IEditorContribution {
return editor.getContribution<RenameController>(RenameController.ID);
}
private _renameInputField: RenameInputField;
private _renameInputVisible: IContextKey<boolean>;
private _renameInputField?: RenameInputField;
private _renameOperationIdPool = 1;
private _activeRename?: {
readonly id: number;
readonly operation: CancelablePromise<void>;
};
constructor(
private editor: ICodeEditor,
private readonly editor: ICodeEditor,
@INotificationService private readonly _notificationService: INotificationService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@IProgressService private readonly _progressService: IProgressService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IThemeService private readonly _themeService: IThemeService,
) {
this._renameInputField = new RenameInputField(editor, themeService);
this._renameInputVisible = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
super();
this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));
this._register(this.editor.onDidChangeModelLanguage(() => this.onModelChanged()));
this._register(this.editor.onDidChangeCursorSelection(() => this.onModelChanged()));
}
dispose(): void {
this._renameInputField.dispose();
private get renameInputField(): RenameInputField {
if (!this._renameInputField) {
this._renameInputField = this._register(new RenameInputField(this.editor, this._themeService, this._contextKeyService));
}
return this._renameInputField;
}
getId(): string {
return RenameController.ID;
}
async run(token: CancellationToken): Promise<void> {
async run(): Promise<void> {
if (this._activeRename) {
this._activeRename.operation.cancel();
}
const id = this._renameOperationIdPool++;
this._activeRename = {
id,
operation: createCancelablePromise(token => this.doRename(token, id))
};
return this._activeRename.operation;
}
private async doRename(token: CancellationToken, id: number): Promise<void> {
if (!this.editor.hasModel()) {
return undefined;
}
@@ -140,7 +163,9 @@ export class RenameController implements IEditorContribution {
let loc: RenameLocation & Rejection | null | undefined;
try {
loc = await skeleton.resolveRenameLocation(token);
const resolveLocationOperation = skeleton.resolveRenameLocation(token);
this._progressService.showWhile(resolveLocationOperation, 250);
loc = await resolveLocationOperation;
} catch (e) {
MessageController.get(this.editor).showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position);
return undefined;
@@ -155,6 +180,10 @@ export class RenameController implements IEditorContribution {
return undefined;
}
if (!this._activeRename || this._activeRename.id !== id) {
return undefined;
}
let selection = this.editor.getSelection();
let selectionStart = 0;
let selectionEnd = loc.text.length;
@@ -164,9 +193,7 @@ export class RenameController implements IEditorContribution {
selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
}
this._renameInputVisible.set(true);
return this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => {
this._renameInputVisible.reset();
return this.renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => {
if (typeof newNameOrFocusFlag === 'boolean') {
if (newNameOrFocusFlag) {
@@ -209,18 +236,26 @@ export class RenameController implements IEditorContribution {
this._progressService.showWhile(renameOperation, 250);
return renameOperation;
}, err => {
this._renameInputVisible.reset();
return Promise.reject(err);
});
}
public acceptRenameInput(): void {
this._renameInputField.acceptInput();
if (this._renameInputField) {
this._renameInputField.acceptInput();
}
}
public cancelRenameInput(): void {
this._renameInputField.cancelInput(true);
if (this._renameInputField) {
this._renameInputField.cancelInput(true);
}
}
private onModelChanged(): void {
if (this._activeRename) {
this._activeRename.operation.cancel();
this._activeRename = undefined;
}
}
}
@@ -246,7 +281,7 @@ export class RenameAction extends EditorAction {
});
}
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Thenable<void> {
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise<void> {
const editorService = accessor.get(ICodeEditorService);
const [uri, pos] = args || [undefined, undefined];
@@ -267,9 +302,9 @@ export class RenameAction extends EditorAction {
}
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
let controller = RenameController.get(editor);
const controller = RenameController.get(editor);
if (controller) {
return Promise.resolve(controller.run(CancellationToken.None));
return controller.run();
}
return Promise.resolve();
}

View File

@@ -3,29 +3,39 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./renameInputField';
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Range, IRange } from 'vs/editor/common/core/range';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
export default class RenameInputField implements IContentWidget, IDisposable {
export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
export class RenameInputField implements IContentWidget, IDisposable {
private _editor: ICodeEditor;
private _position: Position;
private _domNode: HTMLElement;
private _inputField: HTMLInputElement;
private _visible: boolean;
private readonly _visibleContextKey: IContextKey<boolean>;
private _disposables: IDisposable[] = [];
// Editor.IContentWidget.allowEditorOverflow
public allowEditorOverflow: boolean = true;
constructor(editor: ICodeEditor, @IThemeService private themeService: IThemeService) {
constructor(
editor: ICodeEditor,
private readonly themeService: IThemeService,
contextKeyService: IContextKeyService,
) {
this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
this._editor = editor;
this._editor.addContentWidget(this);
@@ -128,10 +138,8 @@ export default class RenameInputField implements IContentWidget, IDisposable {
this._inputField.setAttribute('selectionEnd', selectionEnd.toString());
this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20);
let disposeOnDone: IDisposable[] = [],
always: Function;
always = () => {
const disposeOnDone: IDisposable[] = [];
const always = () => {
dispose(disposeOnDone);
this._hide();
};
@@ -181,6 +189,7 @@ export default class RenameInputField implements IContentWidget, IDisposable {
private _show(): void {
this._editor.revealLineInCenterIfOutsideViewport(this._position.lineNumber, ScrollType.Smooth);
this._visible = true;
this._visibleContextKey.set(true);
this._editor.layoutContentWidget(this);
setTimeout(() => {
@@ -193,6 +202,7 @@ export default class RenameInputField implements IContentWidget, IDisposable {
private _hide(): void {
this._visible = false;
this._visibleContextKey.reset();
this._editor.layoutContentWidget(this);
}
}

View File

@@ -3,47 +3,146 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SelectionRangeProvider } from 'vs/editor/common/modes';
import { SelectionRangeProvider, SelectionRange } from 'vs/editor/common/modes';
import { ITextModel } from 'vs/editor/common/model';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { LinkedList } from 'vs/base/common/linkedList';
export class BracketSelectionRangeProvider implements SelectionRangeProvider {
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
provideSelectionRanges(model: ITextModel, position: Position): Promise<SelectionRange[]> {
const bucket: SelectionRange[] = [];
const ranges = new Map<string, LinkedList<Range>>();
return new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges))
.then(() => new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket)))
.then(() => bucket);
}
let result: Range[] = [];
let last: Range | undefined;
let pos = position;
let i = 0;
for (; i < 1750; i++) {
private static readonly _maxDuration = 30;
private static readonly _maxRounds = 2;
private static _bracketsRightYield(resolve: () => void, round: number, model: ITextModel, pos: Position, ranges: Map<string, LinkedList<Range>>): void {
const counts = new Map<string, number>();
const t1 = Date.now();
while (true) {
if (round >= BracketSelectionRangeProvider._maxRounds) {
resolve();
break;
}
if (!pos) {
resolve();
break;
}
let bracket = model.findNextBracket(pos);
if (!bracket) {
// no more brackets
resolve();
break;
} else if (bracket.isOpen) {
// skip past the closing bracket
let matching = model.matchBracket(bracket.range.getEndPosition());
if (!matching) {
break;
}
pos = model.getPositionAt(model.getOffsetAt(matching[1].getEndPosition()) + 1);
}
let d = Date.now() - t1;
if (d > BracketSelectionRangeProvider._maxDuration) {
setTimeout(() => BracketSelectionRangeProvider._bracketsRightYield(resolve, round + 1, model, pos, ranges));
break;
}
const key = bracket.close;
if (bracket.isOpen) {
// wait for closing
let val = counts.has(key) ? counts.get(key)! : 0;
counts.set(key, val + 1);
} else {
// find matching, opening bracket
let range = model.findMatchingBracketUp(bracket.close, bracket.range.getStartPosition());
if (!range) {
break;
// process closing
let val = counts.has(key) ? counts.get(key)! : 0;
val -= 1;
counts.set(key, Math.max(0, val));
if (val < 0) {
let list = ranges.get(key);
if (!list) {
list = new LinkedList();
ranges.set(key, list);
}
list.push(bracket.range);
}
if (!last || range.getStartPosition().isBefore(last.getStartPosition())) {
const inner = Range.fromPositions(range.getStartPosition(), bracket.range.getEndPosition());
const outer = Range.fromPositions(range.getEndPosition(), bracket.range.getStartPosition());
result.push(inner, outer);
last = outer;
}
pos = bracket.range.getEndPosition();
}
}
private static _bracketsLeftYield(resolve: () => void, round: number, model: ITextModel, pos: Position, ranges: Map<string, LinkedList<Range>>, bucket: SelectionRange[]): void {
const counts = new Map<string, number>();
const t1 = Date.now();
while (true) {
if (round >= BracketSelectionRangeProvider._maxRounds && ranges.size === 0) {
resolve();
break;
}
if (!pos) {
resolve();
break;
}
let bracket = model.findPrevBracket(pos);
if (!bracket) {
resolve();
break;
}
let d = Date.now() - t1;
if (d > BracketSelectionRangeProvider._maxDuration) {
setTimeout(() => BracketSelectionRangeProvider._bracketsLeftYield(resolve, round + 1, model, pos, ranges, bucket));
break;
}
const key = bracket.close;
if (!bracket.isOpen) {
// wait for opening
let val = counts.has(key) ? counts.get(key)! : 0;
counts.set(key, val + 1);
} else {
// opening
let val = counts.has(key) ? counts.get(key)! : 0;
val -= 1;
counts.set(key, Math.max(0, val));
if (val < 0) {
let list = ranges.get(key);
if (list) {
let closing = list.shift();
if (list.size === 0) {
ranges.delete(key);
}
const innerBracket = Range.fromPositions(bracket.range.getEndPosition(), closing!.getStartPosition());
const outerBracket = Range.fromPositions(bracket.range.getStartPosition(), closing!.getEndPosition());
bucket.push({ range: innerBracket, kind: 'statement.brackets' });
bucket.push({ range: outerBracket, kind: 'statement.brackets.full' });
BracketSelectionRangeProvider._addBracketLeading(model, outerBracket, bucket);
}
}
pos = model.getPositionAt(model.getOffsetAt(bracket.range.getEndPosition()) + 1);
}
pos = bracket.range.getStartPosition();
}
}
private static _addBracketLeading(model: ITextModel, bracket: Range, bucket: SelectionRange[]): void {
if (bracket.startLineNumber === bracket.endLineNumber) {
return;
}
// xxxxxxxx {
//
// }
const startLine = bracket.startLineNumber;
const column = model.getLineFirstNonWhitespaceColumn(startLine);
if (column !== 0 && column !== bracket.startColumn) {
bucket.push({ range: Range.fromPositions(new Position(startLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' });
bucket.push({ range: Range.fromPositions(new Position(startLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' });
}
// xxxxxxxx
// {
//
// }
const aboveLine = startLine - 1;
if (aboveLine > 0) {
const column = model.getLineFirstNonWhitespaceColumn(aboveLine);
if (column === bracket.startColumn && column !== model.getLineLastNonWhitespaceColumn(aboveLine)) {
bucket.push({ range: Range.fromPositions(new Position(aboveLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' });
bucket.push({ range: Range.fromPositions(new Position(aboveLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' });
}
}
return result;
}
}

View File

@@ -18,9 +18,9 @@ import * as nls from 'vs/nls';
import { MenuId } from 'vs/platform/actions/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
class SelectionRanges {
@@ -82,10 +82,10 @@ class SmartSelectController implements IEditorContribution {
}
let promise: Promise<void> = Promise.resolve(void 0);
let promise: Promise<void> = Promise.resolve(undefined);
if (!this._state) {
promise = provideSelectionRanges(model, selection.getStartPosition(), CancellationToken.None).then(ranges => {
promise = provideSelectionRanges(model, selection.getPosition(), CancellationToken.None).then(ranges => {
if (!arrays.isNonEmptyArray(ranges)) {
// invalid result
return;
@@ -154,9 +154,9 @@ abstract class AbstractSmartSelect extends EditorAction {
class GrowSelectionAction extends AbstractSmartSelect {
constructor() {
super(true, {
id: 'editor.action.smartSelect.grow',
label: nls.localize('smartSelect.grow', "Expand Select"),
alias: 'Expand Select',
id: 'editor.action.smartSelect.expand',
label: nls.localize('smartSelect.expand', "Expand Selection"),
alias: 'Expand Selection',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
@@ -174,12 +174,15 @@ class GrowSelectionAction extends AbstractSmartSelect {
}
}
// renamed command id
CommandsRegistry.registerCommandAlias('editor.action.smartSelect.grow', 'editor.action.smartSelect.expand');
class ShrinkSelectionAction extends AbstractSmartSelect {
constructor() {
super(false, {
id: 'editor.action.smartSelect.shrink',
label: nls.localize('smartSelect.shrink', "Shrink Select"),
alias: 'Shrink Select',
label: nls.localize('smartSelect.shrink', "Shrink Selection"),
alias: 'Shrink Selection',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.editorTextFocus,
@@ -201,14 +204,18 @@ registerEditorContribution(SmartSelectController);
registerEditorAction(GrowSelectionAction);
registerEditorAction(ShrinkSelectionAction);
// word selection
modes.SelectionRangeRegistry.register('*', new WordSelectionRangeProvider());
modes.SelectionRangeRegistry.register('*', new BracketSelectionRangeProvider());
modes.SelectionRangeRegistry.register('*', new TokenTreeSelectionRangeProvider());
export function provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<Range[] | undefined | null> {
const provider = modes.SelectionRangeRegistry.orderedGroups(model);
if (provider.length === 1) {
// add word selection and bracket selection when no provider exists
provider.unshift([new BracketSelectionRangeProvider()]);
}
interface RankedRange {
rank: number;
range: Range;
@@ -221,11 +228,11 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
for (const group of provider) {
rank += 1;
for (const prov of group) {
work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(res => {
if (arrays.isNonEmptyArray(res)) {
for (const range of res) {
if (Range.isIRange(range) && Range.containsPosition(range, position)) {
ranges.push({ range: Range.lift(range), rank });
work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(selectionRanges => {
if (arrays.isNonEmptyArray(selectionRanges)) {
for (const sel of selectionRanges) {
if (Range.isIRange(sel.range) && Range.containsPosition(sel.range, position)) {
ranges.push({ range: Range.lift(sel.range), rank });
}
}
}
@@ -234,6 +241,11 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
}
return Promise.all(work).then(() => {
if (ranges.length === 0) {
return [];
}
ranges.sort((a, b) => {
if (Position.isBefore(a.range.getStartPosition(), b.range.getStartPosition())) {
return 1;
@@ -248,16 +260,35 @@ export function provideSelectionRanges(model: ITextModel, position: Position, to
}
});
// ranges.sort((a, b) => Range.compareRangesUsingStarts(b.range, a.range));
let result: Range[] = [];
let last: Range | undefined;
for (const { range } of ranges) {
if (!last || Range.containsRange(range, last)) {
if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) {
result.push(range);
last = range;
}
}
return result;
let result2: Range[] = [result[0]];
for (let i = 1; i < result.length; i++) {
const prev = result[i - 1];
const cur = result[i];
if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) {
// add line/block range without leading/failing whitespace
const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber));
if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) {
result2.push(rangeNoWhitespace);
}
// add line/block range
const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber));
if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) {
result2.push(rangeFull);
}
}
result2.push(cur);
}
return result2;
});
}

View File

@@ -0,0 +1,321 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { Range, IRange } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { LanguageIdentifier, SelectionRangeProvider } from 'vs/editor/common/modes';
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect';
import { CancellationToken } from 'vs/base/common/cancellation';
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
_serviceBrand: any;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
}
getEOL(resource: URI | undefined): string {
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
if (filesConfiguration && filesConfiguration.eol) {
if (filesConfiguration.eol !== 'auto') {
return filesConfiguration.eol;
}
}
return (isLinux || isMacintosh) ? '\n' : '\r\n';
}
}
class MockJSMode extends MockMode {
private static readonly _id = new LanguageIdentifier('mockJSMode', 3);
constructor() {
super(MockJSMode._id);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
brackets: [
['(', ')'],
['{', '}'],
['[', ']']
],
onEnterRules: javascriptOnEnterRules,
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\;\:\'\"\,\.\<\>\/\?\s]+)/g
}));
}
}
suite('SmartSelect', () => {
let modelService: ModelServiceImpl;
let mode: MockJSMode;
setup(() => {
const configurationService = new TestConfigurationService();
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService));
mode = new MockJSMode();
});
teardown(() => {
modelService.dispose();
mode.dispose();
});
async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): Promise<void> {
let uri = URI.file('test.js');
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
let actual = await provideSelectionRanges(model, new Position(lineNumber, column), CancellationToken.None);
let actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
let desiredStr = ranges.reverse().map(r => String(r));
assert.deepEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`);
modelService.destroyModel(uri);
}
test('getRangesToPosition #1', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
'\t\treturn (bar + (2 * foo))',
'\t}',
'}'
], 3, 20, [
new Range(1, 1, 5, 2), // all
new Range(1, 21, 5, 2), // {} outside
new Range(1, 22, 5, 1), // {} inside
new Range(2, 1, 4, 3), // block
new Range(2, 1, 4, 3),
new Range(2, 2, 4, 3),
new Range(2, 11, 4, 3),
new Range(2, 12, 4, 2),
new Range(3, 1, 3, 27), // line w/ triva
new Range(3, 3, 3, 27), // line w/o triva
new Range(3, 10, 3, 27), // () outside
new Range(3, 11, 3, 26), // () inside
new Range(3, 17, 3, 26), // () outside
new Range(3, 18, 3, 25), // () inside
]);
});
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
'',
'\t}',
'}'
], 3, 1, [
new Range(1, 1, 5, 2),
new Range(1, 21, 5, 2),
new Range(1, 22, 5, 1),
new Range(2, 1, 4, 3),
new Range(2, 1, 4, 3),
new Range(2, 2, 4, 3),
new Range(2, 11, 4, 3),
new Range(2, 12, 4, 2),
]);
});
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
' ',
'\t}',
'}'
], 3, 1, [
new Range(1, 1, 5, 2), // all
new Range(1, 21, 5, 2), // {} outside
new Range(1, 22, 5, 1), // {} inside
new Range(2, 1, 4, 3),
new Range(2, 1, 4, 3),
new Range(2, 2, 4, 3),
new Range(2, 11, 4, 3),
new Range(2, 12, 4, 2),
new Range(3, 1, 3, 2), // block
new Range(3, 1, 3, 2) // empty line
]);
});
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
return assertGetRangesToPosition([
' [ ]',
' { } ',
'( ) '
], 2, 3, [
new Range(1, 1, 3, 5),
new Range(2, 1, 2, 6), // line w/ triava
new Range(2, 2, 2, 5), // {} inside, line w/o triva
new Range(2, 3, 2, 4) // {} inside
]);
});
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
return assertGetRangesToPosition([
' [] ',
' { } ',
' ( ) '
], 1, 3, [
new Range(1, 1, 3, 7), // all
new Range(1, 1, 1, 5), // line w/ trival
new Range(1, 2, 1, 4), // [] outside, line w/o trival
new Range(1, 3, 1, 3), // [] inside
]);
});
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
return assertGetRangesToPosition([
' [] ',
' { } ',
'selectthis( ) '
], 3, 11, [
new Range(1, 1, 3, 15), // all
new Range(3, 1, 3, 15), // line w/ trivia
new Range(3, 1, 3, 14), // line w/o trivia
new Range(3, 1, 3, 11) // word
]);
});
// -- bracket selections
async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise<void> {
let model = modelService.createModel(value, new StaticLanguageSelector(mode.getLanguageIdentifier()), URI.parse('fake:lang'));
let pos = model.getPositionAt(value.indexOf('|'));
let ranges = await provider.provideSelectionRanges(model, pos, CancellationToken.None);
modelService.destroyModel(model.uri);
assert.equal(expected.length, ranges!.length);
for (const range of ranges!) {
let exp = expected.shift() || null;
assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`);
}
}
test('bracket selection', async () => {
await assertRanges(new BracketSelectionRangeProvider(), '(|)',
new Range(1, 2, 1, 3), new Range(1, 1, 1, 4)
);
await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]',
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8), // ()
new Range(1, 3, 1, 8), new Range(1, 2, 1, 9), // [[]()]
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10), // [[[]()]]
);
await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]',
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8),
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10),
);
// no bracket
await assertRanges(new BracketSelectionRangeProvider(), 'fofof|fofo');
// empty
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]]|');
await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]');
// edge
await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 16), new Range(1, 12, 1, 17));
await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 17), new Range(1, 13, 1, 18), new Range(1, 2, 1, 26), new Range(1, 1, 1, 27));
});
test('bracket with leading/trailing', async () => {
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}',
new Range(2, 7, 2, 8), new Range(2, 6, 2, 9),
new Range(1, 13, 3, 1), new Range(1, 12, 3, 2),
new Range(1, 1, 3, 2), new Range(1, 1, 3, 2),
);
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}',
new Range(3, 7, 3, 8), new Range(3, 6, 3, 9),
new Range(2, 2, 4, 1), new Range(2, 1, 4, 2),
new Range(1, 1, 4, 2), new Range(1, 1, 4, 2),
);
});
test('in-word ranges', async () => {
await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar',
new Range(1, 1, 1, 5), // foo
new Range(1, 1, 1, 8), // fooBar
new Range(1, 1, 1, 8), // doc
);
await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba',
new Range(1, 1, 1, 5),
new Range(1, 1, 1, 8),
new Range(1, 1, 1, 8),
);
await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba',
new Range(1, 1, 1, 5),
new Range(1, 1, 1, 8),
new Range(1, 1, 1, 8),
);
});
test('Default selection should select current word/hump first in camelCase #67493', async function () {
await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect',
new Range(1, 1, 1, 10),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect',
new Range(1, 9, 1, 15),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect',
new Range(1, 9, 1, 15),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect',
new Range(1, 9, 1, 15),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect',
new Range(1, 9, 1, 15),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect',
new Range(1, 9, 1, 15),
new Range(1, 1, 1, 21),
new Range(1, 1, 1, 21),
);
});
});

View File

@@ -1,188 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { TokenTreeSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/tokenTree';
import { MarkerService } from 'vs/platform/markers/common/markerService';
class MockJSMode extends MockMode {
private static readonly _id = new LanguageIdentifier('mockJSMode', 3);
constructor() {
super(MockJSMode._id);
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
brackets: [
['(', ')'],
['{', '}'],
['[', ']']
],
onEnterRules: javascriptOnEnterRules
}));
}
}
suite('TokenSelectionSupport', () => {
let modelService: ModelServiceImpl;
let mode: MockJSMode;
setup(() => {
const configurationService = new TestConfigurationService();
modelService = new ModelServiceImpl(new MarkerService(), configurationService, new TestTextResourcePropertiesService(configurationService));
mode = new MockJSMode();
});
teardown(() => {
modelService.dispose();
mode.dispose();
});
function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): void {
let uri = URI.file('test.js');
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
let actual = new TokenTreeSelectionRangeProvider().provideSelectionRanges(model, new Position(lineNumber, column));
let actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
let desiredStr = ranges.reverse().map(r => String(r));
assert.deepEqual(actualStr, desiredStr);
modelService.destroyModel(uri);
}
test('getRangesToPosition #1', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
'\t\treturn (bar + (2 * foo))',
'\t}',
'}'
], 3, 20, [
new Range(1, 1, 5, 2),
new Range(1, 21, 5, 2),
new Range(2, 1, 4, 3),
new Range(2, 11, 4, 3),
new Range(3, 1, 4, 2),
new Range(3, 1, 3, 27),
new Range(3, 10, 3, 27),
new Range(3, 11, 3, 26),
new Range(3, 17, 3, 26),
new Range(3, 18, 3, 25),
// new Range(3, 19, 3, 20)
]);
});
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
'',
'\t}',
'}'
], 3, 1, [
new Range(1, 1, 5, 2),
new Range(1, 21, 5, 2),
new Range(2, 1, 4, 3),
new Range(2, 11, 4, 3)
]);
});
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
return assertGetRangesToPosition([
'function a(bar, foo){',
'\tif (bar) {',
' ',
'\t}',
'}'
], 3, 1, [
new Range(1, 1, 5, 2),
new Range(1, 21, 5, 2),
new Range(2, 1, 4, 3),
new Range(2, 11, 4, 3),
new Range(3, 1, 4, 2),
new Range(3, 1, 3, 2)
]);
});
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
return assertGetRangesToPosition([
' [ ]',
' { } ',
'( ) '
], 2, 3, [
new Range(1, 1, 3, 5),
new Range(2, 1, 2, 6),
new Range(2, 2, 2, 5),
new Range(2, 3, 2, 4)
]);
});
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
return assertGetRangesToPosition([
' [] ',
' { } ',
' ( ) '
], 1, 3, [
new Range(1, 1, 3, 7),
new Range(1, 1, 1, 5),
new Range(1, 2, 1, 4)
]);
});
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
return assertGetRangesToPosition([
' [] ',
' { } ',
'selectthis( ) '
], 3, 11, [
new Range(1, 1, 3, 15),
new Range(3, 1, 3, 15),
new Range(3, 1, 3, 11)
]);
});
});
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
_serviceBrand: any;
constructor(
@IConfigurationService private configurationService: IConfigurationService,
) {
}
getEOL(resource: URI): string {
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
if (filesConfiguration && filesConfiguration.eol) {
if (filesConfiguration.eol !== 'auto') {
return filesConfiguration.eol;
}
}
return (isLinux || isMacintosh) ? '\n' : '\r\n';
}
}

View File

@@ -1,452 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports';
import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageId, StandardTokenType, SelectionRangeProvider } from 'vs/editor/common/modes';
export class TokenTreeSelectionRangeProvider implements SelectionRangeProvider {
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
let tree = build(model);
let node = find(tree, position);
let ranges: Range[] = [];
let lastRange: Range | undefined;
while (node) {
if (!lastRange || !Range.equalsRange(lastRange, node.range)) {
ranges.push(node.range);
}
lastRange = node.range;
node = node.parent;
}
return ranges;
}
}
export const enum TokenTreeBracket {
None = 0,
Open = 1,
Close = -1
}
export class Node {
start: Position;
end: Position;
get range(): Range {
return new Range(
this.start.lineNumber,
this.start.column,
this.end.lineNumber,
this.end.column
);
}
parent: Node;
}
export class NodeList extends Node {
children: Node[];
get start(): Position {
return this.hasChildren
? this.children[0].start
: this.parent.start;
}
get end(): Position {
return this.hasChildren
? this.children[this.children.length - 1].end
: this.parent.end;
}
get hasChildren() {
return this.children && this.children.length > 0;
}
get isEmpty() {
return !this.hasChildren && !this.parent;
}
public append(node: Node | null): boolean {
if (!node) {
return false;
}
node.parent = this;
if (!this.children) {
this.children = [];
}
if (node instanceof NodeList) {
if (node.children) {
this.children.push.apply(this.children, node.children);
}
} else {
this.children.push(node);
}
return true;
}
}
export class Block extends Node {
open: Node;
close: Node;
elements: NodeList;
get start(): Position {
return this.open.start;
}
get end(): Position {
return this.close.end;
}
constructor() {
super();
this.elements = new NodeList();
this.elements.parent = this;
}
}
class Token {
_tokenBrand: void;
readonly range: Range;
readonly bracket: TokenTreeBracket;
readonly bracketType: string | null;
constructor(range: Range, bracket: TokenTreeBracket, bracketType: string | null) {
this.range = range;
this.bracket = bracket;
this.bracketType = bracketType;
}
}
function newNode(token: Token): Node {
let node = new Node();
node.start = token.range.getStartPosition();
node.end = token.range.getEndPosition();
return node;
}
class RawToken {
_basicTokenBrand: void;
public lineNumber: number;
public lineText: string;
public startOffset: number;
public endOffset: number;
public type: StandardTokenType;
public languageId: LanguageId;
constructor(source: LineTokens, tokenIndex: number, lineNumber: number) {
this.lineNumber = lineNumber;
this.lineText = source.getLineContent();
this.startOffset = source.getStartOffset(tokenIndex);
this.endOffset = source.getEndOffset(tokenIndex);
this.type = source.getStandardTokenType(tokenIndex);
this.languageId = source.getLanguageId(tokenIndex);
}
}
class ModelRawTokenScanner {
private _model: ITextModel;
private _lineCount: number;
private _versionId: number;
private _lineNumber: number;
private _tokenIndex: number;
private _lineTokens: LineTokens | null;
constructor(model: ITextModel) {
this._model = model;
this._lineCount = this._model.getLineCount();
this._versionId = this._model.getVersionId();
this._lineNumber = 0;
this._tokenIndex = 0;
this._lineTokens = null;
this._advance();
}
private _advance(): void {
if (this._lineTokens) {
this._tokenIndex++;
if (this._tokenIndex >= this._lineTokens.getCount()) {
this._lineTokens = null;
}
}
while (this._lineNumber < this._lineCount && !this._lineTokens) {
this._lineNumber++;
this._model.forceTokenization(this._lineNumber);
this._lineTokens = this._model.getLineTokens(this._lineNumber);
this._tokenIndex = 0;
if (this._lineTokens.getLineContent().length === 0) {
// Skip empty lines
this._lineTokens = null;
}
}
}
public next(): RawToken | null {
if (!this._lineTokens) {
return null;
}
if (this._model.getVersionId() !== this._versionId) {
return null;
}
let result = new RawToken(this._lineTokens, this._tokenIndex, this._lineNumber);
this._advance();
return result;
}
}
class TokenScanner {
private _rawTokenScanner: ModelRawTokenScanner;
private _nextBuff: Token[];
private _cachedLanguageBrackets: RichEditBrackets | null;
private _cachedLanguageId: LanguageId;
constructor(model: ITextModel) {
this._rawTokenScanner = new ModelRawTokenScanner(model);
this._nextBuff = [];
this._cachedLanguageBrackets = null;
this._cachedLanguageId = -1;
}
next(): Token | null {
if (this._nextBuff.length > 0) {
return this._nextBuff.shift()!;
}
const token = this._rawTokenScanner.next();
if (!token) {
return null;
}
const lineNumber = token.lineNumber;
const lineText = token.lineText;
const tokenType = token.type;
let startOffset = token.startOffset;
const endOffset = token.endOffset;
if (this._cachedLanguageId !== token.languageId) {
this._cachedLanguageId = token.languageId;
this._cachedLanguageBrackets = LanguageConfigurationRegistry.getBracketsSupport(this._cachedLanguageId);
}
const modeBrackets = this._cachedLanguageBrackets;
if (!modeBrackets || ignoreBracketsInToken(tokenType)) {
return new Token(
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
TokenTreeBracket.None,
null
);
}
let foundBracket: Range | null;
do {
foundBracket = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, startOffset, endOffset);
if (foundBracket) {
const foundBracketStartOffset = foundBracket.startColumn - 1;
const foundBracketEndOffset = foundBracket.endColumn - 1;
if (startOffset < foundBracketStartOffset) {
// there is some text before this bracket in this token
this._nextBuff.push(new Token(
new Range(lineNumber, startOffset + 1, lineNumber, foundBracketStartOffset + 1),
TokenTreeBracket.None,
null
));
}
let bracketText = lineText.substring(foundBracketStartOffset, foundBracketEndOffset);
bracketText = bracketText.toLowerCase();
const bracketData = modeBrackets.textIsBracket[bracketText];
const bracketIsOpen = modeBrackets.textIsOpenBracket[bracketText];
this._nextBuff.push(new Token(
new Range(lineNumber, foundBracketStartOffset + 1, lineNumber, foundBracketEndOffset + 1),
bracketIsOpen ? TokenTreeBracket.Open : TokenTreeBracket.Close,
`${bracketData.languageIdentifier.language};${bracketData.open};${bracketData.close}`
));
startOffset = foundBracketEndOffset;
}
} while (foundBracket);
if (startOffset < endOffset) {
// there is some remaining none-bracket text in this token
this._nextBuff.push(new Token(
new Range(lineNumber, startOffset + 1, lineNumber, endOffset + 1),
TokenTreeBracket.None,
null
));
}
return this._nextBuff.shift() || null;
}
}
class TokenTreeBuilder {
private _scanner: TokenScanner;
private _stack: Token[] = [];
private _currentToken: Token;
constructor(model: ITextModel) {
this._scanner = new TokenScanner(model);
}
public build(): Node {
let node = new NodeList();
while (node.append(this._line() || this._any())) {
// accept all
}
return node;
}
private _accept(condt: (info: Token) => boolean): boolean {
let token = this._stack.pop() || this._scanner.next();
if (!token) {
return false;
}
let accepted = condt(token);
if (!accepted) {
this._stack.push(token);
// this._currentToken = null;
} else {
this._currentToken = token;
// console.log('accepted: ' + token.__debugContent);
}
return accepted;
}
private _peek(condt: (info: Token) => boolean): boolean {
let ret = false;
this._accept(info => {
ret = condt(info);
return false;
});
return ret;
}
private _line(): Node | null {
let node = new NodeList();
let lineNumber: number;
// capture current linenumber
this._peek(info => {
lineNumber = info.range.startLineNumber;
return false;
});
while (this._peek(info => info.range.startLineNumber === lineNumber)
&& node.append(this._token() || this._block())) {
// all children that started on this line
}
if (!node.children || node.children.length === 0) {
return null;
} else if (node.children.length === 1) {
return node.children[0];
} else {
return node;
}
}
private _token(): Node | null {
if (!this._accept(token => token.bracket === TokenTreeBracket.None)) {
return null;
}
return newNode(this._currentToken);
}
private _block(): Node | null {
let bracketType: string | null;
let accepted: boolean;
accepted = this._accept(token => {
bracketType = token.bracketType;
return token.bracket === TokenTreeBracket.Open;
});
if (!accepted) {
return null;
}
let bracket = new Block();
bracket.open = newNode(this._currentToken);
while (bracket.elements.append(this._line())) {
// inside brackets
}
if (!this._accept(token => token.bracket === TokenTreeBracket.Close && token.bracketType === bracketType)) {
// missing closing bracket -> return just a node list
let nodelist = new NodeList();
nodelist.append(bracket.open);
nodelist.append(bracket.elements);
return nodelist;
}
bracket.close = newNode(this._currentToken);
return bracket;
}
private _any(): Node | null {
if (!this._accept(_ => true)) {
return null;
}
return newNode(this._currentToken);
}
}
/**
* Parses this grammar:
* grammer = { line }
* line = { block | "token" }
* block = "open_bracket" { line } "close_bracket"
*/
export function build(model: ITextModel): Node {
let node = new TokenTreeBuilder(model).build();
return node;
}
export function find(node: Node, position: Position): Node | null {
if (node instanceof NodeList && node.isEmpty) {
return null;
}
if (!Range.containsPosition(node.range, position)) {
return null;
}
let result: Node | null = null;
if (node instanceof NodeList) {
if (node.hasChildren) {
for (let i = 0, len = node.children.length; i < len && !result; i++) {
result = find(node.children[i], position);
}
}
} else if (node instanceof Block) {
result = find(node.elements, position) || find(node.open, position) || find(node.close, position);
}
return result || node;
}

View File

@@ -3,29 +3,81 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SelectionRangeProvider } from 'vs/editor/common/modes';
import { SelectionRangeProvider, SelectionRange } from 'vs/editor/common/modes';
import { ITextModel } from 'vs/editor/common/model';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { CharCode } from 'vs/base/common/charCode';
import { isUpperAsciiLetter, isLowerAsciiLetter } from 'vs/base/common/strings';
export class WordSelectionRangeProvider implements SelectionRangeProvider {
provideSelectionRanges(model: ITextModel, position: Position): Range[] {
let result: Range[] = [];
provideSelectionRanges(model: ITextModel, position: Position): SelectionRange[] {
let result: SelectionRange[] = [];
this._addInWordRanges(result, model, position);
this._addWordRanges(result, model, position);
this._addLineRanges(result, model, position);
this._addWhitespaceLine(result, model, position);
result.push({ range: model.getFullModelRange(), kind: 'statement.all' });
return result;
}
private _addWordRanges(bucket: Range[], model: ITextModel, pos: Position): void {
const word = model.getWordAtPosition(pos);
if (word) {
bucket.push(new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn));
private _addInWordRanges(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
const obj = model.getWordAtPosition(pos);
if (!obj) {
return;
}
let { word, startColumn } = obj;
let offset = pos.column - startColumn;
let start = offset;
let end = offset;
let lastCh: number = 0;
// LEFT anchor (start)
for (; start >= 0; start--) {
let ch = word.charCodeAt(start);
if (ch === CharCode.Underline || ch === CharCode.Dash) {
// foo-bar OR foo_bar
break;
} else if (isLowerAsciiLetter(ch) && isUpperAsciiLetter(lastCh)) {
// fooBar
break;
}
lastCh = ch;
}
start += 1;
// RIGHT anchor (end)
for (; end < word.length; end++) {
let ch = word.charCodeAt(end);
if (isUpperAsciiLetter(ch) && isLowerAsciiLetter(lastCh)) {
// fooBar
break;
} else if (ch === CharCode.Underline || ch === CharCode.Dash) {
// foo-bar OR foo_bar
break;
}
lastCh = ch;
}
if (start < end) {
bucket.push({ range: new Range(pos.lineNumber, startColumn + start, pos.lineNumber, startColumn + end), kind: 'statement.word.part' });
}
}
private _addLineRanges(bucket: Range[], model: ITextModel, pos: Position): void {
bucket.push(new Range(pos.lineNumber, model.getLineFirstNonWhitespaceColumn(pos.lineNumber), pos.lineNumber, model.getLineLastNonWhitespaceColumn(pos.lineNumber)));
bucket.push(new Range(pos.lineNumber, model.getLineMinColumn(pos.lineNumber), pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)));
private _addWordRanges(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
const word = model.getWordAtPosition(pos);
if (word) {
bucket.push({ range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn), kind: 'statement.word' });
}
}
private _addWhitespaceLine(bucket: SelectionRange[], model: ITextModel, pos: Position): void {
if (model.getLineLength(pos.lineNumber) > 0
&& model.getLineFirstNonWhitespaceColumn(pos.lineNumber) === 0
&& model.getLineLastNonWhitespaceColumn(pos.lineNumber) === 0
) {
bucket.push({ range: new Range(pos.lineNumber, 1, pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)), kind: 'statement.line' });
}
}
}

View File

@@ -34,10 +34,10 @@ export class SnippetController2 implements IEditorContribution {
private readonly _hasNextTabstop: IContextKey<boolean>;
private readonly _hasPrevTabstop: IContextKey<boolean>;
private _session: SnippetSession;
private _session?: SnippetSession;
private _snippetListener: IDisposable[] = [];
private _modelVersionId: number;
private _currentChoice: Choice;
private _currentChoice?: Choice;
constructor(
private readonly _editor: ICodeEditor,
@@ -87,6 +87,9 @@ export class SnippetController2 implements IEditorContribution {
undoStopBefore: boolean = true, undoStopAfter: boolean = true,
adjustWhitespace: boolean = true,
): void {
if (!this._editor.hasModel()) {
return;
}
// don't listen while inserting the snippet
// as that is the inflight state causing cancelation
@@ -118,7 +121,7 @@ export class SnippetController2 implements IEditorContribution {
}
private _updateState(): void {
if (!this._session) {
if (!this._session || !this._editor.hasModel()) {
// canceled in the meanwhile
return;
}
@@ -147,6 +150,11 @@ export class SnippetController2 implements IEditorContribution {
}
private _handleChoice(): void {
if (!this._session || !this._editor.hasModel()) {
this._currentChoice = undefined;
return;
}
const { choice } = this._session;
if (!choice) {
this._currentChoice = undefined;
@@ -173,7 +181,7 @@ export class SnippetController2 implements IEditorContribution {
// insertText: `\${1|${after.concat(before).join(',')}|}$0`,
// snippetType: 'textmate',
sortText: repeat('a', i),
range: Range.fromPositions(this._editor.getPosition(), this._editor.getPosition().delta(0, first.value.length))
range: Range.fromPositions(this._editor.getPosition()!, this._editor.getPosition()!.delta(0, first.value.length))
};
}));
}
@@ -196,20 +204,24 @@ export class SnippetController2 implements IEditorContribution {
}
prev(): void {
this._session.prev();
if (this._session) {
this._session.prev();
}
this._updateState();
}
next(): void {
this._session.next();
if (this._session) {
this._session.next();
}
this._updateState();
}
isInSnippet(): boolean {
return this._inSnippet.get();
return Boolean(this._inSnippet.get());
}
getSessionEnclosingRange(): Range {
getSessionEnclosingRange(): Range | undefined {
if (this._session) {
return this._session.getEnclosingRange();
}

View File

@@ -373,7 +373,7 @@ export class FormatString extends Marker {
super();
}
resolve(value: string): string {
resolve(value?: string): string {
if (this.shorthandName === 'upcase') {
return !value ? '' : value.toLocaleUpperCase();
} else if (this.shorthandName === 'downcase') {
@@ -626,10 +626,11 @@ export class SnippetParser {
return true;
});
for (const placeholder of incompletePlaceholders) {
if (placeholderDefaultValues.has(placeholder.index)) {
const defaultValues = placeholderDefaultValues.get(placeholder.index);
if (defaultValues) {
const clone = new Placeholder(placeholder.index);
clone.transform = placeholder.transform;
for (const child of placeholderDefaultValues.get(placeholder.index)) {
for (const child of defaultValues) {
clone.appendChild(child.clone());
}
snippet.replace(placeholder, [clone]);
@@ -929,7 +930,7 @@ export class SnippetParser {
let escaped: string;
if (escaped = this._accept(TokenType.Backslash, true)) {
escaped = this._accept(TokenType.Forwardslash, true) || escaped;
escaped = this._accept(TokenType.Backslash, true) || this._accept(TokenType.Forwardslash, true) || escaped;
transform.appendChild(new Text(escaped));
continue;
}

View File

@@ -7,7 +7,7 @@ import { groupBy } from 'vs/base/common/arrays';
import { dispose } from 'vs/base/common/lifecycle';
import { getLeadingWhitespace } from 'vs/base/common/strings';
import 'vs/css!./snippetSession';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -34,7 +34,7 @@ registerThemingParticipant((theme, collector) => {
export class OneSnippet {
private readonly _editor: ICodeEditor;
private readonly _editor: IActiveCodeEditor;
private readonly _snippet: TextmateSnippet;
private readonly _offset: number;
@@ -50,7 +50,7 @@ export class OneSnippet {
inactiveFinal: ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, className: 'finish-snippet-placeholder' }),
};
constructor(editor: ICodeEditor, snippet: TextmateSnippet, offset: number) {
constructor(editor: IActiveCodeEditor, snippet: TextmateSnippet, offset: number) {
this._editor = editor;
this._snippet = snippet;
this._offset = offset;
@@ -95,6 +95,9 @@ export class OneSnippet {
}
move(fwd: boolean | undefined): Selection[] {
if (!this._editor.hasModel()) {
return [];
}
this._initDecorations();
@@ -105,8 +108,8 @@ export class OneSnippet {
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
// Check if the placeholder has a transformation
if (placeholder.transform) {
const id = this._placeholderDecorations.get(placeholder);
const range = this._editor.getModel().getDecorationRange(id);
const id = this._placeholderDecorations.get(placeholder)!;
const range = this._editor.getModel().getDecorationRange(id)!;
const currentValue = this._editor.getModel().getValueInRange(range);
operations.push(EditOperation.replaceMove(range, placeholder.transform.resolve(currentValue)));
@@ -142,8 +145,8 @@ export class OneSnippet {
// Special case #2: placeholders enclosing active placeholders
const selections: Selection[] = [];
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
const id = this._placeholderDecorations.get(placeholder);
const range = this._editor.getModel().getDecorationRange(id);
const id = this._placeholderDecorations.get(placeholder)!;
const range = this._editor.getModel().getDecorationRange(id)!;
selections.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn));
// consider to skip this placeholder index when the decoration
@@ -155,7 +158,7 @@ export class OneSnippet {
activePlaceholders.add(placeholder);
for (const enclosingPlaceholder of this._snippet.enclosingPlaceholders(placeholder)) {
const id = this._placeholderDecorations.get(enclosingPlaceholder);
const id = this._placeholderDecorations.get(enclosingPlaceholder)!;
accessor.changeDecorationOptions(id, enclosingPlaceholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active);
activePlaceholders.add(enclosingPlaceholder);
}
@@ -170,7 +173,7 @@ export class OneSnippet {
});
return selections;
});
})!;
return !skipThisPlaceholder ? newSelections : this.move(fwd);
}
@@ -190,7 +193,7 @@ export class OneSnippet {
computePossibleSelections() {
const result = new Map<number, Range[]>();
for (const placeholdersWithEqualIndex of this._placeholderGroups) {
let ranges: Range[];
let ranges: Range[] | undefined;
for (const placeholder of placeholdersWithEqualIndex) {
if (placeholder.isFinalTabstop) {
@@ -203,7 +206,7 @@ export class OneSnippet {
result.set(placeholder.index, ranges);
}
const id = this._placeholderDecorations.get(placeholder);
const id = this._placeholderDecorations.get(placeholder)!;
const range = this._editor.getModel().getDecorationRange(id);
if (!range) {
// one of the placeholder lost its decoration and
@@ -219,7 +222,7 @@ export class OneSnippet {
return result;
}
get choice(): Choice {
get choice(): Choice | undefined {
return this._placeholderGroups[this._placeholderGroupsIdx][0].choice;
}
@@ -235,13 +238,13 @@ export class OneSnippet {
// everything is sorted by editor selection we can simply remove
// elements from the beginning of the array
for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) {
const nested = others.shift();
const nested = others.shift()!;
console.assert(!nested._placeholderDecorations);
// Massage placeholder-indicies of the nested snippet to be
// sorted right after the insertion point. This ensures we move
// through the placeholders in the correct order
const indexLastPlaceholder = nested._snippet.placeholderInfo.last.index;
const indexLastPlaceholder = nested._snippet.placeholderInfo.last!.index;
for (const nestedPlaceholder of nested._snippet.placeholderInfo.all) {
if (nestedPlaceholder.isFinalTabstop) {
@@ -254,7 +257,7 @@ export class OneSnippet {
// Remove the placeholder at which position are inserting
// the snippet and also remove its decoration.
const id = this._placeholderDecorations.get(placeholder);
const id = this._placeholderDecorations.get(placeholder)!;
accessor.removeDecoration(id);
this._placeholderDecorations.delete(placeholder);
@@ -277,15 +280,15 @@ export class OneSnippet {
});
}
public getEnclosingRange(): Range {
let result: Range;
public getEnclosingRange(): Range | undefined {
let result: Range | undefined;
const model = this._editor.getModel();
this._placeholderDecorations.forEach((decorationId) => {
const placeholderRange = model.getDecorationRange(decorationId);
const placeholderRange = model.getDecorationRange(decorationId) || undefined;
if (!result) {
result = placeholderRange;
} else {
result = result.plusRange(placeholderRange);
result = result.plusRange(placeholderRange!);
}
});
return result;
@@ -340,12 +343,15 @@ export class SnippetSession {
return selection;
}
static createEditsAndSnippets(editor: ICodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
const model = editor.getModel();
static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean): { edits: IIdentifiedSingleEditOperation[], snippets: OneSnippet[] } {
const edits: IIdentifiedSingleEditOperation[] = [];
const snippets: OneSnippet[] = [];
if (!editor.hasModel()) {
return { edits, snippets };
}
const model = editor.getModel();
const modelBasedVariableResolver = new ModelBasedVariableResolver(model);
const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional));
@@ -419,7 +425,7 @@ export class SnippetSession {
return { edits, snippets };
}
private readonly _editor: ICodeEditor;
private readonly _editor: IActiveCodeEditor;
private readonly _template: string;
private readonly _templateMerges: [number, number, string][] = [];
private readonly _overwriteBefore: number;
@@ -427,7 +433,7 @@ export class SnippetSession {
private readonly _adjustWhitespace: boolean;
private _snippets: OneSnippet[] = [];
constructor(editor: ICodeEditor, template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true) {
constructor(editor: IActiveCodeEditor, template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true) {
this._editor = editor;
this._template = template;
this._overwriteBefore = overwriteBefore;
@@ -444,6 +450,9 @@ export class SnippetSession {
}
insert(): void {
if (!this._editor.hasModel()) {
return;
}
const model = this._editor.getModel();
@@ -457,12 +466,15 @@ export class SnippetSession {
} else {
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
}
});
})!;
this._editor.setSelections(selections);
this._editor.revealRange(selections[0]);
}
merge(template: string, overwriteBefore: number = 0, overwriteAfter: number = 0, adjustWhitespace: boolean = true): void {
if (!this._editor.hasModel()) {
return;
}
this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, overwriteBefore, overwriteAfter, true, adjustWhitespace);
@@ -478,7 +490,7 @@ export class SnippetSession {
} else {
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
}
}));
})!);
}
next(): void {
@@ -514,7 +526,7 @@ export class SnippetSession {
return this._snippets[0].hasPlaceholder;
}
get choice(): Choice {
get choice(): Choice | undefined {
return this._snippets[0].choice;
}
@@ -532,7 +544,7 @@ export class SnippetSession {
return false;
}
let allPossibleSelections: Map<number, Range[]>;
let allPossibleSelections = new Map<number, Range[]>();
for (const snippet of this._snippets) {
const possibleSelections = snippet.computePossibleSelections();
@@ -540,8 +552,7 @@ export class SnippetSession {
// for the first snippet find the placeholder (and its ranges)
// that contain at least one selection. for all remaining snippets
// the same placeholder (and their ranges) must be used.
if (!allPossibleSelections) {
allPossibleSelections = new Map<number, Range[]>();
if (allPossibleSelections.size === 0) {
possibleSelections.forEach((ranges, index) => {
ranges.sort(Range.compareRangesUsingStarts);
@@ -563,7 +574,7 @@ export class SnippetSession {
// add selections from 'this' snippet so that we know all
// selections for this placeholder
allPossibleSelections.forEach((array, index) => {
array.push(...possibleSelections.get(index));
array.push(...possibleSelections.get(index)!);
});
}
@@ -595,14 +606,14 @@ export class SnippetSession {
return allPossibleSelections.size > 0;
}
public getEnclosingRange(): Range {
let result: Range;
public getEnclosingRange(): Range | undefined {
let result: Range | undefined;
for (const snippet of this._snippets) {
const snippetRange = snippet.getEnclosingRange();
if (!result) {
result = snippetRange;
} else {
result = result.plusRange(snippetRange);
result = result.plusRange(snippetRange!);
}
}
return result;

View File

@@ -49,7 +49,7 @@ export class CompositeSnippetVariableResolver implements VariableResolver {
resolve(variable: Variable): string | undefined {
for (const delegate of this._delegates) {
let value = delegate.resolve(variable);
if (value !== void 0) {
if (value !== undefined) {
return value;
}
}

View File

@@ -22,7 +22,7 @@ class TestSnippetController extends SnippetController2 {
}
isInSnippetMode(): boolean {
return SnippetController2.InSnippetMode.getValue(this._contextKeyService);
return SnippetController2.InSnippetMode.getValue(this._contextKeyService)!;
}
}
@@ -42,7 +42,7 @@ suite('SnippetController', () => {
}
withTestCodeEditor(lines, {}, (editor, cursor) => {
editor.getModel().updateOptions({
editor.getModel()!.updateOptions({
insertSpaces: false
});
let snippetController = editor.registerAndInstantiateContribution<TestSnippetController>(TestSnippetController);
@@ -63,30 +63,30 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(template, 0, 0);
assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel().getLineContent(6), '\t\t');
assert.equal(editor.getModel().getLineContent(7), '\t}');
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
editor.trigger('test', 'type', { text: 'i' });
assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < array.length; i++) {');
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[i];');
assert.equal(editor.getModel().getLineContent(6), '\t\t');
assert.equal(editor.getModel().getLineContent(7), '\t}');
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
snippetController.next();
editor.trigger('test', 'type', { text: 'arr' });
assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[i];');
assert.equal(editor.getModel().getLineContent(6), '\t\t');
assert.equal(editor.getModel().getLineContent(7), '\t}');
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
snippetController.prev();
editor.trigger('test', 'type', { text: 'j' });
assert.equal(editor.getModel().getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[j];');
assert.equal(editor.getModel().getLineContent(6), '\t\t');
assert.equal(editor.getModel().getLineContent(7), '\t}');
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
snippetController.next();
snippetController.next();
@@ -99,10 +99,10 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(template, 0, 0);
assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel().getLineContent(6), '\t\t');
assert.equal(editor.getModel().getLineContent(7), '\t}');
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
snippetController.cancel();
assert.deepEqual(editor.getPosition(), new Position(4, 16));
@@ -114,7 +114,7 @@ suite('SnippetController', () => {
// editor.setPosition({ lineNumber: 4, column: 2 });
// snippetController.insert(codeSnippet, 0, 0);
// editor.getModel().applyEdits([{
// editor.getModel()!.applyEdits([{
// forceMoveMarkers: false,
// identifier: null,
// isAutoWhitespaceEdit: false,
@@ -131,7 +131,7 @@ suite('SnippetController', () => {
// editor.setPosition({ lineNumber: 4, column: 2 });
// snippetController.run(codeSnippet, 0, 0);
// editor.getModel().applyEdits([{
// editor.getModel()!.applyEdits([{
// forceMoveMarkers: false,
// identifier: null,
// isAutoWhitespaceEdit: false,
@@ -148,7 +148,7 @@ suite('SnippetController', () => {
// editor.setPosition({ lineNumber: 4, column: 2 });
// snippetController.run(codeSnippet, 0, 0);
// editor.getModel().applyEdits([{
// editor.getModel()!.applyEdits([{
// forceMoveMarkers: false,
// identifier: null,
// isAutoWhitespaceEdit: false,
@@ -165,7 +165,7 @@ suite('SnippetController', () => {
// editor.setPosition({ lineNumber: 4, column: 2 });
// snippetController.run(codeSnippet, 0, 0);
// editor.getModel().applyEdits([{
// editor.getModel()!.applyEdits([{
// forceMoveMarkers: false,
// identifier: null,
// isAutoWhitespaceEdit: false,
@@ -182,7 +182,7 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(codeSnippet, 0, 0);
editor.getModel().setValue('goodbye');
editor.getModel()!.setValue('goodbye');
assert.equal(snippetController.isInSnippetMode(), false);
});
@@ -193,7 +193,7 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(codeSnippet, 0, 0);
editor.getModel().undo();
editor.getModel()!.undo();
assert.equal(snippetController.isInSnippetMode(), false);
});
@@ -242,8 +242,8 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0';
snippetController.insert(codeSnippet, 0, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
});
@@ -257,8 +257,8 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0bar';
snippetController.insert(codeSnippet, 0, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
});
@@ -272,8 +272,8 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0bar';
snippetController.insert(codeSnippet, 0, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString());
});
@@ -287,8 +287,8 @@ suite('SnippetController', () => {
codeSnippet = 'foo\n$0\nbar';
snippetController.insert(codeSnippet, 0, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
});
@@ -302,8 +302,8 @@ suite('SnippetController', () => {
codeSnippet = 'foo\n$0\nbar';
snippetController.insert(codeSnippet, 0, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
});
@@ -316,8 +316,8 @@ suite('SnippetController', () => {
codeSnippet = 'xo$0r';
snippetController.insert(codeSnippet, 1, 0);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
assert.equal(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
});
});
@@ -329,9 +329,9 @@ suite('SnippetController', () => {
codeSnippet = '{{% url_**$1** %}}';
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
assert.equal(editor.getModel().getValue(), 'example example {{% url_**** %}}');
assert.equal(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
assert.equal(editor.getModel()!.getValue(), 'example example {{% url_**** %}}');
}, ['example example sc']);
@@ -347,9 +347,9 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection().toString());
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
assert.equal(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection()!.toString());
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
@@ -365,9 +365,9 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection().toString());
assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});');
assert.equal(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection()!.toString());
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
@@ -381,9 +381,9 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 8, 0);
assert.equal(editor.getModel().getValue(), 'after');
assert.equal(editor.getSelections().length, 1);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection().toString());
assert.equal(editor.getModel()!.getValue(), 'after');
assert.equal(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection()!.toString());
}, ['afterone']);
});
@@ -405,8 +405,8 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getSelections().length, 2);
const [first, second] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), second.toString());
@@ -430,8 +430,8 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getSelections().length, 1);
const [first] = editor.getSelections();
assert.equal(editor.getSelections()!.length, 1);
const [first] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString());
@@ -450,7 +450,7 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, 2, 0);
assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 }));
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 }));
}, ['af', '\taf']);
});
@@ -466,7 +466,7 @@ suite('SnippetController', () => {
codeSnippet = '_foo';
controller.insert(codeSnippet, 1, 0);
assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo');
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo');
}, ['this._', 'abc']);
@@ -479,7 +479,7 @@ suite('SnippetController', () => {
codeSnippet = 'XX';
controller.insert(codeSnippet, 1, 0);
assert.equal(editor.getModel().getValue(), 'this.XX\nabcXX');
assert.equal(editor.getModel()!.getValue(), 'this.XX\nabcXX');
}, ['this._', 'abc']);
@@ -493,7 +493,7 @@ suite('SnippetController', () => {
codeSnippet = '_foo';
controller.insert(codeSnippet, 1, 0);
assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo\ndef_foo');
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo');
}, ['this._', 'abc', 'def_']);
@@ -507,7 +507,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getModel().getValue(), 'this._foo\nabc._foo\ndef._foo');
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
}, ['this._', 'abc', 'def._']);
@@ -521,7 +521,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getModel().getValue(), 'this._foo\nabc._foo\ndef._foo');
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
}, ['this._', 'abc', 'def._']);
@@ -535,7 +535,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, 2, 0);
assert.equal(editor.getModel().getValue(), 'this._._foo\na._foo\ndef._._foo');
assert.equal(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo');
}, ['this._', 'abc', 'def._']);
@@ -551,7 +551,7 @@ suite('SnippetController', () => {
codeSnippet = 'document';
controller.insert(codeSnippet, 3, 0);
assert.equal(editor.getModel().getValue(), '{document}\n{document && true}');
assert.equal(editor.getModel()!.getValue(), '{document}\n{document && true}');
}, ['{foo}', '{foo && true}']);
});
@@ -566,7 +566,7 @@ suite('SnippetController', () => {
codeSnippet = 'for (var ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
controller.insert(codeSnippet, 0, 0);
assert.equal(editor.getModel().getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
assert.equal(editor.getModel()!.getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
}, ['for (var i=0; i<len; i++) { }']);
@@ -579,7 +579,7 @@ suite('SnippetController', () => {
codeSnippet = 'for (let ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
controller.insert(codeSnippet, 0, 0);
assert.equal(editor.getModel().getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
assert.equal(editor.getModel()!.getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
}, ['for (var i=0; i<len; i++) { }']);

View File

@@ -15,8 +15,8 @@ import { Handler } from 'vs/editor/common/editorCommon';
suite('SnippetController2', function () {
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
for (const selection of editor.getSelections()) {
const actual = s.shift();
for (const selection of editor.getSelections()!) {
const actual = s.shift()!;
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
}
assert.equal(s.length, 0);

View File

@@ -106,7 +106,7 @@ suite('SnippetParser', () => {
}
while (marker.length > 0) {
let m = marker.pop();
let ctor = ctors.pop();
let ctor = ctors.pop()!;
assert.ok(m instanceof ctor);
}
assert.equal(marker.length, ctors.length);
@@ -748,4 +748,10 @@ suite('SnippetParser', () => {
let [, , clone] = snippet.children;
assertParent(clone);
});
test('Backspace can\'t be escaped in snippet variable transforms #65412', function () {
let snippet = new SnippetParser().parse('namespace ${TM_DIRECTORY/[\\/]/\\\\/g};');
assertMarker(snippet, Text, Variable, Text);
});
});

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
@@ -14,12 +14,12 @@ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
suite('SnippetSession', function () {
let editor: ICodeEditor;
let editor: IActiveCodeEditor;
let model: TextModel;
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
function assertSelections(editor: IActiveCodeEditor, ...s: Selection[]) {
for (const selection of editor.getSelections()) {
const actual = s.shift();
const actual = s.shift()!;
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
}
assert.equal(s.length, 0);
@@ -27,7 +27,7 @@ suite('SnippetSession', function () {
setup(function () {
model = TextModel.createFromString('function foo() {\n console.log(a);\n}');
editor = createTestCodeEditor({ model: model });
editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor;
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]);
assert.equal(model.getEOL(), '\n');
});
@@ -72,7 +72,7 @@ suite('SnippetSession', function () {
test('text edits & selection', function () {
const session = new SnippetSession(editor, 'foo${1:bar}foo$0');
session.insert();
assert.equal(editor.getModel().getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
assert.equal(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
session.next();
@@ -115,7 +115,7 @@ suite('SnippetSession', function () {
const session = new SnippetSession(editor, 'foo\n\t${1:bar}\n$0');
session.insert();
assert.equal(editor.getModel().getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
assert.equal(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(5, 9, 5, 12));
@@ -128,7 +128,7 @@ suite('SnippetSession', function () {
editor.setSelection(new Selection(2, 5, 2, 5));
const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', 0, 0, false);
session.insert();
assert.equal(editor.getModel().getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
assert.equal(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
});
test('snippets, selections -> next/prev', () => {
@@ -446,7 +446,7 @@ suite('SnippetSession', function () {
});
test('snippets, transform', function () {
editor.getModel().setValue('');
editor.getModel()!.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
const session = new SnippetSession(editor, '${1/foo/bar/}$0');
session.insert();
@@ -461,7 +461,7 @@ suite('SnippetSession', function () {
});
test('snippets, multi placeholder same index one transform', function () {
editor.getModel().setValue('');
editor.getModel()!.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
const session = new SnippetSession(editor, '$1 baz ${1/foo/bar/}$0');
session.insert();
@@ -476,7 +476,7 @@ suite('SnippetSession', function () {
});
test('snippets, transform example', function () {
editor.getModel().setValue('');
editor.getModel()!.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
const session = new SnippetSession(editor, '${1:name} : ${2:type}${3/\\s:=(.*)/${1:+ :=}${1}/};\n$0');
session.insert();
@@ -516,8 +516,8 @@ suite('SnippetSession', function () {
'}'
].join('\n');
editor.getModel().setValue(base);
editor.getModel().updateOptions({ insertSpaces: false });
editor.getModel()!.setValue(base);
editor.getModel()!.updateOptions({ insertSpaces: false });
editor.setSelection(new Selection(2, 2, 2, 2));
const session = new SnippetSession(editor, snippet);
@@ -538,7 +538,7 @@ suite('SnippetSession', function () {
});
test('snippets, transform example hit if', function () {
editor.getModel().setValue('');
editor.getModel()!.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
const session = new SnippetSession(editor, '${1:name} : ${2:type}${3/\\s:=(.*)/${1:+ :=}${1}/};\n$0');
session.insert();
@@ -561,43 +561,43 @@ suite('SnippetSession', function () {
});
test('Snippet placeholder index incorrect after using 2+ snippets in a row that each end with a placeholder, #30769', function () {
editor.getModel().setValue('');
editor.getModel()!.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
const session = new SnippetSession(editor, 'test ${1:replaceme}');
session.insert();
editor.trigger('test', 'type', { text: '1' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel().getValue(), 'test 1\n');
assert.equal(editor.getModel()!.getValue(), 'test 1\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '2' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\n');
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '3' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\ntest 3\n');
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '4' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel().getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
});
test('Snippet variable text isn\'t whitespace normalised, #31124', function () {
editor.getModel().setValue([
editor.getModel()!.setValue([
'start',
'\t\t-one',
'\t\t-two',
'end'
].join('\n'));
editor.getModel().updateOptions({ insertSpaces: false });
editor.getModel()!.updateOptions({ insertSpaces: false });
editor.setSelection(new Selection(2, 2, 3, 7));
new SnippetSession(editor, '<div>\n\t$TM_SELECTED_TEXT\n</div>$0').insert();
@@ -611,16 +611,16 @@ suite('SnippetSession', function () {
'end'
].join('\n');
assert.equal(editor.getModel().getValue(), expected);
assert.equal(editor.getModel()!.getValue(), expected);
editor.getModel().setValue([
editor.getModel()!.setValue([
'start',
'\t\t-one',
'\t-two',
'end'
].join('\n'));
editor.getModel().updateOptions({ insertSpaces: false });
editor.getModel()!.updateOptions({ insertSpaces: false });
editor.setSelection(new Selection(2, 2, 3, 7));
new SnippetSession(editor, '<div>\n\t$TM_SELECTED_TEXT\n</div>$0').insert();
@@ -634,11 +634,11 @@ suite('SnippetSession', function () {
'end'
].join('\n');
assert.equal(editor.getModel().getValue(), expected);
assert.equal(editor.getModel()!.getValue(), expected);
});
test('Selecting text from left to right, and choosing item messes up code, #31199', function () {
const model = editor.getModel();
const model = editor.getModel()!;
model.setValue('console.log');
let actual = SnippetSession.adjustSelection(model, new Selection(1, 12, 1, 9), 3, 0);

View File

@@ -33,7 +33,7 @@ suite('Snippet Variables Resolver', function () {
model.dispose();
});
function assertVariableResolve(resolver: VariableResolver, varName: string, expected: string) {
function assertVariableResolve(resolver: VariableResolver, varName: string, expected?: string) {
const snippet = new SnippetParser().parse(`$${varName}`);
const variable = <Variable>snippet.children[0];
variable.resolve(resolver);
@@ -208,10 +208,10 @@ suite('Snippet Variables Resolver', function () {
test('Add variable to insert value from clipboard to a snippet #40153', function () {
let readTextResult: string;
let readTextResult: string | null | undefined;
const clipboardService = new class implements IClipboardService {
_serviceBrand: any;
readText(): string { return readTextResult; }
readText(): any { return readTextResult; }
_throw = () => { throw new Error(); };
writeText = this._throw;
readFindText = this._throw;

View File

@@ -3,21 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer } from 'vs/base/common/filters';
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
import { isDisposable } from 'vs/base/common/lifecycle';
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
import { ISuggestionItem, ensureLowerCaseVariants } from './suggest';
import { CompletionItem } from './suggest';
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
import { CharCode } from 'vs/base/common/charCode';
export interface ICompletionItem extends ISuggestionItem {
matches?: number[];
score?: number;
idx?: number;
distance?: number;
word?: string;
}
type StrictCompletionItem = Required<CompletionItem>;
/* __GDPR__FRAGMENT__
"ICompletionStats" : {
@@ -47,7 +41,7 @@ const enum Refilter {
export class CompletionModel {
private readonly _items: ICompletionItem[];
private readonly _items: CompletionItem[];
private readonly _column: number;
private readonly _wordDistance: WordDistance;
private readonly _options: InternalSuggestOptions;
@@ -55,12 +49,12 @@ export class CompletionModel {
private _lineContext: LineContext;
private _refilterKind: Refilter;
private _filteredItems: ICompletionItem[];
private _filteredItems: StrictCompletionItem[];
private _isIncomplete: Set<CompletionItemProvider>;
private _stats: ICompletionStats;
constructor(
items: ISuggestionItem[],
items: CompletionItem[],
column: number,
lineContext: LineContext,
wordDistance: WordDistance,
@@ -105,7 +99,7 @@ export class CompletionModel {
}
}
get items(): ICompletionItem[] {
get items(): CompletionItem[] {
this._ensureCachedState();
return this._filteredItems;
}
@@ -115,10 +109,10 @@ export class CompletionModel {
return this._isIncomplete;
}
adopt(except: Set<CompletionItemProvider>): ISuggestionItem[] {
let res = new Array<ISuggestionItem>();
adopt(except: Set<CompletionItemProvider>): CompletionItem[] {
let res = new Array<CompletionItem>();
for (let i = 0; i < this._items.length;) {
if (!except.has(this._items[i].support)) {
if (!except.has(this._items[i].provider)) {
res.push(this._items[i]);
// unordered removed
@@ -155,7 +149,7 @@ export class CompletionModel {
// incrementally filter less
const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems;
const target: typeof source = [];
const target: StrictCompletionItem[] = [];
// picks a score function based on the number of
// items that we have to score/filter and based on the
@@ -165,21 +159,17 @@ export class CompletionModel {
for (let i = 0; i < source.length; i++) {
const item = source[i];
const { suggestion, container } = item;
// make sure _labelLow, _filterTextLow, _sortTextLow exist
ensureLowerCaseVariants(suggestion);
// collect those supports that signaled having
// an incomplete result
if (container.incomplete) {
this._isIncomplete.add(item.support);
if (item.container.incomplete) {
this._isIncomplete.add(item.provider);
}
// 'word' is that remainder of the current line that we
// filter and score against. In theory each suggestion uses a
// different word, but in practice not - that's why we cache
const overwriteBefore = item.position.column - suggestion.range.startColumn;
const overwriteBefore = item.position.column - item.completion.range.startColumn;
const wordLen = overwriteBefore + characterCountDelta - (item.position.column - this._column);
if (word.length !== wordLen) {
word = wordLen === 0 ? '' : leadingLineContent.slice(-wordLen);
@@ -196,8 +186,7 @@ export class CompletionModel {
// the fallback-sort using the initial sort order.
// use a score of `-100` because that is out of the
// bound of values `fuzzyScore` will return
item.score = -100;
item.matches = undefined;
item.score = FuzzyScore.Default;
} else {
// skip word characters that are whitespace until
@@ -215,40 +204,37 @@ export class CompletionModel {
if (wordPos >= wordLen) {
// the wordPos at which scoring starts is the whole word
// and therefore the same rules as not having a word apply
item.score = -100;
item.matches = [];
item.score = FuzzyScore.Default;
} else if (typeof suggestion.filterText === 'string') {
} else if (typeof item.completion.filterText === 'string') {
// when there is a `filterText` it must match the `word`.
// if it matches we check with the label to compute highlights
// and if that doesn't yield a result we have no highlights,
// despite having the match
let match = scoreFn(word, wordLow, wordPos, suggestion.filterText, suggestion._filterTextLow, 0, false);
let match = scoreFn(word, wordLow, wordPos, item.completion.filterText, item.filterTextLow!, 0, false);
if (!match) {
continue;
continue; // NO match
}
item.score = match[0];
item.matches = (fuzzyScore(word, wordLow, 0, suggestion.label, suggestion._labelLow, 0, true) || anyScore(word, suggestion.label))[1];
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
item.score[0] = match[0]; // use score from filterText
} else {
// by default match `word` against the `label`
let match = scoreFn(word, wordLow, wordPos, suggestion.label, suggestion._labelLow, 0, false);
if (match) {
item.score = match[0];
item.matches = match[1];
} else {
continue;
let match = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false);
if (!match) {
continue; // NO match
}
item.score = match;
}
}
item.idx = i;
item.distance = this._wordDistance.distance(item.position, suggestion);
target.push(item);
item.distance = this._wordDistance.distance(item.position, item.completion);
target.push(item as StrictCompletionItem);
// update stats
this._stats.suggestionCount++;
switch (suggestion.kind) {
switch (item.completion.kind) {
case CompletionItemKind.Snippet: this._stats.snippetCount++; break;
case CompletionItemKind.Text: this._stats.textCount++; break;
}
@@ -258,10 +244,10 @@ export class CompletionModel {
this._refilterKind = Refilter.Nothing;
}
private static _compareCompletionItems(a: ICompletionItem, b: ICompletionItem): number {
if (a.score > b.score) {
private static _compareCompletionItems(a: StrictCompletionItem, b: StrictCompletionItem): number {
if (a.score[0] > b.score[0]) {
return -1;
} else if (a.score < b.score) {
} else if (a.score[0] < b.score[0]) {
return 1;
} else if (a.distance < b.distance) {
return -1;
@@ -276,22 +262,22 @@ export class CompletionModel {
}
}
private static _compareCompletionItemsSnippetsDown(a: ICompletionItem, b: ICompletionItem): number {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === CompletionItemKind.Snippet) {
private static _compareCompletionItemsSnippetsDown(a: StrictCompletionItem, b: StrictCompletionItem): number {
if (a.completion.kind !== b.completion.kind) {
if (a.completion.kind === CompletionItemKind.Snippet) {
return 1;
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
} else if (b.completion.kind === CompletionItemKind.Snippet) {
return -1;
}
}
return CompletionModel._compareCompletionItems(a, b);
}
private static _compareCompletionItemsSnippetsUp(a: ICompletionItem, b: ICompletionItem): number {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === CompletionItemKind.Snippet) {
private static _compareCompletionItemsSnippetsUp(a: StrictCompletionItem, b: StrictCompletionItem): number {
if (a.completion.kind !== b.completion.kind) {
if (a.completion.kind === CompletionItemKind.Snippet) {
return -1;
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
} else if (b.completion.kind === CompletionItemKind.Snippet) {
return 1;
}
}

View File

@@ -56,7 +56,14 @@
height: 100%;
}
.monaco-editor .suggest-widget .monaco-list {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
/** Styles for each row in the list element **/
@@ -68,6 +75,8 @@
background-repeat: no-repeat;
background-position: 2px 2px;
white-space: nowrap;
cursor: pointer;
touch-action: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents {
@@ -173,7 +182,7 @@
background-image: url('Misc_16x.svg');
background-repeat: no-repeat;
background-position: center;
background-size: contain;
background-size: 75%;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method::before,

View File

@@ -10,12 +10,13 @@ import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionProviderRegistry, CompletionContext, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes';
import * as modes from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Range } from 'vs/editor/common/core/range';
import { FuzzyScore } from 'vs/base/common/filters';
export const Context = {
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
@@ -24,23 +25,77 @@ export const Context = {
AcceptSuggestionsOnEnter: new RawContextKey<boolean>('acceptSuggestionOnEnter', true)
};
export interface ISuggestionItem {
position: IPosition;
suggestion: CompletionItem;
container: CompletionList;
support: CompletionItemProvider;
resolve(token: CancellationToken): Thenable<void>;
export class CompletionItem {
_brand: 'ISuggestionItem';
readonly resolve: (token: CancellationToken) => Promise<void>;
// perf
readonly labelLow: string;
readonly sortTextLow?: string;
readonly filterTextLow?: string;
// sorting, filtering
score: FuzzyScore = FuzzyScore.Default;
distance: number = 0;
idx?: number;
word?: string;
constructor(
readonly position: IPosition,
readonly completion: modes.CompletionItem,
readonly container: modes.CompletionList,
readonly provider: modes.CompletionItemProvider,
model: ITextModel
) {
// ensure lower-variants (perf)
this.labelLow = completion.label.toLowerCase();
this.sortTextLow = completion.sortText && completion.sortText.toLowerCase();
this.filterTextLow = completion.filterText && completion.filterText.toLowerCase();
// create the suggestion resolver
const { resolveCompletionItem } = provider;
if (typeof resolveCompletionItem !== 'function') {
this.resolve = () => Promise.resolve();
} else {
let cached: Promise<void> | undefined;
this.resolve = (token) => {
if (!cached) {
let isDone = false;
cached = Promise.resolve(resolveCompletionItem.call(provider, model, position, completion, token)).then(value => {
assign(completion, value);
isDone = true;
}, err => {
if (isPromiseCanceledError(err)) {
// the IPC queue will reject the request with the
// cancellation error -> reset cached
cached = undefined;
}
});
token.onCancellationRequested(() => {
if (!isDone) {
// cancellation after the request has been
// dispatched -> reset cache
cached = undefined;
}
});
}
return cached;
};
}
}
}
export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none';
let _snippetSuggestSupport: CompletionItemProvider;
let _snippetSuggestSupport: modes.CompletionItemProvider;
export function getSnippetSuggestSupport(): CompletionItemProvider {
export function getSnippetSuggestSupport(): modes.CompletionItemProvider {
return _snippetSuggestSupport;
}
export function setSnippetSuggestSupport(support: CompletionItemProvider): CompletionItemProvider {
export function setSnippetSuggestSupport(support: modes.CompletionItemProvider): modes.CompletionItemProvider {
const old = _snippetSuggestSupport;
_snippetSuggestSupport = support;
return old;
@@ -50,12 +105,12 @@ export function provideSuggestionItems(
model: ITextModel,
position: Position,
snippetConfig: SnippetConfig = 'bottom',
onlyFrom?: CompletionItemProvider[],
context?: CompletionContext,
onlyFrom?: modes.CompletionItemProvider[],
context?: modes.CompletionContext,
token: CancellationToken = CancellationToken.None
): Promise<ISuggestionItem[]> {
): Promise<CompletionItem[]> {
const allSuggestions: ISuggestionItem[] = [];
const allSuggestions: CompletionItem[] = [];
const acceptSuggestion = createSuggesionFilter(snippetConfig);
const wordUntil = model.getWordUntilPosition(position);
@@ -64,27 +119,27 @@ export function provideSuggestionItems(
position = position.clone();
// get provider groups, always add snippet suggestion provider
const supports = CompletionProviderRegistry.orderedGroups(model);
const supports = modes.CompletionProviderRegistry.orderedGroups(model);
// add snippets provider unless turned off
if (snippetConfig !== 'none' && _snippetSuggestSupport) {
supports.unshift([_snippetSuggestSupport]);
}
const suggestConext = context || { triggerKind: CompletionTriggerKind.Invoke };
const suggestConext = context || { triggerKind: modes.CompletionTriggerKind.Invoke };
// add suggestions from contributed providers - providers are ordered in groups of
// equal score and once a group produces a result the process stops
let hasResult = false;
const factory = supports.map(supports => () => {
// for each support in the group ask for suggestions
return Promise.all(supports.map(support => {
return Promise.all(supports.map(provider => {
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(support) < 0) {
if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(provider) < 0) {
return undefined;
}
return Promise.resolve(support.provideCompletionItems(model, position, suggestConext, token)).then(container => {
return Promise.resolve(provider.provideCompletionItems(model, position, suggestConext, token)).then(container => {
const len = allSuggestions.length;
@@ -97,21 +152,12 @@ export function provideSuggestionItems(
suggestion.range = defaultRange;
}
// fill in lower-case text
ensureLowerCaseVariants(suggestion);
allSuggestions.push({
position,
container,
suggestion,
support,
resolve: createSuggestionResolver(support, suggestion, model, position)
});
allSuggestions.push(new CompletionItem(position, suggestion, container, provider, model));
}
}
}
if (len !== allSuggestions.length && support !== _snippetSuggestSupport) {
if (len !== allSuggestions.length && provider !== _snippetSuggestSupport) {
hasResult = true;
}
@@ -139,101 +185,55 @@ export function provideSuggestionItems(
return result;
}
export function ensureLowerCaseVariants(suggestion: CompletionItem) {
if (!suggestion._labelLow) {
suggestion._labelLow = suggestion.label.toLowerCase();
}
if (suggestion.sortText && !suggestion._sortTextLow) {
suggestion._sortTextLow = suggestion.sortText.toLowerCase();
}
if (suggestion.filterText && !suggestion._filterTextLow) {
suggestion._filterTextLow = suggestion.filterText.toLowerCase();
}
}
function createSuggestionResolver(provider: CompletionItemProvider, suggestion: CompletionItem, model: ITextModel, position: Position): (token: CancellationToken) => Promise<void> {
const { resolveCompletionItem } = provider;
if (typeof resolveCompletionItem !== 'function') {
return () => Promise.resolve();
}
let cached: Promise<void> | undefined;
return (token) => {
if (!cached) {
let isDone = false;
cached = Promise.resolve(provider.resolveCompletionItem!(model, position, suggestion, token)).then(value => {
assign(suggestion, value);
isDone = true;
}, err => {
if (isPromiseCanceledError(err)) {
// the IPC queue will reject the request with the
// cancellation error -> reset cached
cached = undefined;
}
});
token.onCancellationRequested(() => {
if (!isDone) {
// cancellation after the request has been
// dispatched -> reset cache
cached = undefined;
}
});
}
return cached;
};
}
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: CompletionItem) => boolean {
function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: modes.CompletionItem) => boolean {
if (snippetConfig === 'none') {
return suggestion => suggestion.kind !== CompletionItemKind.Snippet;
return suggestion => suggestion.kind !== modes.CompletionItemKind.Snippet;
} else {
return () => true;
}
}
function defaultComparator(a: ISuggestionItem, b: ISuggestionItem): number {
function defaultComparator(a: CompletionItem, b: CompletionItem): number {
// check with 'sortText'
if (a.suggestion._sortTextLow && b.suggestion._sortTextLow) {
if (a.suggestion._sortTextLow < b.suggestion._sortTextLow) {
if (a.sortTextLow && b.sortTextLow) {
if (a.sortTextLow < b.sortTextLow) {
return -1;
} else if (a.suggestion._sortTextLow > b.suggestion._sortTextLow) {
} else if (a.sortTextLow > b.sortTextLow) {
return 1;
}
}
// check with 'label'
if (a.suggestion.label < b.suggestion.label) {
if (a.completion.label < b.completion.label) {
return -1;
} else if (a.suggestion.label > b.suggestion.label) {
} else if (a.completion.label > b.completion.label) {
return 1;
}
// check with 'type'
return a.suggestion.kind - b.suggestion.kind;
return a.completion.kind - b.completion.kind;
}
function snippetUpComparator(a: ISuggestionItem, b: ISuggestionItem): number {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === CompletionItemKind.Snippet) {
function snippetUpComparator(a: CompletionItem, b: CompletionItem): number {
if (a.completion.kind !== b.completion.kind) {
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
return -1;
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
return 1;
}
}
return defaultComparator(a, b);
}
function snippetDownComparator(a: ISuggestionItem, b: ISuggestionItem): number {
if (a.suggestion.kind !== b.suggestion.kind) {
if (a.suggestion.kind === CompletionItemKind.Snippet) {
function snippetDownComparator(a: CompletionItem, b: CompletionItem): number {
if (a.completion.kind !== b.completion.kind) {
if (a.completion.kind === modes.CompletionItemKind.Snippet) {
return 1;
} else if (b.suggestion.kind === CompletionItemKind.Snippet) {
} else if (b.completion.kind === modes.CompletionItemKind.Snippet) {
return -1;
}
}
return defaultComparator(a, b);
}
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISuggestionItem, b: ISuggestionItem) => number {
export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: CompletionItem, b: CompletionItem) => number {
if (snippetConfig === 'top') {
return snippetUpComparator;
} else if (snippetConfig === 'bottom') {
@@ -245,12 +245,12 @@ export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: ISugg
registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
const result: CompletionList = {
const result: modes.CompletionList = {
incomplete: false,
suggestions: []
};
let resolving: Thenable<any>[] = [];
let resolving: Promise<any>[] = [];
let maxItemsToResolve = args['maxItemsToResolve'] || 0;
return provideSuggestionItems(model, position).then(items => {
@@ -259,7 +259,7 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
resolving.push(item.resolve(CancellationToken.None));
}
result.incomplete = result.incomplete || item.container.incomplete;
result.suggestions.push(item.suggestion);
result.suggestions.push(item.completion);
}
}).then(() => {
return Promise.all(resolving);
@@ -269,15 +269,15 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
});
interface SuggestController extends IEditorContribution {
triggerSuggest(onlyFrom?: CompletionItemProvider[]): void;
triggerSuggest(onlyFrom?: modes.CompletionItemProvider[]): void;
}
let _provider = new class implements CompletionItemProvider {
let _provider = new class implements modes.CompletionItemProvider {
onlyOnceSuggestions: CompletionItem[] = [];
onlyOnceSuggestions: modes.CompletionItem[] = [];
provideCompletionItems(): CompletionList {
provideCompletionItems(): modes.CompletionList {
let suggestions = this.onlyOnceSuggestions.slice(0);
let result = { suggestions };
this.onlyOnceSuggestions.length = 0;
@@ -285,9 +285,9 @@ let _provider = new class implements CompletionItemProvider {
}
};
CompletionProviderRegistry.register('*', _provider);
modes.CompletionProviderRegistry.register('*', _provider);
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: CompletionItem[]) {
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) {
setTimeout(() => {
_provider.onlyOnceSuggestions.push(...suggestions);
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest([_provider]);

View File

@@ -16,8 +16,8 @@ export class SuggestAlternatives {
private readonly _ckOtherSuggestions: IContextKey<boolean>;
private _index: number;
private _model: CompletionModel;
private _acceptNext: (selected: ISelectedSuggestion) => any;
private _model: CompletionModel | undefined;
private _acceptNext: ((selected: ISelectedSuggestion) => any) | undefined;
private _listener: IDisposable;
private _ignore: boolean;
@@ -73,7 +73,7 @@ export class SuggestAlternatives {
if (newIndex === index) {
break;
}
if (!model.items[newIndex].suggestion.additionalTextEdits) {
if (!model.items[newIndex].completion.additionalTextEdits) {
break;
}
}
@@ -96,7 +96,7 @@ export class SuggestAlternatives {
try {
this._ignore = true;
this._index = SuggestAlternatives._moveIndex(fwd, this._model, this._index);
this._acceptNext({ index: this._index, item: this._model.items[this._index], model: this._model });
this._acceptNext!({ index: this._index, item: this._model.items[this._index], model: this._model });
} finally {
this._ignore = false;
}

Some files were not shown because too many files have changed in this diff Show More