Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2

This commit is contained in:
ADS Merger
2020-04-23 02:50:35 +00:00
committed by Anthony Dresser
parent 3603f55d97
commit 7f1d8fc32f
659 changed files with 22709 additions and 12497 deletions

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { TimeoutTimer } from 'vs/base/common/async';
@@ -119,7 +118,7 @@ export class MouseHandler extends ViewEventHandler {
e.stopPropagation();
}
};
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, browser.isEdge ? 'mousewheel' : 'wheel', onMouseWheel, { capture: true, passive: false }));
this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false }));
this._context.addEventHandler(this);
}

View File

@@ -125,6 +125,7 @@ export class TextAreaHandler extends ViewPart {
this.textArea.setAttribute('spellcheck', 'false');
this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
this.textArea.setAttribute('role', 'textbox');
this.textArea.setAttribute('aria-roledescription', nls.localize('editor', "editor"));
this.textArea.setAttribute('aria-multiline', 'true');
this.textArea.setAttribute('aria-haspopup', 'false');
this.textArea.setAttribute('aria-autocomplete', 'both');

View File

@@ -136,6 +136,8 @@ export class View extends ViewEventHandler {
this.domNode = createFastDomNode(document.createElement('div'));
this.domNode.setClassName(this.getEditorClassName());
// Set role 'code' for better screen reader support https://github.com/microsoft/vscode/issues/93438
this.domNode.setAttribute('role', 'code');
this.overflowGuardContainer = createFastDomNode(document.createElement('div'));
PartFingerprints.write(this.overflowGuardContainer, PartFingerprint.OverflowGuard);

View File

@@ -49,6 +49,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
interface IEditorDiffDecorations {
decorations: IModelDeltaDecoration[];
@@ -159,6 +160,10 @@ class VisualEditorState {
let DIFF_EDITOR_ID = 0;
const diffInsertIcon = registerIcon('diff-insert', Codicon.add);
const diffRemoveIcon = registerIcon('diff-remove', Codicon.remove);
export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffEditor {
private static readonly ONE_OVERVIEW_WIDTH = 15;
@@ -1630,7 +1635,7 @@ const DECORATIONS = {
}),
lineInsertWithSign: ModelDecorationOptions.register({
className: 'line-insert',
linesDecorationsClassName: 'insert-sign codicon codicon-add',
linesDecorationsClassName: 'insert-sign ' + diffInsertIcon.classNames,
marginClassName: 'line-insert',
isWholeLine: true
}),
@@ -1642,7 +1647,7 @@ const DECORATIONS = {
}),
lineDeleteWithSign: ModelDecorationOptions.register({
className: 'line-delete',
linesDecorationsClassName: 'delete-sign codicon codicon-remove',
linesDecorationsClassName: 'delete-sign ' + diffRemoveIcon.classNames,
marginClassName: 'line-delete',
isWholeLine: true
@@ -2101,7 +2106,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
if (this.renderIndicators) {
let index = lineNumber - lineChange.originalStartLineNumber;
marginHTML = marginHTML.concat([
`<div class="delete-sign codicon codicon-remove" style="position:absolute;top:${index * lineHeight}px;width:${lineDecorationsWidth}px;height:${lineHeight}px;right:0;"></div>`
`<div class="delete-sign ${diffRemoveIcon.classNames}" style="position:absolute;top:${index * lineHeight}px;width:${lineDecorationsWidth}px;height:${lineHeight}px;right:0;"></div>`
]);
}
}

View File

@@ -31,6 +31,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { Constants } from 'vs/base/common/uint';
import { registerIcon, Codicon } from 'vs/base/common/codicons';
const DIFF_LINES_PADDING = 3;
@@ -72,6 +73,10 @@ class Diff {
}
}
const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add);
const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove);
const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close);
export class DiffReview extends Disposable {
private readonly _diffEditor: DiffEditorWidget;
@@ -99,7 +104,7 @@ export class DiffReview extends Disposable {
this.actionBarContainer.domNode
));
this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review codicon-close', true, () => {
this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + diffReviewCloseIcon.classNames, true, () => {
this.hide();
return Promise.resolve(null);
}), { label: false, icon: true });
@@ -639,17 +644,17 @@ export class DiffReview extends Disposable {
let rowClassName: string = 'diff-review-row';
let lineNumbersExtraClassName: string = '';
const spacerClassName: string = 'diff-review-spacer';
let spacerCodiconName: string | null = null;
let spacerIcon: Codicon | null = null;
switch (type) {
case DiffEntryType.Insert:
rowClassName = 'diff-review-row line-insert';
lineNumbersExtraClassName = ' char-insert';
spacerCodiconName = 'codicon codicon-add';
spacerIcon = diffReviewInsertIcon;
break;
case DiffEntryType.Delete:
rowClassName = 'diff-review-row line-delete';
lineNumbersExtraClassName = ' char-delete';
spacerCodiconName = 'codicon codicon-remove';
spacerIcon = diffReviewRemoveIcon;
break;
}
@@ -713,9 +718,9 @@ export class DiffReview extends Disposable {
const spacer = document.createElement('span');
spacer.className = spacerClassName;
if (spacerCodiconName) {
if (spacerIcon) {
const spacerCodicon = document.createElement('span');
spacerCodicon.className = spacerCodiconName;
spacerCodicon.className = spacerIcon.classNames;
spacerCodicon.innerHTML = '&#160;&#160;';
spacer.appendChild(spacerCodicon);
} else {

View File

@@ -13,6 +13,7 @@ import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrow
import { Range } from 'vs/editor/common/core/range';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Codicon } from 'vs/base/common/codicons';
export interface IDiffLinesChange {
readonly originalStartLineNumber: number;
@@ -57,7 +58,7 @@ export class InlineDiffMargin extends Disposable {
this._marginDomNode.style.zIndex = '10';
this._diffActions = document.createElement('div');
this._diffActions.className = 'codicon codicon-lightbulb lightbulb-glyph';
this._diffActions.className = Codicon.lightBulb.classNames + ' lightbulb-glyph';
this._diffActions.style.position = 'absolute';
const lineHeight = editor.getOption(EditorOption.lineHeight);
const lineFeed = editor.getModel()!.getEOL();

View File

@@ -1073,7 +1073,7 @@ class EditorComments extends BaseEditorOption<EditorOption.comments, EditorComme
}
public validate(_input: any): EditorCommentsOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorCommentsOptions;
@@ -1259,6 +1259,10 @@ export interface IEditorFindOptions {
* Controls if the Find Widget should read or modify the shared find clipboard on macOS
*/
globalFindClipboard?: boolean;
/**
* Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found
*/
loop?: boolean;
}
export type EditorFindOptions = Readonly<Required<IEditorFindOptions>>;
@@ -1270,7 +1274,8 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
seedSearchStringFromSelection: true,
autoFindInSelection: 'never',
globalFindClipboard: false,
addExtraSpaceOnTop: true
addExtraSpaceOnTop: true,
loop: true
};
super(
EditorOption.find, 'find', defaults,
@@ -1301,13 +1306,19 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
type: 'boolean',
default: defaults.addExtraSpaceOnTop,
description: nls.localize('find.addExtraSpaceOnTop', "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.")
}
},
'editor.find.loop': {
type: 'boolean',
default: defaults.loop,
description: nls.localize('find.loop', "Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found.")
},
}
);
}
public validate(_input: any): EditorFindOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorFindOptions;
@@ -1317,7 +1328,8 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
? (_input.autoFindInSelection ? 'always' : 'never')
: EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']),
globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard),
addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop)
addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop),
loop: EditorBooleanOption.boolean(input.loop, this.defaultValue.loop),
};
}
}
@@ -1532,7 +1544,7 @@ class EditorGoToLocation extends BaseEditorOption<EditorOption.gotoLocation, GoT
}
public validate(_input: any): GoToLocationOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IGotoLocationOptions;
@@ -1610,7 +1622,7 @@ class EditorHover extends BaseEditorOption<EditorOption.hover, EditorHoverOption
}
public validate(_input: any): EditorHoverOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorHoverOptions;
@@ -2036,7 +2048,7 @@ class EditorLightbulb extends BaseEditorOption<EditorOption.lightbulb, EditorLig
}
public validate(_input: any): EditorLightbulbOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorLightbulbOptions;
@@ -2180,7 +2192,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
}
public validate(_input: any): EditorMinimapOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorMinimapOptions;
@@ -2255,7 +2267,7 @@ class EditorPadding extends BaseEditorOption<EditorOption.padding, InternalEdito
}
public validate(_input: any): InternalEditorPaddingOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorPaddingOptions;
@@ -2313,7 +2325,7 @@ class EditorParameterHints extends BaseEditorOption<EditorOption.parameterHints,
}
public validate(_input: any): InternalParameterHintOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorParameterHintOptions;
@@ -2403,7 +2415,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
if (typeof _input === 'boolean') {
return _input;
}
if (typeof _input === 'object') {
if (_input && typeof _input === 'object') {
const input = _input as IQuickSuggestionsOptions;
const opts = {
other: EditorBooleanOption.boolean(input.other, this.defaultValue.other),
@@ -2553,7 +2565,7 @@ class EditorRulers extends BaseEditorOption<EditorOption.rulers, IRulerOption[]>
column: EditorIntOption.clampedInt(_element, 0, 0, 10000),
color: null
});
} else if (typeof _element === 'object') {
} else if (_element && typeof _element === 'object') {
const element = _element as IRulerOption;
rulers.push({
column: EditorIntOption.clampedInt(element.column, 0, 0, 10000),
@@ -2687,7 +2699,7 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
}
public validate(_input: any): InternalEditorScrollbarOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorScrollbarOptions;
@@ -3108,7 +3120,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
}
public validate(_input: any): InternalSuggestOptions {
if (typeof _input !== 'object') {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as ISuggestOptions;

View File

@@ -10,6 +10,7 @@ export namespace EditorContextKeys {
export const editorSimpleInput = new RawContextKey<boolean>('editorSimpleInput', false);
/**
* A context key that is set when the editor's text has focus (cursor is blinking).
* Is false when focus is in simple editor widgets (repl input, scm commit input).
*/
export const editorTextFocus = new RawContextKey<boolean>('editorTextFocus', false);
/**

View File

@@ -56,77 +56,80 @@ export function ensureValidWordDefinition(wordDefinition?: RegExp | null): RegEx
return result;
}
function getWordAtPosFast(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null {
// find whitespace enclosed text around column and match from there
const _defaultConfig = {
maxLen: 1000,
windowSize: 15,
timeBudget: 150
};
let pos = column - 1 - textOffset;
let start = text.lastIndexOf(' ', pos - 1) + 1;
export function getWordAtText(column: number, wordDefinition: RegExp, text: string, textOffset: number, config = _defaultConfig): IWordAtPosition | null {
wordDefinition.lastIndex = start;
if (text.length > config.maxLen) {
// don't throw strings that long at the regexp
// but use a sub-string in which a word must occur
let start = column - config.maxLen / 2;
if (start < 0) {
textOffset += column;
start = 0;
} else {
textOffset += start;
}
text = text.substring(start, column + config.maxLen / 2);
return getWordAtText(column, wordDefinition, text, textOffset, config);
}
const t1 = Date.now();
const pos = column - 1 - textOffset;
let prevRegexIndex = -1;
let match: RegExpMatchArray | null = null;
for (let i = 1; ; i++) {
// check time budget
if (Date.now() - t1 >= config.timeBudget) {
// break;
}
// reset the index at which the regexp should start matching, also know where it
// should stop so that subsequent search don't repeat previous searches
const regexIndex = pos - config.windowSize * i;
wordDefinition.lastIndex = Math.max(0, regexIndex);
match = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex);
// stop: found something
if (match) {
break;
}
// stop: searched at start
if (regexIndex <= 0) {
break;
}
prevRegexIndex = regexIndex;
}
if (match) {
let result = {
word: match[0],
startColumn: textOffset + 1 + match.index!,
endColumn: textOffset + 1 + wordDefinition.lastIndex
};
wordDefinition.lastIndex = 0;
return result;
}
return null;
}
function _findRegexMatchEnclosingPosition(wordDefinition: RegExp, text: string, pos: number, stopPos: number): RegExpMatchArray | null {
let match: RegExpMatchArray | null;
while (match = wordDefinition.exec(text)) {
const matchIndex = match.index || 0;
if (matchIndex <= pos && wordDefinition.lastIndex >= pos) {
return {
word: match[0],
startColumn: textOffset + 1 + matchIndex,
endColumn: textOffset + 1 + wordDefinition.lastIndex
};
}
}
return null;
}
function getWordAtPosSlow(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null {
// matches all words starting at the beginning
// of the input until it finds a match that encloses
// the desired column. slow but correct
let pos = column - 1 - textOffset;
wordDefinition.lastIndex = 0;
let match: RegExpMatchArray | null;
while (match = wordDefinition.exec(text)) {
const matchIndex = match.index || 0;
if (matchIndex > pos) {
// |nW -> matched only after the pos
return match;
} else if (stopPos > 0 && matchIndex > stopPos) {
return null;
} else if (wordDefinition.lastIndex >= pos) {
// W|W -> match encloses pos
return {
word: match[0],
startColumn: textOffset + 1 + matchIndex,
endColumn: textOffset + 1 + wordDefinition.lastIndex
};
}
}
return null;
}
export function getWordAtText(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null {
// if `words` can contain whitespace character we have to use the slow variant
// otherwise we use the fast variant of finding a word
wordDefinition.lastIndex = 0;
let match = wordDefinition.exec(text);
if (!match) {
return null;
}
// todo@joh the `match` could already be the (first) word
const ret = match[0].indexOf(' ') >= 0
// did match a word which contains a space character -> use slow word find
? getWordAtPosSlow(column, wordDefinition, text, textOffset)
// sane word definition -> use fast word find
: getWordAtPosFast(column, wordDefinition, text, textOffset);
// both (getWordAtPosFast and getWordAtPosSlow) leave the wordDefinition-RegExp
// in an undefined state and to not confuse other users of the wordDefinition
// we reset the lastIndex
wordDefinition.lastIndex = 0;
return ret;
}

View File

@@ -19,6 +19,7 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { iconRegistry, Codicon } from 'vs/base/common/codicons';
/**
* Open ended enum at runtime
@@ -359,7 +360,13 @@ export const completionKindToCssClass = (function () {
data[CompletionItemKind.Issue] = 'issues';
return function (kind: CompletionItemKind) {
return data[kind] || 'property';
const name = data[kind];
let codicon = name && iconRegistry.get(name);
if (!codicon) {
console.info('No codicon found for CompletionItemKind ' + kind);
codicon = Codicon.symbolProperty;
}
return codicon.classNames;
};
})();
@@ -697,6 +704,12 @@ export interface SignatureInformation {
* The parameters of this signature.
*/
parameters: ParameterInformation[];
/**
* Index of the active parameter.
*
* If provided, this is used in place of `SignatureHelp.activeSignature`.
*/
activeParameter?: number;
}
/**
* Signature help represents the signature of something
@@ -1037,7 +1050,13 @@ export namespace SymbolKinds {
* @internal
*/
export function toCssClassName(kind: SymbolKind, inline?: boolean): string {
return `codicon ${inline ? 'inline' : 'block'} codicon-symbol-${byKind.get(kind) || 'property'}`;
const symbolName = byKind.get(kind);
let codicon = symbolName && iconRegistry.get('symbol-' + symbolName);
if (!codicon) {
console.info('No codicon found for SymbolKind ' + kind);
codicon = Codicon.symbolProperty;
}
return `${inline ? 'inline' : 'block'} ${codicon.classNames}`;
}
}
@@ -1393,7 +1412,10 @@ export interface RenameProvider {
export interface AuthenticationSession {
id: string;
getAccessToken(): Thenable<string>;
accountName: string;
account: {
displayName: string;
id: string;
}
}
/**

View File

@@ -114,7 +114,7 @@ class EditorScrollable extends Disposable {
scrollWidth: dimensions.scrollWidth,
height: dimensions.height,
scrollHeight: dimensions.scrollHeight
});
}, true);
const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);

View File

@@ -35,6 +35,12 @@ export interface CodeActionSet extends IDisposable {
class ManagedCodeActionSet extends Disposable implements CodeActionSet {
private static codeActionsComparator(a: modes.CodeAction, b: modes.CodeAction): number {
if (a.isPreferred && !b.isPreferred) {
return -1;
} else if (!a.isPreferred && b.isPreferred) {
return 1;
}
if (isNonEmptyArray(a.diagnostics)) {
if (isNonEmptyArray(b.diagnostics)) {
return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message);

View File

@@ -19,6 +19,7 @@ import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry';
import { Gesture } from 'vs/base/browser/touch';
import type { CodeActionTrigger } from 'vs/editor/contrib/codeAction/types';
import { Codicon } from 'vs/base/common/codicons';
namespace LightBulbState {
@@ -63,7 +64,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
) {
super();
this._domNode = document.createElement('div');
this._domNode.className = 'codicon codicon-lightbulb';
this._domNode.className = Codicon.lightBulb.classNames;
this._editor.addContentWidget(this);
@@ -121,8 +122,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
}
}));
this._updateLightBulbTitle();
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this));
this._updateLightBulbTitleAndIcon();
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitleAndIcon, this));
}
dispose(): void {
@@ -184,7 +185,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
position: { lineNumber: effectiveLineNumber, column: 1 },
preference: LightBulbWidget._posPref
});
dom.toggleClass(this._domNode, 'codicon-lightbulb-autofix', actions.hasAutoFix);
this._editor.layoutContentWidget(this);
}
@@ -197,11 +197,15 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
private set state(value) {
this._state = value;
this._updateLightBulbTitle();
this._updateLightBulbTitleAndIcon();
}
private _updateLightBulbTitle(): void {
private _updateLightBulbTitleAndIcon(): void {
if (this.state.type === LightBulbState.Type.Showing && this.state.actions.hasAutoFix) {
// update icon
dom.removeClasses(this._domNode, Codicon.lightBulb.classNames);
dom.addClasses(this._domNode, Codicon.lightbulbAutofix.classNames);
const preferredKb = this._keybindingService.lookupKeybinding(this._preferredFixActionId);
if (preferredKb) {
this.title = nls.localize('prefferedQuickFixWithKb', "Show Fixes. Preferred Fix Available ({0})", preferredKb.getLabel());
@@ -209,6 +213,10 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
}
}
// update icon
dom.removeClasses(this._domNode, Codicon.lightbulbAutofix.classNames);
dom.addClasses(this._domNode, Codicon.lightBulb.classNames);
const kb = this._keybindingService.lookupKeybinding(this._quickFixActionId);
if (kb) {
this.title = nls.localize('quickFixWithKb', "Show Fixes ({0})", kb.getLabel());
@@ -228,7 +236,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground);
if (editorLightBulbForegroundColor) {
collector.addRule(`
.monaco-editor .contentWidgets .codicon-lightbulb {
.monaco-editor .contentWidgets ${Codicon.lightBulb.cssSelector} {
color: ${editorLightBulbForegroundColor};
}`);
}
@@ -237,7 +245,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const editorLightBulbAutoFixForegroundColor = theme.getColor(editorLightBulbAutoFixForeground);
if (editorLightBulbAutoFixForegroundColor) {
collector.addRule(`
.monaco-editor .contentWidgets .codicon-lightbulb-autofix {
.monaco-editor .contentWidgets ${Codicon.lightbulbAutofix.cssSelector} {
color: ${editorLightBulbAutoFixForegroundColor};
}`);
}

View File

@@ -67,7 +67,7 @@ export class CodeLensContribution implements IEditorContribution {
}));
this._onModelChange();
this._styleClassName = hash(this._editor.getId()).toString(16);
this._styleClassName = '_' + hash(this._editor.getId()).toString(16);
this._styleElement = dom.createStyleSheet(
dom.isInShadowDOM(this._editor.getContainerDomNode())
? this._editor.getContainerDomNode()

View File

@@ -5,7 +5,6 @@
import { binarySearch, coalesceInPlace, equals } from 'vs/base/common/arrays';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { first, forEach, size } from 'vs/base/common/collections';
import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { LRUCache } from 'vs/base/common/map';
import { commonPrefixLength } from 'vs/base/common/strings';
@@ -14,18 +13,21 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
import { MarkerSeverity } from 'vs/platform/markers/common/markers';
import { Iterable } from 'vs/base/common/iterator';
import { MovingAverage } from 'vs/base/common/numbers';
import { URI } from 'vs/base/common/uri';
export abstract class TreeElement {
abstract id: string;
abstract children: { [id: string]: TreeElement };
abstract children: Map<string, TreeElement>;
abstract parent: TreeElement | undefined;
abstract adopt(newParent: TreeElement): TreeElement;
remove(): void {
if (this.parent) {
delete this.parent.children[this.id];
this.parent.children.delete(this.id);
}
}
@@ -37,13 +39,13 @@ export abstract class TreeElement {
candidateId = `${container.id}/${candidate}`;
} else {
candidateId = `${container.id}/${candidate.name}`;
if (container.children[candidateId] !== undefined) {
if (container.children.get(candidateId) !== undefined) {
candidateId = `${container.id}/${candidate.name}_${candidate.range.startLineNumber}_${candidate.range.startColumn}`;
}
}
let id = candidateId;
for (let i = 0; container.children[id] !== undefined; i++) {
for (let i = 0; container.children.get(id) !== undefined; i++) {
id = `${candidateId}_${i}`;
}
@@ -61,8 +63,8 @@ export abstract class TreeElement {
if (len < element.id.length) {
return undefined;
}
for (const key in element.children) {
let candidate = TreeElement.getElementById(id, element.children[key]);
for (const [, child] of element.children) {
let candidate = TreeElement.getElementById(id, child);
if (candidate) {
return candidate;
}
@@ -72,17 +74,14 @@ export abstract class TreeElement {
static size(element: TreeElement): number {
let res = 1;
for (const key in element.children) {
res += TreeElement.size(element.children[key]);
for (const [, child] of element.children) {
res += TreeElement.size(child);
}
return res;
}
static empty(element: TreeElement): boolean {
for (const _key in element.children) {
return false;
}
return true;
return element.children.size === 0;
}
}
@@ -96,7 +95,7 @@ export interface IOutlineMarker {
export class OutlineElement extends TreeElement {
children: { [id: string]: OutlineElement; } = Object.create(null);
children = new Map<string, OutlineElement>();
marker: { count: number, topSev: MarkerSeverity } | undefined;
constructor(
@@ -109,27 +108,31 @@ export class OutlineElement extends TreeElement {
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));
for (const [key, value] of this.children) {
res.children.set(key, value.adopt(res));
}
return res;
}
}
export class OutlineGroup extends TreeElement {
children: { [id: string]: OutlineElement; } = Object.create(null);
children = new Map<string, OutlineElement>();
constructor(
readonly id: string,
public parent: TreeElement | undefined,
readonly provider: DocumentSymbolProvider,
readonly providerIndex: number,
readonly label: string,
readonly order: number,
) {
super();
}
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));
let res = new OutlineGroup(this.id, parent, this.label, this.order);
for (const [key, value] of this.children) {
res.children.set(key, value.adopt(res));
}
return res;
}
@@ -137,9 +140,8 @@ export class OutlineGroup extends TreeElement {
return position ? this._getItemEnclosingPosition(position, this.children) : undefined;
}
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement | undefined {
for (let key in children) {
let item = children[key];
private _getItemEnclosingPosition(position: IPosition, children: Map<string, OutlineElement>): OutlineElement | undefined {
for (const [, item] of children) {
if (!item.symbol.range || !Range.containsPosition(item.symbol.range, position)) {
continue;
}
@@ -149,8 +151,8 @@ export class OutlineGroup extends TreeElement {
}
updateMarker(marker: IOutlineMarker[]): void {
for (const key in this.children) {
this._updateMarker(marker, this.children[key]);
for (const [, child] of this.children) {
this._updateMarker(marker, child);
}
}
@@ -187,8 +189,8 @@ export class OutlineGroup extends TreeElement {
// this outline element. This might remove markers from this element and
// therefore we remember that we have had markers. That allows us to render
// the dot, saying 'this element has children with markers'
for (const key in item.children) {
this._updateMarker(myMarkers, item.children[key]);
for (const [, child] of item.children) {
this._updateMarker(myMarkers, child);
}
if (myTopSev) {
@@ -202,21 +204,7 @@ export class OutlineGroup extends TreeElement {
}
}
class MovingAverage {
private _n = 1;
private _val = 0;
update(value: number): this {
this._val = this._val + (value - this._val) / this._n;
this._n += 1;
return this;
}
get value(): number {
return this._val;
}
}
export class OutlineModel extends TreeElement {
@@ -315,14 +303,14 @@ export class OutlineModel extends TreeElement {
private static _create(textModel: ITextModel, token: CancellationToken): Promise<OutlineModel> {
const cts = new CancellationTokenSource(token);
const result = new OutlineModel(textModel);
const result = new OutlineModel(textModel.uri);
const provider = DocumentSymbolProviderRegistry.ordered(textModel);
const promises = provider.map((provider, index) => {
let id = TreeElement.findId(`provider_${index}`, result);
let group = new OutlineGroup(id, result, provider, index);
let group = new OutlineGroup(id, result, provider.displayName ?? 'Unknown Outline Provider', index);
return Promise.resolve(provider.provideDocumentSymbols(result.textModel, cts.token)).then(result => {
return Promise.resolve(provider.provideDocumentSymbols(textModel, cts.token)).then(result => {
for (const info of result || []) {
OutlineModel._makeOutlineElement(info, group);
}
@@ -332,7 +320,7 @@ export class OutlineModel extends TreeElement {
return group;
}).then(group => {
if (!TreeElement.empty(group)) {
result._groups[id] = group;
result._groups.set(id, group);
} else {
group.remove();
}
@@ -365,7 +353,7 @@ export class OutlineModel extends TreeElement {
OutlineModel._makeOutlineElement(childInfo, res);
}
}
container.children[res.id] = res;
container.children.set(res.id, res);
}
static get(element: TreeElement | undefined): OutlineModel | undefined {
@@ -381,10 +369,10 @@ export class OutlineModel extends TreeElement {
readonly id = 'root';
readonly parent = undefined;
protected _groups: { [id: string]: OutlineGroup; } = Object.create(null);
children: { [id: string]: OutlineGroup | OutlineElement; } = Object.create(null);
protected _groups = new Map<string, OutlineGroup>();
children = new Map<string, OutlineGroup | OutlineElement>();
protected constructor(readonly textModel: ITextModel) {
protected constructor(readonly uri: URI) {
super();
this.id = 'root';
@@ -392,17 +380,18 @@ export class OutlineModel extends TreeElement {
}
adopt(): OutlineModel {
let res = new OutlineModel(this.textModel);
forEach(this._groups, entry => res._groups[entry.key] = entry.value.adopt(res));
let res = new OutlineModel(this.uri);
for (const [key, value] of this._groups) {
res._groups.set(key, value.adopt(res));
}
return res._compact();
}
private _compact(): this {
let count = 0;
for (const key in this._groups) {
let group = this._groups[key];
if (first(group.children) === undefined) { // empty
delete this._groups[key];
for (const [key, group] of this._groups) {
if (group.children.size === 0) { // empty
this._groups.delete(key);
} else {
count += 1;
}
@@ -412,21 +401,20 @@ export class OutlineModel extends TreeElement {
this.children = this._groups;
} else {
// adopt all elements of the first group
let group = first(this._groups);
for (let key in group!.children) {
let child = group!.children[key];
let group = Iterable.first(this._groups.values())!;
for (let [, child] of group.children) {
child.parent = this;
this.children[child.id] = child;
this.children.set(child.id, child);
}
}
return this;
}
merge(other: OutlineModel): boolean {
if (this.textModel.uri.toString() !== other.textModel.uri.toString()) {
if (this.uri.toString() !== other.uri.toString()) {
return false;
}
if (size(this._groups) !== size(other._groups)) {
if (this._groups.size !== other._groups.size) {
return false;
}
this._groups = other._groups;
@@ -448,8 +436,7 @@ export class OutlineModel extends TreeElement {
}
let result: OutlineElement | undefined = undefined;
for (const key in this._groups) {
const group = this._groups[key];
for (const [, group] of this._groups) {
result = group.getItemEnclosingPosition(position);
if (result && (!preferredGroup || preferredGroup === group)) {
break;
@@ -467,8 +454,8 @@ export class OutlineModel extends TreeElement {
// outline element starts for quicker look up
marker.sort(Range.compareRangesUsingStarts);
for (const key in this._groups) {
this._groups[key].updateMarker(marker.slice(0));
for (const [, group] of this._groups) {
group.updateMarker(marker.slice(0));
}
}
}

View File

@@ -7,7 +7,6 @@ import * as dom from 'vs/base/browser/dom';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter, ITreeFilter } from 'vs/base/browser/ui/tree/tree';
import { values } from 'vs/base/common/collections';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import 'vs/css!./media/outlineTree';
import 'vs/css!./media/symbol-icons';
@@ -24,6 +23,9 @@ import { registerColor, listErrorForeground, listWarningForeground, foreground }
import { IdleValue } from 'vs/base/common/async';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { URI } from 'vs/base/common/uri';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { Iterable } from 'vs/base/common/iterator';
import { Codicon } from 'vs/base/common/codicons';
export type OutlineItem = OutlineGroup | OutlineElement;
@@ -31,13 +33,29 @@ export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelP
getKeyboardNavigationLabel(element: OutlineItem): { toString(): string; } {
if (element instanceof OutlineGroup) {
return element.provider.displayName || element.id;
return element.label;
} else {
return element.symbol.name;
}
}
}
export class OutlineAccessibilityProvider implements IListAccessibilityProvider<OutlineItem> {
constructor(private readonly ariaLabel: string) { }
getWidgetAriaLabel(): string {
return this.ariaLabel;
}
getAriaLabel(element: OutlineItem): string | null {
if (element instanceof OutlineGroup) {
return element.label;
} else {
return element.symbol.name;
}
}
}
export class OutlineIdentityProvider implements IIdentityProvider<OutlineItem> {
getId(element: OutlineItem): { toString(): string; } {
@@ -91,7 +109,7 @@ export class OutlineGroupRenderer implements ITreeRenderer<OutlineGroup, FuzzySc
renderElement(node: ITreeNode<OutlineGroup, FuzzyScore>, index: number, template: OutlineGroupTemplate): void {
template.label.set(
node.element.provider.displayName || localize('provider', "Outline Provider"),
node.element.label,
createMatches(node.filterData)
);
}
@@ -290,7 +308,7 @@ export class OutlineFilter implements ITreeFilter<OutlineItem> {
let uri: URI | undefined;
if (outline) {
uri = outline.textModel.uri;
uri = outline.uri;
}
if (!(element instanceof OutlineElement)) {
@@ -313,7 +331,7 @@ export class OutlineItemComparator implements ITreeSorter<OutlineItem> {
compare(a: OutlineItem, b: OutlineItem): number {
if (a instanceof OutlineGroup && b instanceof OutlineGroup) {
return a.providerIndex - b.providerIndex;
return a.order - b.order;
} else if (a instanceof OutlineElement && b instanceof OutlineElement) {
if (this.type === OutlineSortOrder.ByKind) {
@@ -330,11 +348,11 @@ export class OutlineItemComparator implements ITreeSorter<OutlineItem> {
export class OutlineDataSource implements IDataSource<OutlineModel, OutlineItem> {
getChildren(element: undefined | OutlineModel | OutlineGroup | OutlineElement): OutlineItem[] {
getChildren(element: undefined | OutlineModel | OutlineGroup | OutlineElement) {
if (!element) {
return [];
return Iterable.empty();
}
return values(element.children);
return element.children.values();
}
}
@@ -540,168 +558,168 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const symbolIconArrayColor = theme.getColor(SYMBOL_ICON_ARRAY_FOREGROUND);
if (symbolIconArrayColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-array { color: ${symbolIconArrayColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolArray.cssSelector} { color: ${symbolIconArrayColor}; }`);
}
const symbolIconBooleanColor = theme.getColor(SYMBOL_ICON_BOOLEAN_FOREGROUND);
if (symbolIconBooleanColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-boolean { color: ${symbolIconBooleanColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolBoolean.cssSelector} { color: ${symbolIconBooleanColor}; }`);
}
const symbolIconClassColor = theme.getColor(SYMBOL_ICON_CLASS_FOREGROUND);
if (symbolIconClassColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-class { color: ${symbolIconClassColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolClass.cssSelector} { color: ${symbolIconClassColor}; }`);
}
const symbolIconMethodColor = theme.getColor(SYMBOL_ICON_METHOD_FOREGROUND);
if (symbolIconMethodColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-method { color: ${symbolIconMethodColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolMethod.cssSelector} { color: ${symbolIconMethodColor}; }`);
}
const symbolIconColorColor = theme.getColor(SYMBOL_ICON_COLOR_FOREGROUND);
if (symbolIconColorColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-color { color: ${symbolIconColorColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolColor.cssSelector} { color: ${symbolIconColorColor}; }`);
}
const symbolIconConstantColor = theme.getColor(SYMBOL_ICON_CONSTANT_FOREGROUND);
if (symbolIconConstantColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-constant { color: ${symbolIconConstantColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolConstant.cssSelector} { color: ${symbolIconConstantColor}; }`);
}
const symbolIconConstructorColor = theme.getColor(SYMBOL_ICON_CONSTRUCTOR_FOREGROUND);
if (symbolIconConstructorColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-constructor { color: ${symbolIconConstructorColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolConstructor.cssSelector} { color: ${symbolIconConstructorColor}; }`);
}
const symbolIconEnumeratorColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_FOREGROUND);
if (symbolIconEnumeratorColor) {
collector.addRule(`
.monaco-workbench .codicon-symbol-value,.monaco-workbench .codicon-symbol-enum { color: ${symbolIconEnumeratorColor}; }`);
.monaco-workbench ${Codicon.symbolValue.cssSelector},.monaco-workbench ${Codicon.symbolEnum.cssSelector} { color: ${symbolIconEnumeratorColor}; }`);
}
const symbolIconEnumeratorMemberColor = theme.getColor(SYMBOL_ICON_ENUMERATOR_MEMBER_FOREGROUND);
if (symbolIconEnumeratorMemberColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-enum-member { color: ${symbolIconEnumeratorMemberColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolEnumMember.cssSelector} { color: ${symbolIconEnumeratorMemberColor}; }`);
}
const symbolIconEventColor = theme.getColor(SYMBOL_ICON_EVENT_FOREGROUND);
if (symbolIconEventColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-event { color: ${symbolIconEventColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolEvent.cssSelector} { color: ${symbolIconEventColor}; }`);
}
const symbolIconFieldColor = theme.getColor(SYMBOL_ICON_FIELD_FOREGROUND);
if (symbolIconFieldColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-field { color: ${symbolIconFieldColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolField.cssSelector} { color: ${symbolIconFieldColor}; }`);
}
const symbolIconFileColor = theme.getColor(SYMBOL_ICON_FILE_FOREGROUND);
if (symbolIconFileColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-file { color: ${symbolIconFileColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolFile.cssSelector} { color: ${symbolIconFileColor}; }`);
}
const symbolIconFolderColor = theme.getColor(SYMBOL_ICON_FOLDER_FOREGROUND);
if (symbolIconFolderColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-folder { color: ${symbolIconFolderColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolFolder.cssSelector} { color: ${symbolIconFolderColor}; }`);
}
const symbolIconFunctionColor = theme.getColor(SYMBOL_ICON_FUNCTION_FOREGROUND);
if (symbolIconFunctionColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-function { color: ${symbolIconFunctionColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolFunction.cssSelector} { color: ${symbolIconFunctionColor}; }`);
}
const symbolIconInterfaceColor = theme.getColor(SYMBOL_ICON_INTERFACE_FOREGROUND);
if (symbolIconInterfaceColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-interface { color: ${symbolIconInterfaceColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolInterface.cssSelector} { color: ${symbolIconInterfaceColor}; }`);
}
const symbolIconKeyColor = theme.getColor(SYMBOL_ICON_KEY_FOREGROUND);
if (symbolIconKeyColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-key { color: ${symbolIconKeyColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolKey.cssSelector} { color: ${symbolIconKeyColor}; }`);
}
const symbolIconKeywordColor = theme.getColor(SYMBOL_ICON_KEYWORD_FOREGROUND);
if (symbolIconKeywordColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-keyword { color: ${symbolIconKeywordColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolKeyword.cssSelector} { color: ${symbolIconKeywordColor}; }`);
}
const symbolIconModuleColor = theme.getColor(SYMBOL_ICON_MODULE_FOREGROUND);
if (symbolIconModuleColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-module { color: ${symbolIconModuleColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolModule.cssSelector} { color: ${symbolIconModuleColor}; }`);
}
const outlineNamespaceColor = theme.getColor(SYMBOL_ICON_NAMESPACE_FOREGROUND);
if (outlineNamespaceColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-namespace { color: ${outlineNamespaceColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolNamespace.cssSelector} { color: ${outlineNamespaceColor}; }`);
}
const symbolIconNullColor = theme.getColor(SYMBOL_ICON_NULL_FOREGROUND);
if (symbolIconNullColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-null { color: ${symbolIconNullColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolNull.cssSelector} { color: ${symbolIconNullColor}; }`);
}
const symbolIconNumberColor = theme.getColor(SYMBOL_ICON_NUMBER_FOREGROUND);
if (symbolIconNumberColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-number { color: ${symbolIconNumberColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolNumber.cssSelector} { color: ${symbolIconNumberColor}; }`);
}
const symbolIconObjectColor = theme.getColor(SYMBOL_ICON_OBJECT_FOREGROUND);
if (symbolIconObjectColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-object { color: ${symbolIconObjectColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolObject.cssSelector} { color: ${symbolIconObjectColor}; }`);
}
const symbolIconOperatorColor = theme.getColor(SYMBOL_ICON_OPERATOR_FOREGROUND);
if (symbolIconOperatorColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-operator { color: ${symbolIconOperatorColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolOperator.cssSelector} { color: ${symbolIconOperatorColor}; }`);
}
const symbolIconPackageColor = theme.getColor(SYMBOL_ICON_PACKAGE_FOREGROUND);
if (symbolIconPackageColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-package { color: ${symbolIconPackageColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolPackage.cssSelector} { color: ${symbolIconPackageColor}; }`);
}
const symbolIconPropertyColor = theme.getColor(SYMBOL_ICON_PROPERTY_FOREGROUND);
if (symbolIconPropertyColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-property { color: ${symbolIconPropertyColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolProperty.cssSelector} { color: ${symbolIconPropertyColor}; }`);
}
const symbolIconReferenceColor = theme.getColor(SYMBOL_ICON_REFERENCE_FOREGROUND);
if (symbolIconReferenceColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-reference { color: ${symbolIconReferenceColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolReference.cssSelector} { color: ${symbolIconReferenceColor}; }`);
}
const symbolIconSnippetColor = theme.getColor(SYMBOL_ICON_SNIPPET_FOREGROUND);
if (symbolIconSnippetColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-snippet { color: ${symbolIconSnippetColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolSnippet.cssSelector} { color: ${symbolIconSnippetColor}; }`);
}
const symbolIconStringColor = theme.getColor(SYMBOL_ICON_STRING_FOREGROUND);
if (symbolIconStringColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-string { color: ${symbolIconStringColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolString.cssSelector} { color: ${symbolIconStringColor}; }`);
}
const symbolIconStructColor = theme.getColor(SYMBOL_ICON_STRUCT_FOREGROUND);
if (symbolIconStructColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-struct { color: ${symbolIconStructColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolStruct.cssSelector} { color: ${symbolIconStructColor}; }`);
}
const symbolIconTextColor = theme.getColor(SYMBOL_ICON_TEXT_FOREGROUND);
if (symbolIconTextColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-text { color: ${symbolIconTextColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolText.cssSelector} { color: ${symbolIconTextColor}; }`);
}
const symbolIconTypeParameterColor = theme.getColor(SYMBOL_ICON_TYPEPARAMETER_FOREGROUND);
if (symbolIconTypeParameterColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-type-parameter { color: ${symbolIconTypeParameterColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolTypeParameter.cssSelector} { color: ${symbolIconTypeParameterColor}; }`);
}
const symbolIconUnitColor = theme.getColor(SYMBOL_ICON_UNIT_FOREGROUND);
if (symbolIconUnitColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-unit { color: ${symbolIconUnitColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolUnit.cssSelector} { color: ${symbolIconUnitColor}; }`);
}
const symbolIconVariableColor = theme.getColor(SYMBOL_ICON_VARIABLE_FOREGROUND);
if (symbolIconVariableColor) {
collector.addRule(`.monaco-workbench .codicon-symbol-variable { color: ${symbolIconVariableColor}; }`);
collector.addRule(`.monaco-workbench ${Codicon.symbolVariable.cssSelector} { color: ${symbolIconVariableColor}; }`);
}
});

View File

@@ -93,9 +93,9 @@ suite('OutlineModel', function () {
let e2 = new OutlineElement('foo3', null!, fakeSymbolInformation(new Range(6, 1, 10, 10)));
let group = new OutlineGroup('group', null!, null!, 1);
group.children[e0.id] = e0;
group.children[e1.id] = e1;
group.children[e2.id] = e2;
group.children.set(e0.id, e0);
group.children.set(e1.id, e1);
group.children.set(e2.id, e2);
const data = [fakeMarker(new Range(6, 1, 6, 7)), fakeMarker(new Range(1, 1, 1, 4)), fakeMarker(new Range(10, 2, 14, 1))];
data.sort(Range.compareRangesUsingStarts); // model does this
@@ -119,9 +119,9 @@ suite('OutlineModel', function () {
let c2 = new OutlineElement('A/C', null!, fakeSymbolInformation(new Range(6, 4, 9, 4)));
let group = new OutlineGroup('group', null!, null!, 1);
group.children[p.id] = p;
p.children[c1.id] = c1;
p.children[c2.id] = c2;
group.children.set(p.id, p);
p.children.set(c1.id, c1);
p.children.set(c2.id, c2);
let data = [
fakeMarker(new Range(2, 4, 5, 4))
@@ -162,13 +162,13 @@ suite('OutlineModel', function () {
this._groups = this.children as any;
}
};
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.set('g1', new OutlineGroup('g1', model, null!, 1));
model.children.get('g1')!.children.set('c1', new OutlineElement('c1', model.children.get('g1')!, fakeSymbolInformation(new Range(1, 1, 11, 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)));
model.children.set('g2', new OutlineGroup('g2', model, null!, 1));
model.children.get('g2')!.children.set('c2', new OutlineElement('c2', model.children.get('g2')!, fakeSymbolInformation(new Range(1, 1, 7, 1))));
model.children.get('g2')!.children.get('c2')!.children.set('c2.1', new OutlineElement('c2.1', model.children.get('g2')!.children.get('c2')!, fakeSymbolInformation(new Range(1, 3, 2, 19))));
model.children.get('g2')!.children.get('c2')!.children.set('c2.2', new OutlineElement('c2.2', model.children.get('g2')!.children.get('c2')!, fakeSymbolInformation(new Range(4, 1, 6, 10))));
model.readyForTesting();
@@ -179,9 +179,9 @@ suite('OutlineModel', function () {
model.updateMarker(data);
assert.equal(model.children['g1']!.children['c1'].marker!.count, 2);
assert.equal(model.children['g2']!.children['c2'].children['c2.1'].marker!.count, 1);
assert.equal(model.children['g2']!.children['c2'].children['c2.2'].marker!.count, 1);
assert.equal(model.children.get('g1')!.children.get('c1')!.marker!.count, 2);
assert.equal(model.children.get('g2')!.children.get('c2')!.children.get('c2.1')!.marker!.count, 1);
assert.equal(model.children.get('g2')!.children.get('c2')!.children.get('c2.2')!.marker!.count, 1);
});
});

View File

@@ -27,6 +27,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
const SEARCH_STRING_MAX_LENGTH = 524288;
@@ -66,6 +67,7 @@ export interface IFindStartOptions {
shouldFocus: FindStartFocusAction;
shouldAnimate: boolean;
updateSearchScope: boolean;
loop: boolean;
}
export class CommonFindController extends Disposable implements IEditorContribution {
@@ -125,7 +127,8 @@ export class CommonFindController extends Disposable implements IEditorContribut
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: false
updateSearchScope: false,
loop: this._editor.getOption(EditorOption.find).loop
});
}
}));
@@ -296,6 +299,8 @@ export class CommonFindController extends Disposable implements IEditorContribut
}
}
stateChanges.loop = opts.loop;
this._state.change(stateChanges, false);
if (!this._model) {
@@ -383,6 +388,7 @@ export class FindController extends CommonFindController implements IFindControl
@IThemeService private readonly _themeService: IThemeService,
@INotificationService private readonly _notificationService: INotificationService,
@IStorageService _storageService: IStorageService,
@IStorageKeysSyncRegistryService private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
@optional(IClipboardService) clipboardService: IClipboardService,
) {
super(editor, _contextKeyService, _storageService, clipboardService);
@@ -437,7 +443,7 @@ export class FindController extends CommonFindController implements IFindControl
}
private _createFindWidget() {
this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService));
this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService, this._storageKeysSyncRegistryService));
this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService));
}
}
@@ -473,7 +479,8 @@ export class StartFindAction extends EditorAction {
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard,
shouldFocus: FindStartFocusAction.FocusFindInput,
shouldAnimate: true,
updateSearchScope: false
updateSearchScope: false,
loop: editor.getOption(EditorOption.find).loop
});
}
}
@@ -507,7 +514,8 @@ export class StartFindWithSelectionAction extends EditorAction {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: true,
updateSearchScope: false
updateSearchScope: false,
loop: editor.getOption(EditorOption.find).loop
});
controller.setGlobalBufferTerm(controller.getState().searchString);
@@ -524,7 +532,8 @@ export abstract class MatchFindAction extends EditorAction {
seedSearchStringFromGlobalClipboard: true,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: true,
updateSearchScope: false
updateSearchScope: false,
loop: editor.getOption(EditorOption.find).loop
});
this._run(controller);
}
@@ -636,7 +645,8 @@ export abstract class SelectionMatchFindAction extends EditorAction {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: true,
updateSearchScope: false
updateSearchScope: false,
loop: editor.getOption(EditorOption.find).loop
});
this._run(controller);
}
@@ -741,7 +751,8 @@ export class StartFindReplaceAction extends EditorAction {
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
shouldFocus: shouldFocus,
shouldAnimate: true,
updateSearchScope: false
updateSearchScope: false,
loop: editor.getOption(EditorOption.find).loop
});
}
}

View File

@@ -86,7 +86,8 @@ export class FindDecorations implements IDisposable {
return this._getDecorationIndex(candidate.id);
}
}
return 1;
// We don't know the current match position, so returns zero to show '?' in find widget
return 0;
}
public setCurrentFindMatch(nextMatch: Range | null): number {

View File

@@ -251,6 +251,9 @@ export class FindModelBoundToEditorModel {
}
private _moveToPrevMatch(before: Position, isRecursed: boolean = false): void {
if (!this._state.canNavigateBack()) {
return;
}
if (this._decorations.getCount() < MATCHES_LIMIT) {
let prevMatchRange = this._decorations.matchBeforePosition(before);
@@ -336,6 +339,9 @@ export class FindModelBoundToEditorModel {
}
private _moveToNextMatch(after: Position): void {
if (!this._state.canNavigateForward()) {
return;
}
if (this._decorations.getCount() < MATCHES_LIMIT) {
let nextMatchRange = this._decorations.matchAfterPosition(after);

View File

@@ -6,6 +6,7 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { MATCHES_LIMIT } from './findModel';
export interface FindReplaceStateChangedEvent {
moveCursor: boolean;
@@ -23,6 +24,7 @@ export interface FindReplaceStateChangedEvent {
matchesPosition: boolean;
matchesCount: boolean;
currentMatch: boolean;
loop: boolean;
}
export const enum FindOptionOverride {
@@ -45,6 +47,7 @@ export interface INewFindReplaceState {
preserveCase?: boolean;
preserveCaseOverride?: FindOptionOverride;
searchScope?: Range | null;
loop?: boolean;
}
function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {
@@ -74,6 +77,7 @@ export class FindReplaceState extends Disposable {
private _matchesPosition: number;
private _matchesCount: number;
private _currentMatch: Range | null;
private _loop: boolean;
private readonly _onFindReplaceStateChange = this._register(new Emitter<FindReplaceStateChangedEvent>());
public get searchString(): string { return this._searchString; }
@@ -114,6 +118,7 @@ export class FindReplaceState extends Disposable {
this._matchesPosition = 0;
this._matchesCount = 0;
this._currentMatch = null;
this._loop = true;
}
public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void {
@@ -131,7 +136,8 @@ export class FindReplaceState extends Disposable {
searchScope: false,
matchesPosition: false,
matchesCount: false,
currentMatch: false
currentMatch: false,
loop: false
};
let somethingChanged = false;
@@ -181,7 +187,8 @@ export class FindReplaceState extends Disposable {
searchScope: false,
matchesPosition: false,
matchesCount: false,
currentMatch: false
currentMatch: false,
loop: false
};
let somethingChanged = false;
@@ -237,7 +244,13 @@ export class FindReplaceState extends Disposable {
somethingChanged = true;
}
}
if (typeof newState.loop !== 'undefined') {
if (this._loop !== newState.loop) {
this._loop = newState.loop;
changeEvent.loop = true;
somethingChanged = true;
}
}
// Overrides get set when they explicitly come in and get reset anytime something else changes
this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);
this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);
@@ -266,4 +279,17 @@ export class FindReplaceState extends Disposable {
this._onFindReplaceStateChange.fire(changeEvent);
}
}
public canNavigateBack(): boolean {
return this.canNavigateInLoop() || (this.matchesPosition !== 1);
}
public canNavigateForward(): boolean {
return this.canNavigateInLoop() || (this.matchesPosition < this.matchesCount);
}
private canNavigateInLoop(): boolean {
return this._loop || (this.matchesCount >= MATCHES_LIMIT);
}
}

View File

@@ -36,6 +36,18 @@ import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/b
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
const findSelectionIcon = registerIcon('find-selection', Codicon.selection);
const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight);
const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown);
export const findCloseIcon = registerIcon('find-close', Codicon.close);
export const findReplaceIcon = registerIcon('find-replace', Codicon.replace);
export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll);
export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp);
export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown);
export interface IFindController {
replace(): void;
@@ -152,6 +164,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
themeService: IThemeService,
storageService: IStorageService,
notificationService: INotificationService,
storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
super();
this._codeEditor = codeEditor;
@@ -163,6 +176,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._storageService = storageService;
this._notificationService = notificationService;
storageKeysSyncRegistryService.registerStorageKey({ key: ctrlEnterReplaceAllWarningPromptedKey, version: 1 });
this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL);
this._isVisible = false;
@@ -360,6 +374,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (e.updateHistory) {
this._delayedUpdateHistory();
}
if (e.loop) {
this._updateButtons();
}
}
private _delayedUpdateHistory() {
@@ -405,7 +422,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._matchesCount.appendChild(document.createTextNode(label));
alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString), true);
alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString));
MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);
}
@@ -455,14 +472,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
let findInputIsNonEmpty = (this._state.searchString.length > 0);
let matchesCount = this._state.matchesCount ? true : false;
this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount);
this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount);
this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateBack());
this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateForward());
this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible);
this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible);
this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible);
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
let canReplace = !this._codeEditor.getOption(EditorOption.readOnly);
@@ -984,7 +999,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Previous button
this._prevBtn = this._register(new SimpleButton({
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
className: 'codicon codicon-arrow-up',
className: findPreviousMatchIcon.classNames,
onTrigger: () => {
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
}
@@ -993,7 +1008,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Next button
this._nextBtn = this._register(new SimpleButton({
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
className: 'codicon codicon-arrow-down',
className: findNextMatchIcon.classNames,
onTrigger: () => {
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
}
@@ -1011,7 +1026,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Toggle selection button
this._toggleSelectionFind = this._register(new Checkbox({
actionClassName: 'codicon codicon-selection',
icon: findSelectionIcon,
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
isChecked: false
}));
@@ -1037,7 +1052,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Close button
this._closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
className: 'codicon codicon-close',
className: findCloseIcon.classNames,
onTrigger: () => {
this._state.change({ isRevealed: false, searchScope: null }, false);
},
@@ -1100,7 +1115,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Replace one button
this._replaceBtn = this._register(new SimpleButton({
label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
className: 'codicon codicon-replace',
className: findReplaceIcon.classNames,
onTrigger: () => {
this._controller.replace();
},
@@ -1115,7 +1130,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// Replace all button
this._replaceAllBtn = this._register(new SimpleButton({
label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),
className: 'codicon codicon-replace-all',
className: findReplaceAllIcon.classNames,
onTrigger: () => {
this._controller.replaceAll();
}
@@ -1145,8 +1160,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._showViewZone();
}
}));
this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible);
this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible);
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
// Widget
@@ -1289,10 +1302,13 @@ export class SimpleButton extends Widget {
public setExpanded(expanded: boolean): void {
this._domNode.setAttribute('aria-expanded', String(!!expanded));
}
public toggleClass(className: string, shouldHaveIt: boolean): void {
dom.toggleClass(this._domNode, className, shouldHaveIt);
if (expanded) {
dom.removeClasses(this._domNode, findCollapsedIcon.classNames);
dom.addClasses(this._domNode, findExpandedIcon.classNames);
} else {
dom.removeClasses(this._domNode, findExpandedIcon.classNames);
dom.addClasses(this._domNode, findCollapsedIcon.classNames);
}
}
}

View File

@@ -276,7 +276,8 @@ suite('FindController', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.FocusFindInput,
shouldAnimate: false,
updateSearchScope: false
updateSearchScope: false,
loop: true
});
nextMatchFindAction.run(null, editor);
startFindReplaceAction.run(null, editor);
@@ -301,7 +302,8 @@ suite('FindController', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: false
updateSearchScope: false,
loop: true
});
assert.equal(findController.getState().searchScope, null);
@@ -530,7 +532,8 @@ suite('FindController query options persistence', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: true
updateSearchScope: true,
loop: true
});
assert.deepEqual(findController.getState().searchScope, new Selection(1, 1, 2, 1));
@@ -553,7 +556,8 @@ suite('FindController query options persistence', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: true
updateSearchScope: true,
loop: true
});
assert.deepEqual(findController.getState().searchScope, null);
@@ -576,7 +580,8 @@ suite('FindController query options persistence', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: true
updateSearchScope: true,
loop: true
});
assert.deepEqual(findController.getState().searchScope, new Selection(1, 2, 1, 3));
@@ -600,7 +605,8 @@ suite('FindController query options persistence', () => {
seedSearchStringFromGlobalClipboard: false,
shouldFocus: FindStartFocusAction.NoFocusChange,
shouldAnimate: false,
updateSearchScope: true
updateSearchScope: true,
loop: true
});
assert.deepEqual(findController.getState().searchScope, new Selection(1, 6, 2, 1));

View File

@@ -2089,4 +2089,165 @@ suite('FindModel', () => {
findModel.dispose();
findState.dispose();
});
findTest('issue #3516: Control behavior of "Next" operations (not looping back to beginning)', (editor, cursor) => {
let findState = new FindReplaceState();
findState.change({ searchString: 'hello', loop: false }, false);
let findModel = new FindModelBoundToEditorModel(editor, findState);
assert.equal(findState.matchesCount, 5);
// Test next operations
assert.equal(findState.matchesPosition, 0);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), false);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 2);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 3);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 4);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 5);
assert.equal(findState.canNavigateForward(), false);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 5);
assert.equal(findState.canNavigateForward(), false);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 5);
assert.equal(findState.canNavigateForward(), false);
assert.equal(findState.canNavigateBack(), true);
// Test previous operations
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 4);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 3);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 2);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), false);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), false);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), false);
});
findTest('issue #3516: Control behavior of "Next" operations (looping back to beginning)', (editor, cursor) => {
let findState = new FindReplaceState();
findState.change({ searchString: 'hello' }, false);
let findModel = new FindModelBoundToEditorModel(editor, findState);
assert.equal(findState.matchesCount, 5);
// Test next operations
assert.equal(findState.matchesPosition, 0);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 2);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 3);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 4);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 5);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToNextMatch();
assert.equal(findState.matchesPosition, 2);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
// Test previous operations
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 5);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 4);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 3);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 2);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
findModel.moveToPrevMatch();
assert.equal(findState.matchesPosition, 1);
assert.equal(findState.canNavigateForward(), true);
assert.equal(findState.canNavigateBack(), true);
});
});

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .margin-view-overlays .codicon-chevron-right,
.monaco-editor .margin-view-overlays .codicon-chevron-down {
.monaco-editor .margin-view-overlays .codicon-folding-expanded,
.monaco-editor .margin-view-overlays .codicon-folding-collapsed {
cursor: pointer;
opacity: 0;
transition: opacity 0.5s;
@@ -16,7 +16,7 @@
}
.monaco-editor .margin-view-overlays:hover .codicon,
.monaco-editor .margin-view-overlays .codicon.codicon-chevron-right,
.monaco-editor .margin-view-overlays .codicon.codicon-folding-collapsed,
.monaco-editor .margin-view-overlays .codicon.alwaysShowFoldIcons {
opacity: 1;
}

View File

@@ -33,7 +33,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { onUnexpectedError } from 'vs/base/common/errors';
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { registerColor, editorSelectionBackground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { registerColor, editorSelectionBackground, transparent, iconForeground } from 'vs/platform/theme/common/colorRegistry';
const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);
@@ -898,10 +898,21 @@ for (let i = 1; i <= 7; i++) {
}
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges. The color must not be opaque so as not to hide underlying decorations."), true);
export const editorFoldForeground = registerColor('editorGutter.foldingControlForeground', { dark: iconForeground, light: iconForeground, hc: iconForeground }, nls.localize('editorGutter.foldingControlForeground', 'Color of the folding control in the editor gutter.'));
registerThemingParticipant((theme, collector) => {
const foldBackground = theme.getColor(foldBackgroundBackground);
if (foldBackground) {
collector.addRule(`.monaco-editor .folded-background { background-color: ${foldBackground}; }`);
}
const editorFoldColor = theme.getColor(editorFoldForeground);
if (editorFoldColor) {
collector.addRule(`
.monaco-editor .cldr.codicon-chevron-right,
.monaco-editor .cldr.codicon-chevron-down {
color: ${editorFoldColor} !important;
}
`);
}
});

View File

@@ -7,6 +7,10 @@ import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationsChangeA
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { IDecorationProvider } from 'vs/editor/contrib/folding/foldingModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown);
const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight);
export class FoldingDecorationProvider implements IDecorationProvider {
@@ -14,7 +18,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
afterContentClassName: 'inline-folded',
isWholeLine: true,
firstLineDecorationClassName: 'codicon codicon-chevron-right'
firstLineDecorationClassName: foldingCollapsedIcon.classNames
});
private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
@@ -22,19 +26,19 @@ export class FoldingDecorationProvider implements IDecorationProvider {
afterContentClassName: 'inline-folded',
className: 'folded-background',
isWholeLine: true,
firstLineDecorationClassName: 'codicon codicon-chevron-right'
firstLineDecorationClassName: foldingCollapsedIcon.classNames
});
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
isWholeLine: true,
firstLineDecorationClassName: 'codicon codicon-chevron-down'
firstLineDecorationClassName: foldingExpandedIcon.classNames
});
private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
isWholeLine: true,
firstLineDecorationClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons'
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + foldingExpandedIcon.classNames
});
private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({

View File

@@ -151,6 +151,26 @@ export class FoldingRegions {
}
return res.join(', ');
}
public equals(b: FoldingRegions) {
if (this.length !== b.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.getStartLineNumber(i) !== b.getStartLineNumber(i)) {
return false;
}
if (this.getEndLineNumber(i) !== b.getEndLineNumber(i)) {
return false;
}
if (this.getType(i) !== b.getType(i)) {
return false;
}
}
return true;
}
}
export class FoldingRegion {

View File

@@ -29,6 +29,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { isEqual } from 'vs/base/common/resources';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
class MarkerModel {
@@ -191,6 +192,9 @@ class MarkerModel {
}
}
const markerNavigationNextIcon = registerIcon('marker-navigation-next', Codicon.chevronDown);
const markerNavigationPreviousIcon = registerIcon('marker-navigation-previous', Codicon.chevronUp);
export class MarkerController implements IEditorContribution {
public static readonly ID = 'editor.contrib.markerController';
@@ -243,8 +247,8 @@ export class MarkerController implements IEditorContribution {
const prevMarkerKeybinding = this._keybindingService.lookupKeybinding(PrevMarkerAction.ID);
const nextMarkerKeybinding = this._keybindingService.lookupKeybinding(NextMarkerAction.ID);
const actions = [
new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem codicon-chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }),
new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem codicon-chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } })
new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem ' + markerNavigationNextIcon.classNames, this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }),
new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem ' + markerNavigationPreviousIcon.classNames, this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } })
];
this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService);
this._widgetVisible.set(true);

View File

@@ -11,19 +11,19 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { values } from 'vs/base/common/collections';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { assertType } from 'vs/base/common/types';
import { Iterable } from 'vs/base/common/iterator';
export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
const model = await OutlineModel.create(document, token);
const roots: DocumentSymbol[] = [];
for (const child of values(model.children)) {
for (const child of model.children.values()) {
if (child instanceof OutlineElement) {
roots.push(child.symbol);
} else {
roots.push(...values(child.children).map(child => child.symbol));
roots.push(...Iterable.map(child.children.values(), child => child.symbol));
}
}

View File

@@ -17,7 +17,7 @@ import { getBaseLabel } from 'vs/base/common/labels';
import { dirname, basename } from 'vs/base/common/resources';
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 { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
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';
@@ -213,7 +213,11 @@ export class OneReferenceRenderer implements ITreeRenderer<OneReference, FuzzySc
//#endregion
export class AriaProvider implements IAccessibilityProvider<FileReferences | OneReference> {
export class AccessibilityProvider implements IListAccessibilityProvider<FileReferences | OneReference> {
getWidgetAriaLabel(): string {
return localize('treeAriaLabel', "References");
}
getAriaLabel(element: FileReferences | OneReference): string | null {
return element.ariaMessage;

View File

@@ -22,7 +22,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, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree';
import { AccessibilityProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -311,9 +311,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
// tree
this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline'));
const treeOptions: IWorkbenchAsyncDataTreeOptions<TreeElement, FuzzyScore> = {
ariaLabel: nls.localize('treeAriaLabel', "References"),
keyboardSupport: this._defaultTreeKeyboardSupport,
accessibilityProvider: new AriaProvider(),
accessibilityProvider: new AccessibilityProvider(),
keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider),
identityProvider: new IdentityProvider(),
overrideStyles: {

View File

@@ -30,7 +30,7 @@
}
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
min-width: 100vw;
min-width: 100%;
}
.monaco-editor-hover p,

View File

@@ -143,9 +143,7 @@ export class HoverOperation<Result> {
}
private _onComplete(value: Result): void {
if (this._completeCallback) {
this._completeCallback(value);
}
this._completeCallback(value);
}
private _onError(error: any): void {
@@ -157,9 +155,7 @@ export class HoverOperation<Result> {
}
private _onProgress(value: Result): void {
if (this._progressCallback) {
this._progressCallback(value);
}
this._progressCallback(value);
}
public start(mode: HoverStartMode): void {

View File

@@ -47,6 +47,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget {
this._containerDomNode = document.createElement('div');
this._containerDomNode.className = 'monaco-editor-hover hidden';
this._containerDomNode.tabIndex = 0;
this._containerDomNode.setAttribute('role', 'tooltip');
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-editor-hover-content';
@@ -178,7 +179,7 @@ export class GlyphHoverWidget extends Widget implements IOverlayWidget {
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-editor-hover hidden';
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.setAttribute('role', 'presentation');
this._domNode.setAttribute('role', 'tooltip');
this._showAtLineNumber = -1;

View File

@@ -607,6 +607,9 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
showing = true;
const controller = QuickFixController.get(this._editor);
const elementPosition = dom.getDomNodePagePosition(target);
// Hide the hover pre-emptively, otherwise the editor can close the code actions
// context menu as well when using keyboard navigation
this.hide();
controller.showCodeActions(markerCodeActionTrigger, actions, {
x: elementPosition.left + 6,
y: elementPosition.top + elementPosition.height + 6
@@ -633,6 +636,8 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
private renderAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IDisposable {
const actionContainer = dom.append(parent, $('div.action-container'));
const action = dom.append(actionContainer, $('a.action'));
action.setAttribute('href', '#');
action.setAttribute('role', 'button');
if (actionOptions.iconClass) {
dom.append(action, $(`span.icon.${actionOptions.iconClass}`));
}

View File

@@ -23,9 +23,13 @@ import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, text
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
import { pad } from 'vs/base/common/strings';
import { registerIcon, Codicon } from 'vs/base/common/codicons';
const $ = dom.$;
const parameterHintsNextIcon = registerIcon('parameter-hints-next', Codicon.chevronDown);
const parameterHintsPreviousIcon = registerIcon('parameter-hints-previous', Codicon.chevronUp);
export class ParameterHintsWidget extends Disposable implements IContentWidget {
private static readonly ID = 'editor.widget.parameterHintsWidget';
@@ -78,9 +82,9 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
wrapper.tabIndex = -1;
const controls = dom.append(wrapper, $('.controls'));
const previous = dom.append(controls, $('.button.codicon.codicon-chevron-up'));
const previous = dom.append(controls, $('.button' + parameterHintsPreviousIcon.cssSelector));
const overloads = dom.append(controls, $('.overloads'));
const next = dom.append(controls, $('.button.codicon.codicon-chevron-down'));
const next = dom.append(controls, $('.button' + parameterHintsNextIcon.cssSelector));
const onPreviousClick = stop(domEvent(previous, 'click'));
this._register(onPreviousClick(this.previous, this));
@@ -153,6 +157,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
}
private hide(): void {
this.renderDisposeables.clear();
if (!this.visible) {
return;
}
@@ -177,6 +183,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
}
private render(hints: modes.SignatureHelp): void {
this.renderDisposeables.clear();
if (!this.domNodes) {
return;
}
@@ -194,43 +202,40 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
}
const code = dom.append(this.domNodes.signature, $('.code'));
const hasParameters = signature.parameters.length > 0;
const fontInfo = this.editor.getOption(EditorOption.fontInfo);
code.style.fontSize = `${fontInfo.fontSize}px`;
code.style.fontFamily = fontInfo.fontFamily;
const hasParameters = signature.parameters.length > 0;
const activeParameterIndex = signature.activeParameter ?? hints.activeParameter;
if (!hasParameters) {
const label = dom.append(code, $('span'));
label.textContent = signature.label;
} else {
this.renderParameters(code, signature, hints.activeParameter);
this.renderParameters(code, signature, activeParameterIndex);
}
this.renderDisposeables.clear();
const activeParameter: modes.ParameterInformation | undefined = signature.parameters[hints.activeParameter];
if (activeParameter && activeParameter.documentation) {
const activeParameter: modes.ParameterInformation | undefined = signature.parameters[activeParameterIndex];
if (activeParameter?.documentation) {
const documentation = $('span.documentation');
if (typeof activeParameter.documentation === 'string') {
documentation.textContent = activeParameter.documentation;
} else {
const renderedContents = this.markdownRenderer.render(activeParameter.documentation);
const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(activeParameter.documentation));
dom.addClass(renderedContents.element, 'markdown-docs');
this.renderDisposeables.add(renderedContents);
documentation.appendChild(renderedContents.element);
}
dom.append(this.domNodes.docs, $('p', {}, documentation));
}
if (signature.documentation === undefined) { /** no op */ }
else if (typeof signature.documentation === 'string') {
if (signature.documentation === undefined) {
/** no op */
} else if (typeof signature.documentation === 'string') {
dom.append(this.domNodes.docs, $('p', {}, signature.documentation));
} else {
const renderedContents = this.markdownRenderer.render(signature.documentation);
const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(signature.documentation));
dom.addClass(renderedContents.element, 'markdown-docs');
this.renderDisposeables.add(renderedContents);
dom.append(this.domNodes.docs, renderedContents.element);
}
@@ -243,7 +248,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length;
if (activeParameter) {
const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter);
const labelToAnnounce = this.getParameterLabel(signature, activeParameterIndex);
// 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.
@@ -273,8 +278,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
return false;
}
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void {
const [start, end] = this.getParameterLabelOffsets(signature, currentParameter);
private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, activeParameterIndex: number): void {
const [start, end] = this.getParameterLabelOffsets(signature, activeParameterIndex);
const beforeSpan = document.createElement('span');
beforeSpan.textContent = signature.label.substring(0, start);

View File

@@ -25,6 +25,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Codicon } from 'vs/base/common/codicons';
export const IPeekViewService = createDecorator<IPeekViewService>('IPeekViewService');
@@ -186,7 +187,7 @@ export abstract class PeekViewWidget extends ZoneWidget {
this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions);
this._disposables.add(this._actionbarWidget);
this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'codicon-close', true, () => {
this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), Codicon.close.classNames, true, () => {
this.dispose();
return Promise.resolve();
}), { label: false, icon: true });

View File

@@ -13,10 +13,11 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes';
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { values } from 'vs/base/common/collections';
import { trim, format } from 'vs/base/common/strings';
import { prepareQuery, IPreparedQuery, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer';
import { IMatch } from 'vs/base/common/filters';
import { Iterable } from 'vs/base/common/iterator';
import { Codicon } from 'vs/base/common/codicons';
export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
kind: SymbolKind,
@@ -35,15 +36,14 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
static SCOPE_PREFIX = ':';
static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
constructor(protected options?: IGotoSymbolQuickAccessProviderOptions) {
super({ ...options, canAcceptInBackground: true });
constructor(protected options: IGotoSymbolQuickAccessProviderOptions = Object.create(null)) {
super(options);
options.canAcceptInBackground = true;
}
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information.");
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information."));
return Disposable.None;
}
@@ -69,9 +69,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
const disposables = new DisposableStore();
// Generic pick for not having any symbol information
const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information.");
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information."));
// Wait for changes to the registry and see if eventually
// we do get symbols. This can happen if the picker is opened
@@ -90,6 +88,11 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return disposables;
}
private provideLabelPick(picker: IQuickPick<IGotoSymbolQuickPickItem>, label: string): void {
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
}
protected async waitForLanguageSymbolRegistry(model: ITextModel, disposables: DisposableStore): Promise<boolean> {
if (DocumentSymbolProviderRegistry.has(model)) {
return true;
@@ -155,12 +158,21 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
// Collect symbol picks
picker.busy = true;
try {
const items = await this.doGetSymbolPicks(symbolsPromise, prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim()), undefined, picksCts.token);
const query = prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim());
const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token);
if (token.isCancellationRequested) {
return;
}
picker.items = items;
if (items.length > 0) {
picker.items = items;
} else {
if (query.original.length > 0) {
this.provideLabelPick(picker, localize('noMatchingSymbolResults', "No matching editor symbols"));
} else {
this.provideLabelPick(picker, localize('noSymbolResults', "No editor symbols"));
}
}
} finally {
if (!token.isCancellationRequested) {
picker.busy = false;
@@ -220,6 +232,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
const symbolLabel = trim(symbol.name);
const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length;
let containerLabel = symbol.containerName;
if (options?.extraContainerLabel) {
@@ -238,23 +251,37 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
if (query.original.length > filterPos) {
// Score by symbol
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */);
if (!symbolScore) {
continue;
// First: try to score on the entire query, it is possible that
// the symbol matches perfectly (e.g. searching for "change log"
// can be a match on a markdown symbol "change log"). In that
// case we want to skip the container query altogether.
let skipContainerQuery = false;
if (symbolQuery !== query) {
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, { ...query, values: undefined /* disable multi-query support */ }, filterPos, symbolLabelIconOffset);
if (typeof symbolScore === 'number') {
skipContainerQuery = true; // since we consumed the query, skip any container matching
}
}
// Otherwise: score on the symbol query and match on the container later
if (typeof symbolScore !== 'number') {
[symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelIconOffset);
if (typeof symbolScore !== 'number') {
continue;
}
}
// Score by container if specified
if (containerQuery) {
if (!skipContainerQuery && containerQuery) {
if (containerLabel && containerQuery.original.length > 0) {
[containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery);
}
if (!containerScore) {
if (typeof containerScore !== 'number') {
continue;
}
if (symbolScore) {
if (typeof symbolScore === 'number') {
symbolScore += containerScore; // boost symbolScore by containerScore
}
}
@@ -286,7 +313,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return [
{
iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical',
iconClass: openSideBySideDirection === 'right' ? Codicon.splitHorizontal.classNames : Codicon.splitVertical.classNames,
tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom")
}
];
@@ -342,7 +369,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
// Update last separator with number of symbols we found for kind
updateLastSeparatorLabel();
} else {
} else if (sortedFilteredSymbolPicks.length > 0) {
symbolPicks = [
{ label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' },
...sortedFilteredSymbolPicks
@@ -353,13 +380,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number {
if (!symbolA.score && symbolB.score) {
if (typeof symbolA.score !== 'number' && typeof symbolB.score === 'number') {
return 1;
} else if (symbolA.score && !symbolB.score) {
} else if (typeof symbolA.score === 'number' && typeof symbolB.score !== 'number') {
return -1;
}
if (symbolA.score && symbolB.score) {
if (typeof symbolA.score === 'number' && typeof symbolB.score === 'number') {
if (symbolA.score > symbolB.score) {
return -1;
} else if (symbolA.score < symbolB.score) {
@@ -396,11 +423,11 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
const roots: DocumentSymbol[] = [];
for (const child of values(model.children)) {
for (const child of model.children.values()) {
if (child instanceof OutlineElement) {
roots.push(child.symbol);
} else {
roots.push(...values(child.children).map(child => child.symbol));
roots.push(...Iterable.map(child.children.values(), child => child.symbol));
}
}

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/suggest';
import 'vs/css!./media/suggestStatusBar';
import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded
import 'vs/base/browser/ui/codicons/codiconStyles'; // The codicon symbol styles are defined here and must be loaded
import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded
import * as nls from 'vs/nls';
import { createMatches } from 'vs/base/common/filters';
@@ -45,9 +45,12 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction } from 'vs/base/common/actions';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
const expandSuggestionDocsByDefault = false;
const suggestMoreInfoIcon = registerIcon('suggest-more-info', Codicon.chevronRight);
interface ISuggestionTemplateData {
root: HTMLElement;
@@ -155,7 +158,7 @@ class ItemRenderer implements IListRenderer<CompletionItem, ISuggestionTemplateD
data.qualifierLabel = append(data.left, $('span.qualifier-label'));
data.detailsLabel = append(data.right, $('span.details-label'));
data.readMore = append(data.right, $('span.readMore.codicon.codicon-info'));
data.readMore = append(data.right, $('span.readMore' + suggestMoreInfoIcon.cssSelector));
data.readMore.title = nls.localize('readMore', "Read More...{0}", this.triggerKeybindingLabel);
const configureFont = () => {
@@ -229,7 +232,7 @@ class ItemRenderer implements IListRenderer<CompletionItem, ISuggestionTemplateD
// normal icon
data.icon.className = 'icon hide';
data.iconContainer.className = '';
addClasses(data.iconContainer, `suggest-icon codicon codicon-${completionKindToCssClass(suggestion.kind)}`);
addClasses(data.iconContainer, `suggest-icon ${completionKindToCssClass(suggestion.kind)}`);
}
if (suggestion.tags && suggestion.tags.indexOf(CompletionItemTag.Deprecated) >= 0) {
@@ -317,7 +320,7 @@ class SuggestionDetails {
this.disposables.add(this.scrollbar);
this.header = append(this.body, $('.header'));
this.close = append(this.header, $('span.codicon.codicon-close'));
this.close = append(this.header, $('span' + Codicon.close.cssSelector));
this.close.title = nls.localize('readLess', "Read less...{0}", this.kbToggleDetails);
this.type = append(this.header, $('p.type'));
@@ -604,13 +607,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
useShadows: false,
openController: { shouldOpen: () => false },
mouseSupport: false,
ariaRole: 'listbox',
ariaProvider: {
getRole: () => 'option',
getSetSize: (_: CompletionItem, _index: number, listLength: number) => listLength,
getPosInSet: (_: CompletionItem, index: number) => index,
},
accessibilityProvider: {
getRole: () => 'option',
getAriaLabel: (item: CompletionItem) => {
const textLabel = typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name;
if (item.isResolved && this.expandDocsSettingFromStorage()) {
@@ -624,7 +622,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
} else {
return textLabel;
}
}
},
getWidgetAriaLabel: () => nls.localize('suggest', "Suggest"),
getWidgetRole: () => 'listbox'
}
});

View File

@@ -48,3 +48,5 @@ import 'vs/editor/contrib/wordPartOperations/wordPartOperations';
// Load up these strings even in VSCode, even if they are not used
// in order to get them translated
import 'vs/editor/common/standaloneStrings';
import 'vs/base/browser/ui/codicons/codiconStyles'; // The codicons are defined here and must be loaded

View File

@@ -90,11 +90,11 @@ export class StandaloneQuickInputServiceImpl implements IQuickInputService {
) {
}
pick<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options: O = <O>{}, token: CancellationToken = CancellationToken.None): Promise<O extends { canPickMany: true } ? T[] : T> {
pick<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options: O = <O>{}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> {
return (this.activeService as unknown as QuickInputController /* TS fail */).pick(picks, options, token);
}
input(options?: IInputOptions | undefined, token?: CancellationToken | undefined): Promise<string> {
input(options?: IInputOptions | undefined, token?: CancellationToken | undefined): Promise<string | undefined> {
return this.activeService.input(options, token);
}

View File

@@ -40,6 +40,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -241,13 +242,17 @@ export function createWebWorker<T>(opts: IWebWorkerOptions): MonacoWebWorker<T>
* Colorize the contents of `domNode` using attribute `data-lang`.
*/
export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise<void> {
return Colorizer.colorizeElement(StaticServices.standaloneThemeService.get(), StaticServices.modeService.get(), domNode, options);
const themeService = <StandaloneThemeServiceImpl>StaticServices.standaloneThemeService.get();
themeService.registerEditorContainer(domNode);
return Colorizer.colorizeElement(themeService, StaticServices.modeService.get(), domNode, options);
}
/**
* Colorize `text` using language `languageId`.
*/
export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise<string> {
const themeService = <StandaloneThemeServiceImpl>StaticServices.standaloneThemeService.get();
themeService.registerEditorContainer(document.body);
return Colorizer.colorize(StaticServices.modeService.get(), text, languageId, options);
}
@@ -255,6 +260,8 @@ export function colorize(text: string, languageId: string, options: IColorizerOp
* Colorize a line in a model.
*/
export function colorizeModelLine(model: ITextModel, lineNumber: number, tabSize: number = 4): string {
const themeService = <StandaloneThemeServiceImpl>StaticServices.standaloneThemeService.get();
themeService.registerEditorContainer(document.body);
return Colorizer.colorizeModelLine(model, lineNumber, tabSize);
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig';
import { IEditorHoverOptions, EditorOption, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { IEditorHoverOptions, EditorOption, ConfigurationChangedEvent, IQuickSuggestionsOptions } from 'vs/editor/common/config/editorOptions';
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
@@ -201,4 +201,14 @@ suite('Common Editor Config', () => {
config.updateOptions({ roundedSelection: false });
assert.equal(event, null);
});
test('issue #94931: Unable to open source file', () => {
const config = new TestConfiguration({ quickSuggestions: null! });
const actual = <Readonly<Required<IQuickSuggestionsOptions>>>config.options.get(EditorOption.quickSuggestions);
assert.deepEqual(actual, {
other: true,
comments: false,
strings: false
});
});
});