Merge from vscode e558dc6ea73a75bd69d7a0b485f0e7e4194c66bf (#6864)

This commit is contained in:
Anthony Dresser
2019-08-21 20:44:59 -07:00
committed by GitHub
parent d2ae0f0154
commit 985bfae8a0
107 changed files with 2260 additions and 814 deletions

View File

@@ -4,41 +4,39 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IOpenerService, IOpener } from 'vs/platform/opener/common/opener';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { localize } from 'vs/nls';
import { IProductService } from 'vs/platform/product/common/product';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import Severity from 'vs/base/common/severity';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { IOpener, IOpenerService, IValidator } from 'vs/platform/opener/common/opener';
export class OpenerService implements IOpenerService {
export class OpenerService extends Disposable implements IOpenerService {
_serviceBrand!: ServiceIdentifier<any>;
private readonly _opener = new LinkedList<IOpener>();
private readonly _openers = new LinkedList<IOpener>();
private readonly _validators = new LinkedList<IValidator>();
constructor(
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@ICommandService private readonly _commandService: ICommandService,
@IStorageService private readonly _storageService: IStorageService,
@IDialogService private readonly _dialogService: IDialogService,
@IProductService private readonly _productService: IProductService
) {
//
super();
}
registerOpener(opener: IOpener): IDisposable {
const remove = this._opener.push(opener);
const remove = this._openers.push(opener);
return { dispose: remove };
}
registerValidator(validator: IValidator): IDisposable {
const remove = this._validators.push(validator);
return { dispose: remove };
}
@@ -47,8 +45,16 @@ export class OpenerService implements IOpenerService {
if (!resource.scheme) {
return Promise.resolve(false);
}
// check with contributed validators
for (const validator of this._validators.toArray()) {
if (!(await validator.shouldOpen(resource))) {
return false;
}
}
// check with contributed openers
for (const opener of this._opener.toArray()) {
for (const opener of this._openers.toArray()) {
const handled = await opener.open(resource, options);
if (handled) {
return true;
@@ -60,7 +66,7 @@ export class OpenerService implements IOpenerService {
private _doOpen(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise<boolean> {
const { scheme, authority, path, query, fragment } = resource;
const { scheme, path, query, fragment } = resource;
if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) {
// open default mail application
@@ -68,48 +74,8 @@ export class OpenerService implements IOpenerService {
}
if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) {
let trustedDomains: string[] = ['https://code.visualstudio.com'];
try {
const trustedDomainsSrc = this._storageService.get('http.trustedDomains', StorageScope.GLOBAL);
if (trustedDomainsSrc) {
trustedDomains = JSON.parse(trustedDomainsSrc);
}
} catch (err) { }
const domainToOpen = `${scheme}://${authority}`;
if (isDomainTrusted(domainToOpen, trustedDomains)) {
return this._doOpenExternal(resource);
} else {
return this._dialogService.show(
Severity.Info,
localize(
'openExternalLinkAt',
'Do you want {0} to open the external website?\n{1}',
this._productService.nameShort,
resource.toString(true)
),
[
localize('openLink', 'Open Link'),
localize('cancel', 'Cancel'),
localize('configureTrustedDomains', 'Configure Trusted Domains')
],
{
cancelId: 1
}).then((choice) => {
if (choice === 0) {
return this._doOpenExternal(resource);
} else if (choice === 2) {
return this._commandService.executeCommand('workbench.action.configureTrustedDomains', domainToOpen).then((pickedDomains: string[]) => {
if (pickedDomains.indexOf(domainToOpen) !== -1) {
return this._doOpenExternal(resource);
}
return Promise.resolve(false);
});
}
return Promise.resolve(false);
});
}
// open link in default browser
return this._doOpenExternal(resource);
} else if (equalsIgnoreCase(scheme, Schemas.command)) {
// run command or bail out if command isn't known
if (!CommandsRegistry.getCommand(path)) {
@@ -158,22 +124,8 @@ export class OpenerService implements IOpenerService {
return Promise.resolve(true);
}
}
/**
* Check whether a domain like https://www.microsoft.com matches
* the list of trusted domains.
*/
function isDomainTrusted(domain: string, trustedDomains: string[]) {
for (let i = 0; i < trustedDomains.length; i++) {
if (trustedDomains[i] === '*') {
return true;
}
if (trustedDomains[i] === domain) {
return true;
}
dispose() {
this._validators.clear();
}
return false;
}

View File

@@ -367,6 +367,10 @@ export let completionKindFromString: {
};
})();
export const enum CompletionItemKindModifier {
Deprecated = 1
}
export const enum CompletionItemInsertTextRule {
/**
* Adjust whitespace/indentation of multiline insert texts to
@@ -396,6 +400,11 @@ export interface CompletionItem {
* an icon is chosen by the editor.
*/
kind: CompletionItemKind;
/**
* A modifier to the `kind` which affect how the item
* is rendered, e.g. Deprecated is rendered with a strikeout
*/
kindModifier?: CompletionItemKindModifier;
/**
* A human-readable string with additional information
* about this item, like type or symbol information.
@@ -464,7 +473,7 @@ export interface CompletionItem {
/**
* @internal
*/
[key: string]: any;
_id?: [number, number];
}
export interface CompletionList {

View File

@@ -581,6 +581,10 @@ export enum CompletionItemKind {
Snippet = 25
}
export enum CompletionItemKindModifier {
Deprecated = 1
}
export enum CompletionItemInsertTextRule {
/**
* Adjust whitespace/indentation of multiline insert texts to

View File

@@ -12,19 +12,20 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
import { FindOptionsWidget } from 'vs/editor/contrib/find/findOptionsWidget';
import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/findState';
import { FindWidget, IFindController } from 'vs/editor/contrib/find/findWidget';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
const SEARCH_STRING_MAX_LENGTH = 524288;
@@ -75,7 +76,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
protected _state: FindReplaceState;
protected _updateHistoryDelayer: Delayer<void>;
private _model: FindModelBoundToEditorModel | null;
private readonly _storageService: IStorageService;
protected readonly _storageService: IStorageService;
private readonly _clipboardService: IClipboardService;
protected readonly _contextKeyService: IContextKeyService;
@@ -383,10 +384,11 @@ export class FindController extends CommonFindController implements IFindControl
@IContextKeyService _contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IThemeService private readonly _themeService: IThemeService,
@IStorageService storageService: IStorageService,
@optional(IClipboardService) clipboardService: IClipboardService
@INotificationService private readonly _notificationService: INotificationService,
@IStorageService _storageService: IStorageService,
@optional(IClipboardService) clipboardService: IClipboardService,
) {
super(editor, _contextKeyService, storageService, clipboardService);
super(editor, _contextKeyService, _storageService, clipboardService);
this._widget = null;
this._findOptionsWidget = null;
}
@@ -422,7 +424,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._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService));
this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService));
}
}
@@ -540,6 +542,27 @@ export class NextMatchFindAction extends MatchFindAction {
}
}
export class NextMatchFindAction2 extends MatchFindAction {
constructor() {
super({
id: FIND_IDS.NextMatchFindAction,
label: nls.localize('findNextMatchAction', "Find Next"),
alias: 'Find Next',
precondition: undefined,
kbOpts: {
kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),
primary: KeyCode.Enter,
weight: KeybindingWeight.EditorContrib
}
});
}
protected _run(controller: CommonFindController): boolean {
return controller.moveToNextMatch();
}
}
export class PreviousMatchFindAction extends MatchFindAction {
constructor() {
@@ -562,6 +585,27 @@ export class PreviousMatchFindAction extends MatchFindAction {
}
}
export class PreviousMatchFindAction2 extends MatchFindAction {
constructor() {
super({
id: FIND_IDS.PreviousMatchFindAction,
label: nls.localize('findPreviousMatchAction', "Find Previous"),
alias: 'Find Previous',
precondition: undefined,
kbOpts: {
kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),
primary: KeyMod.Shift | KeyCode.Enter,
weight: KeybindingWeight.EditorContrib
}
});
}
protected _run(controller: CommonFindController): boolean {
return controller.moveToPrevMatch();
}
}
export abstract class SelectionMatchFindAction extends EditorAction {
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
let controller = CommonFindController.get(editor);
@@ -695,7 +739,9 @@ registerEditorContribution(FindController);
registerEditorAction(StartFindAction);
registerEditorAction(StartFindWithSelectionAction);
registerEditorAction(NextMatchFindAction);
registerEditorAction(NextMatchFindAction2);
registerEditorAction(PreviousMatchFindAction);
registerEditorAction(PreviousMatchFindAction2);
registerEditorAction(NextSelectionMatchFindAction);
registerEditorAction(PreviousSelectionMatchFindAction);
registerEditorAction(StartFindReplaceAction);
@@ -781,6 +827,17 @@ registerEditorCommand(new FindCommand({
}
}));
registerEditorCommand(new FindCommand({
id: FIND_IDS.ReplaceOneAction,
precondition: CONTEXT_FIND_WIDGET_VISIBLE,
handler: x => x.replace(),
kbOpts: {
weight: KeybindingWeight.EditorContrib + 5,
kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED),
primary: KeyCode.Enter
}
}));
registerEditorCommand(new FindCommand({
id: FIND_IDS.ReplaceAllAction,
precondition: CONTEXT_FIND_WIDGET_VISIBLE,
@@ -792,6 +849,20 @@ registerEditorCommand(new FindCommand({
}
}));
registerEditorCommand(new FindCommand({
id: FIND_IDS.ReplaceAllAction,
precondition: CONTEXT_FIND_WIDGET_VISIBLE,
handler: x => x.replaceAll(),
kbOpts: {
weight: KeybindingWeight.EditorContrib + 5,
kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED),
primary: undefined,
mac: {
primary: KeyMod.CtrlCmd | KeyCode.Enter,
}
}
}));
registerEditorCommand(new FindCommand({
id: FIND_IDS.SelectAllMatchesAction,
precondition: CONTEXT_FIND_WIDGET_VISIBLE,

View File

@@ -32,8 +32,8 @@
.monaco-editor .find-widget {
position: absolute;
z-index: 10;
top: -44px; /* find input height + shadow (10px) */
height: 34px; /* find input height */
top: -44px;
height: 33px;
overflow: hidden;
line-height: 19px;
transition: top 200ms linear;
@@ -47,12 +47,10 @@
/* Find widget when replace is toggled on */
.monaco-editor .find-widget.replaceToggled {
top: -74px; /* find input height + replace input height + shadow (10px) */
height: 64px; /* find input height + replace input height */
}
.monaco-editor .find-widget.replaceToggled > .replace-part {
display: flex;
display: -webkit-flex;
align-items: center;
}
.monaco-editor .find-widget.visible,
@@ -60,13 +58,31 @@
top: 0;
}
/* Multiple line find widget */
.monaco-editor .find-widget.multipleline {
top: unset;
bottom: 10px;
}
.monaco-editor .find-widget.multipleline.visible,
.monaco-editor .find-widget.multipleline.replaceToggled.visible {
top: 0px;
bottom: unset;
}
.monaco-editor .find-widget .monaco-inputbox.synthetic-focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
.monaco-editor .find-widget .monaco-inputbox .input {
background-color: transparent;
/* Style to compensate for //winjs */
min-height: 0;
}
.monaco-editor .find-widget .replace-input .input {
.monaco-editor .find-widget .monaco-findInput .input {
font-size: 13px;
}
@@ -76,29 +92,38 @@
font-size: 12px;
display: flex;
display: -webkit-flex;
align-items: center;
}
.monaco-editor .find-widget > .find-part .monaco-inputbox,
.monaco-editor .find-widget > .replace-part .monaco-inputbox {
height: 25px;
min-height: 25px;
}
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input {
width: 100% !important;
padding-right: 66px;
}
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror {
padding-right: 22px;
}
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input,
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .mirror,
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input,
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror {
padding-top: 2px;
padding-bottom: 2px;
}
.monaco-editor .find-widget > .find-part .find-actions {
height: 25px;
display: flex;
align-items: center;
}
.monaco-editor .find-widget > .replace-part .replace-actions {
height: 25px;
display: flex;
align-items: center;
}
.monaco-editor .find-widget .monaco-findInput {
vertical-align: middle;
display: flex;
@@ -106,6 +131,16 @@
flex:1;
}
.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element {
/* Make sure textarea inherits the width correctly */
width: 100%;
}
.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element .scrollbar.vertical {
/* Hide vertical scrollbar */
opacity: 0;
}
.monaco-editor .find-widget .matchesCount {
display: flex;
display: -webkit-flex;
@@ -237,15 +272,17 @@
display: none;
}
.monaco-editor .find-widget > .replace-part > .replace-input {
.monaco-editor .find-widget > .replace-part > .monaco-findInput {
position: relative;
display: flex;
display: -webkit-flex;
vertical-align: middle;
width: auto !important;
flex: auto;
flex-grow: 0;
flex-shrink: 0;
}
.monaco-editor .find-widget > .replace-part > .replace-input > .controls {
.monaco-editor .find-widget > .replace-part > .monaco-findInput > .controls {
position: absolute;
top: 3px;
right: 2px;

View File

@@ -9,11 +9,12 @@ import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { alert as alertFn } from 'vs/base/browser/ui/aria/aria';
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput';
import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { Widget } from 'vs/base/browser/ui/widget';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { Delayer } from 'vs/base/common/async';
import { Color } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -28,11 +29,12 @@ import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_REPLACE_INPUT_FOCUSED, FIND_IDS, MA
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget';
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { alert as alertFn } from 'vs/base/browser/ui/aria/aria';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IFindController {
replace(): void;
@@ -48,7 +50,6 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind'
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");
const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case");
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
@@ -59,14 +60,12 @@ const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
const FIND_WIDGET_INITIAL_WIDTH = 411;
const PART_WIDTH = 275;
const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;
const REPLACE_INPUT_AREA_WIDTH = FIND_INPUT_AREA_WIDTH;
let MAX_MATCHES_COUNT_WIDTH = 69;
let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */;
const FIND_INPUT_AREA_HEIGHT = 34; // The height of Find Widget when Replace Input is not visible.
const FIND_REPLACE_AREA_HEIGHT = 64; // The height of Find Widget when Replace Input is visible.
const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible.
const ctrlEnterReplaceAllWarningPromptedKey = 'ctrlEnterReplaceAll.windows.donotask';
export class FindWidgetViewZone implements IViewZone {
public readonly afterLineNumber: number;
@@ -84,6 +83,22 @@ export class FindWidgetViewZone implements IViewZone {
}
}
function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {
const isMultiline = !!value.match(/\n/);
if (textarea && isMultiline && textarea.selectionStart > 0) {
event.stopPropagation();
return;
}
}
function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {
const isMultiline = !!value.match(/\n/);
if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) {
event.stopPropagation();
return;
}
}
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
private static readonly ID = 'editor.contrib.findWidget';
private readonly _codeEditor: ICodeEditor;
@@ -92,10 +107,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private readonly _contextViewProvider: IContextViewProvider;
private readonly _keybindingService: IKeybindingService;
private readonly _contextKeyService: IContextKeyService;
private readonly _storageService: IStorageService;
private readonly _notificationService: INotificationService;
private _domNode!: HTMLElement;
private _cachedHeight: number | null;
private _findInput!: FindInput;
private _replaceInputBox!: HistoryInputBox;
private _replaceInput!: ReplaceInput;
private _toggleReplaceBtn!: SimpleButton;
private _matchesCount!: HTMLElement;
@@ -103,13 +121,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _nextBtn!: SimpleButton;
private _toggleSelectionFind!: SimpleCheckbox;
private _closeBtn!: SimpleButton;
private _preserveCase!: Checkbox;
private _replaceBtn!: SimpleButton;
private _replaceAllBtn!: SimpleButton;
private _isVisible: boolean;
private _isReplaceVisible: boolean;
private _ignoreChangeEvent: boolean;
private _ctrlEnterReplaceAllWarningPrompted: boolean;
private readonly _findFocusTracker: dom.IFocusTracker;
private readonly _findInputFocused: IContextKey<boolean>;
@@ -129,7 +147,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
contextViewProvider: IContextViewProvider,
keybindingService: IKeybindingService,
contextKeyService: IContextKeyService,
themeService: IThemeService
themeService: IThemeService,
storageService: IStorageService,
notificationService: INotificationService,
) {
super();
this._codeEditor = codeEditor;
@@ -138,6 +158,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._contextViewProvider = contextViewProvider;
this._keybindingService = keybindingService;
this._contextKeyService = contextKeyService;
this._storageService = storageService;
this._notificationService = notificationService;
this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL);
this._isVisible = false;
this._isReplaceVisible = false;
@@ -149,6 +173,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._buildDomNode();
this._updateButtons();
this._tryUpdateWidgetWidth();
this._findInput.inputBox.layout();
this._register(this._codeEditor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.readOnly) {
@@ -203,7 +228,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}));
this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService);
this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInputBox.inputElement));
this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInput.inputBox.inputElement));
this._register(this._replaceFocusTracker.onDidFocus(() => {
this._replaceInputFocused.set(true);
this._updateSearchScope();
@@ -264,6 +289,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _onStateChanged(e: FindReplaceStateChangedEvent): void {
if (e.searchString) {
if (this._state.searchString.indexOf('\n') >= 0) {
dom.addClass(this._domNode, 'multipleline');
} else {
dom.removeClass(this._domNode, 'multipleline');
}
try {
this._ignoreChangeEvent = true;
this._findInput.setValue(this._state.searchString);
@@ -273,7 +304,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._updateButtons();
}
if (e.replaceString) {
this._replaceInputBox.value = this._state.replaceString;
this._replaceInput.inputBox.value = this._state.replaceString;
}
if (e.isRevealed) {
if (this._state.isRevealed) {
@@ -286,8 +317,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (this._state.isReplaceRevealed) {
if (!this._codeEditor.getConfiguration().readOnly && !this._isReplaceVisible) {
this._isReplaceVisible = true;
this._replaceInputBox.width = this._findInput.inputBox.width;
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
this._updateButtons();
this._replaceInput.inputBox.layout();
}
} else {
if (this._isReplaceVisible) {
@@ -296,6 +328,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
}
}
if ((e.isRevealed || e.isReplaceRevealed) && (this._state.isRevealed || this._state.isReplaceRevealed)) {
if (this._tryUpdateHeight()) {
this._showViewZone();
}
}
if (e.isRegex) {
this._findInput.setRegex(this._state.isRegex);
}
@@ -337,7 +375,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._findInput.inputBox.addToHistory();
}
if (this._state.replaceString) {
this._replaceInputBox.addToHistory();
this._replaceInput.inputBox.addToHistory();
}
}
@@ -402,7 +440,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _updateButtons(): void {
this._findInput.setEnabled(this._isVisible);
this._replaceInputBox.setEnabled(this._isVisible && this._isReplaceVisible);
this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible);
this._updateToggleSelectionFindButton();
this._closeBtn.setEnabled(this._isVisible);
@@ -512,12 +550,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
this._codeEditor.changeViewZones((accessor) => {
if (this._state.isReplaceRevealed) {
viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT;
} else {
viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
}
viewZone.heightInPx = this._getHeight();
this._viewZoneId = accessor.addZone(viewZone);
// scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning.
this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + viewZone.heightInPx);
@@ -525,30 +558,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
private _showViewZone(adjustScroll: boolean = true) {
const viewZone = this._viewZone;
if (!this._isVisible || !viewZone) {
if (!this._isVisible) {
return;
}
const addExtraSpaceOnTop = this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop;
if (!addExtraSpaceOnTop) {
return;
}
if (this._viewZone === undefined) {
this._viewZone = new FindWidgetViewZone(0);
}
const viewZone = this._viewZone;
this._codeEditor.changeViewZones((accessor) => {
let scrollAdjustment = FIND_INPUT_AREA_HEIGHT;
if (this._viewZoneId !== undefined) {
if (this._state.isReplaceRevealed) {
viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT;
scrollAdjustment = FIND_REPLACE_AREA_HEIGHT - FIND_INPUT_AREA_HEIGHT;
} else {
viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
scrollAdjustment = FIND_INPUT_AREA_HEIGHT - FIND_REPLACE_AREA_HEIGHT;
// the view zone already exists, we need to update the height
const newHeight = this._getHeight();
if (newHeight === viewZone.heightInPx) {
return;
}
accessor.removeZone(this._viewZoneId);
} else {
viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
}
this._viewZoneId = accessor.addZone(viewZone);
if (adjustScroll) {
this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);
let scrollAdjustment = newHeight - viewZone.heightInPx;
viewZone.heightInPx = newHeight;
accessor.layoutZone(this._viewZoneId);
if (adjustScroll) {
this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);
}
return;
} else {
const scrollAdjustment = this._getHeight();
viewZone.heightInPx = scrollAdjustment;
this._viewZoneId = accessor.addZone(viewZone);
if (adjustScroll) {
this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);
}
}
});
}
@@ -584,8 +634,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder),
};
this._findInput.style(inputStyles);
this._replaceInputBox.style(inputStyles);
this._preserveCase.style(inputStyles);
this._replaceInput.style(inputStyles);
}
private _tryUpdateWidgetWidth() {
@@ -615,7 +664,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {
// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.
this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;
this._replaceInputBox.inputElement.style.width = `${dom.getTotalWidth(this._findInput.inputBox.inputElement)}px`;
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
return;
}
}
@@ -639,13 +688,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
if (this._resized) {
let findInputWidth = dom.getTotalWidth(this._findInput.inputBox.inputElement);
this._findInput.inputBox.layout();
let findInputWidth = this._findInput.inputBox.width;
if (findInputWidth > 0) {
this._replaceInputBox.inputElement.style.width = `${findInputWidth}px`;
this._replaceInput.width = findInputWidth;
}
}
}
private _getHeight(): number {
let totalheight = 0;
// find input margin top
totalheight += 4;
// find input height
totalheight += this._findInput.inputBox.height + 2 /** input box border */;
if (this._isReplaceVisible) {
// replace input margin
totalheight += 4;
totalheight += this._replaceInput.inputBox.height + 2 /** input box border */;
}
// margin bottom
totalheight += 4;
return totalheight;
}
private _tryUpdateHeight(): boolean {
const totalHeight = this._getHeight();
if (this._cachedHeight !== null && this._cachedHeight === totalHeight) {
return false;
}
this._cachedHeight = totalHeight;
this._domNode.style.height = `${totalHeight}px`;
return true;
}
// ----- Public
public focusFindInput(): void {
@@ -655,9 +738,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
public focusReplaceInput(): void {
this._replaceInputBox.select();
this._replaceInput.select();
// Edge browser requires focus() in addition to select()
this._replaceInputBox.focus();
this._replaceInput.focus();
}
public highlightFindOptions(): void {
@@ -692,22 +775,25 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
private _onFindInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) {
const inputElement = this._findInput.inputBox.inputElement;
const start = inputElement.selectionStart;
const end = inputElement.selectionEnd;
const content = inputElement.value;
if (e.equals(KeyCode.Enter)) {
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
e.preventDefault();
return;
}
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
e.preventDefault();
return;
if (start && end) {
const value = content.substr(0, start) + '\n' + content.substr(end);
this._findInput.inputBox.value = value;
inputElement.setSelectionRange(start + 1, start + 1);
this._findInput.inputBox.layout();
e.preventDefault();
return;
}
}
if (e.equals(KeyCode.Tab)) {
if (this._isReplaceVisible) {
this._replaceInputBox.focus();
this._replaceInput.focus();
} else {
this._findInput.focusOnCaseSensitive();
}
@@ -720,20 +806,43 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
e.preventDefault();
return;
}
if (e.equals(KeyCode.UpArrow)) {
return stopPropagationForMultiLineUpwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));
}
if (e.equals(KeyCode.DownArrow)) {
return stopPropagationForMultiLineDownwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));
}
}
private _onReplaceInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) {
if (platform.isWindows && platform.isNative && !this._ctrlEnterReplaceAllWarningPrompted) {
// this is the first time when users press Ctrl + Enter to replace all
this._notificationService.info(
nls.localize('ctrlEnter.keybindingChanged',
'Ctrl+Enter now inserts line break instead of replacing all. You can modify the keybinding for editor.action.replaceAll to override this behavior.')
);
if (e.equals(KeyCode.Enter)) {
this._controller.replace();
e.preventDefault();
return;
}
this._ctrlEnterReplaceAllWarningPrompted = true;
this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL);
if (e.equals(KeyMod.CtrlCmd | KeyCode.Enter)) {
this._controller.replaceAll();
e.preventDefault();
return;
}
const inputElement = this._replaceInput.inputBox.inputElement;
const start = inputElement.selectionStart;
const end = inputElement.selectionEnd;
const content = inputElement.value;
if (start && end) {
const value = content.substr(0, start) + '\n' + content.substr(end);
this._replaceInput.inputBox.value = value;
inputElement.setSelectionRange(start + 1, start + 1);
this._replaceInput.inputBox.layout();
e.preventDefault();
return;
}
}
if (e.equals(KeyCode.Tab)) {
@@ -753,6 +862,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
e.preventDefault();
return;
}
if (e.equals(KeyCode.UpArrow)) {
return stopPropagationForMultiLineUpwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));
}
if (e.equals(KeyCode.DownArrow)) {
return stopPropagationForMultiLineDownwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));
}
}
// ----- sash
@@ -777,6 +894,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
private _buildDomNode(): void {
const flexibleHeight = true;
const flexibleWidth = true;
// Find input
this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, {
width: FIND_INPUT_AREA_WIDTH,
@@ -796,7 +915,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
} catch (e) {
return { content: e.message };
}
}
},
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118
}, this._contextKeyService, true));
this._findInput.setRegex(!!this._state.isRegex);
this._findInput.setCaseSensitive(!!this._state.matchCase);
@@ -818,11 +940,24 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._register(this._findInput.onCaseSensitiveKeyDown((e) => {
if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
if (this._isReplaceVisible) {
this._replaceInputBox.focus();
this._replaceInput.focus();
e.preventDefault();
}
}
}));
this._register(this._findInput.onRegexKeyDown((e) => {
if (e.equals(KeyCode.Tab)) {
if (this._isReplaceVisible) {
this._replaceInput.focusOnPreserve();
e.preventDefault();
}
}
}));
this._register(this._findInput.inputBox.onDidHeightChange((e) => {
if (this._tryUpdateHeight()) {
this._showViewZone();
}
}));
if (platform.isLinux) {
this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));
}
@@ -852,13 +987,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
let findPart = document.createElement('div');
findPart.className = 'find-part';
findPart.appendChild(this._findInput.domNode);
findPart.appendChild(this._matchesCount);
findPart.appendChild(this._prevBtn.domNode);
findPart.appendChild(this._nextBtn.domNode);
const actionsContainer = document.createElement('div');
actionsContainer.className = 'find-actions';
findPart.appendChild(actionsContainer);
actionsContainer.appendChild(this._matchesCount);
actionsContainer.appendChild(this._prevBtn.domNode);
actionsContainer.appendChild(this._nextBtn.domNode);
// Toggle selection button
this._toggleSelectionFind = this._register(new SimpleCheckbox({
parent: findPart,
parent: actionsContainer,
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
onChange: () => {
if (this._toggleSelectionFind.checked) {
@@ -898,34 +1036,45 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
}));
findPart.appendChild(this._closeBtn.domNode);
actionsContainer.appendChild(this._closeBtn.domNode);
// Replace input
let replaceInput = document.createElement('div');
replaceInput.className = 'replace-input';
replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px';
this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, undefined, {
ariaLabel: NLS_REPLACE_INPUT_LABEL,
this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, {
label: NLS_REPLACE_INPUT_LABEL,
placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
history: []
}, this._contextKeyService));
this._register(dom.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e)));
this._register(this._replaceInputBox.onDidChange(() => {
this._state.change({ replaceString: this._replaceInputBox.value }, false);
history: [],
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118
}, this._contextKeyService, true));
this._replaceInput.setPreserveCase(!!this._state.preserveCase);
this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e)));
this._register(this._replaceInput.inputBox.onDidChange(() => {
this._state.change({ replaceString: this._replaceInput.inputBox.value }, false);
}));
this._preserveCase = this._register(new Checkbox({
actionClassName: 'monaco-preserve-case',
title: NLS_PRESERVE_CASE_LABEL,
isChecked: false,
this._register(this._replaceInput.inputBox.onDidHeightChange((e) => {
if (this._isReplaceVisible && this._tryUpdateHeight()) {
this._showViewZone();
}
}));
this._preserveCase.checked = !!this._state.preserveCase;
this._register(this._preserveCase.onChange(viaKeyboard => {
if (!viaKeyboard) {
this._state.change({ preserveCase: !this._state.preserveCase }, false);
this._replaceInputBox.focus();
this._register(this._replaceInput.onDidOptionChange(() => {
this._state.change({
preserveCase: this._replaceInput.getPreserveCase()
}, true);
}));
this._register(this._replaceInput.onPreserveCaseKeyDown((e) => {
if (e.equals(KeyCode.Tab)) {
if (this._prevBtn.isEnabled()) {
this._prevBtn.focus();
} else if (this._nextBtn.isEnabled()) {
this._nextBtn.focus();
} else if (this._toggleSelectionFind.isEnabled()) {
this._toggleSelectionFind.focus();
} else if (this._closeBtn.isEnabled()) {
this._closeBtn.focus();
}
e.preventDefault();
}
}));
@@ -953,17 +1102,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
}));
let controls = document.createElement('div');
controls.className = 'controls';
controls.style.display = 'block';
controls.appendChild(this._preserveCase.domNode);
replaceInput.appendChild(controls);
let replacePart = document.createElement('div');
replacePart.className = 'replace-part';
replacePart.appendChild(replaceInput);
replacePart.appendChild(this._replaceBtn.domNode);
replacePart.appendChild(this._replaceAllBtn.domNode);
replacePart.appendChild(this._replaceInput.domNode);
const replaceActionsContainer = document.createElement('div');
replaceActionsContainer.className = 'replace-actions';
replacePart.appendChild(replaceActionsContainer);
replaceActionsContainer.appendChild(this._replaceBtn.domNode);
replaceActionsContainer.appendChild(this._replaceAllBtn.domNode);
// Toggle replace button
this._toggleReplaceBtn = this._register(new SimpleButton({
@@ -972,7 +1120,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
onTrigger: () => {
this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false);
if (this._isReplaceVisible) {
this._replaceInputBox.width = this._findInput.inputBox.width;
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
this._replaceInput.inputBox.layout();
}
this._showViewZone();
}
@@ -1015,9 +1164,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
return;
}
this._domNode.style.width = `${width}px`;
this._findInput.inputBox.width = inputBoxWidth;
if (this._isReplaceVisible) {
this._replaceInputBox.width = inputBoxWidth;
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
}
this._findInput.inputBox.layout();
this._tryUpdateHeight();
}));
}
@@ -1077,6 +1230,10 @@ class SimpleCheckbox extends Widget {
return this._domNode;
}
public isEnabled(): boolean {
return (this._domNode.tabIndex >= 0);
}
public get checked(): boolean {
return this._checkbox.checked;
}
@@ -1086,7 +1243,7 @@ class SimpleCheckbox extends Widget {
}
public focus(): void {
this._checkbox.focus();
this._domNode.focus();
}
private enable(): void {
@@ -1245,4 +1402,11 @@ registerThemingParticipant((theme, collector) => {
if (inputActiveBackground) {
collector.addRule(`.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { background-color: ${inputActiveBackground.toString()}; }`);
}
// This rule is used to override the outline color for synthetic-focus find input.
const focusOutline = theme.getColor(focusBorder);
if (focusOutline) {
collector.addRule(`.monaco-workbench .monaco-editor .find-widget .monaco-inputbox.synthetic-focus { outline-color: ${focusOutline}; }`);
}
});

View File

@@ -52,7 +52,7 @@ export class ParameterHintsModel extends Disposable {
public readonly onChangedHints = this._onChangedHints.event;
private readonly editor: ICodeEditor;
private enabled: boolean;
private triggerOnType = false;
private _state: ParameterHintState.State = ParameterHintState.Default;
private readonly _lastSignatureHelpResult = this._register(new MutableDisposable<modes.SignatureHelpResult>());
private triggerChars = new CharacterSet();
@@ -68,7 +68,6 @@ export class ParameterHintsModel extends Disposable {
super();
this.editor = editor;
this.enabled = false;
this.throttledDelayer = new Delayer(delay);
@@ -242,7 +241,7 @@ export class ParameterHintsModel extends Disposable {
}
private onDidType(text: string) {
if (!this.enabled) {
if (!this.triggerOnType) {
return;
}
@@ -272,9 +271,9 @@ export class ParameterHintsModel extends Disposable {
}
private onEditorConfigurationChange(): void {
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
this.triggerOnType = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
if (!this.enabled) {
if (!this.triggerOnType) {
this.cancel();
}
}
@@ -283,4 +282,4 @@ export class ParameterHintsModel extends Disposable {
this.cancel(true);
super.dispose();
}
}
}

View File

@@ -97,6 +97,10 @@
font-weight: bold;
}
.monaco-editor .suggest-widget-deprecated span {
text-decoration: line-through;
}
/** Icon styles **/
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close,

View File

@@ -30,7 +30,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async';
import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes';
import { CompletionItemKind, completionKindToCssClass, CompletionItemKindModifier } from 'vs/editor/common/modes';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -38,6 +38,7 @@ import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileKind } from 'vs/platform/files/common/files';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { flatten } from 'vs/base/common/arrays';
const expandSuggestionDocsByDefault = false;
@@ -60,7 +61,6 @@ export const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.
export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: listFocusBackground, light: listFocusBackground, hc: listFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
export const editorSuggestWidgetHighlightForeground = registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hc: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.'));
const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i;
function extractColor(item: CompletionItem, out: string[]): boolean {
if (item.completion.label.match(colorRegExp)) {
@@ -173,18 +173,18 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
} else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) {
// special logic for 'file' completion items
data.icon.className = 'icon hide';
labelOptions.extraClasses = ([] as string[]).concat(
labelOptions.extraClasses = flatten([
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FILE),
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE)
);
]);
} else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) {
// special logic for 'folder' completion items
data.icon.className = 'icon hide';
labelOptions.extraClasses = ([] as string[]).concat(
labelOptions.extraClasses = flatten([
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FOLDER),
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FOLDER)
);
]);
} else {
// normal icon
data.icon.className = 'icon hide';
@@ -193,6 +193,10 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
];
}
if (suggestion.kindModifier && suggestion.kindModifier & CompletionItemKindModifier.Deprecated) {
labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['suggest-widget-deprecated']);
}
data.iconLabel.setLabel(suggestion.label, undefined, labelOptions);
data.typeLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, '');

View File

@@ -38,9 +38,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/product';
import { IStorageService } from 'vs/platform/storage/common/storage';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -54,13 +51,7 @@ function withAllStandaloneServices<T extends editorCommon.IEditor>(domElement: H
}
if (!services.has(IOpenerService)) {
services.set(IOpenerService, new OpenerService(
services.get(ICodeEditorService),
services.get(ICommandService),
services.get(IStorageService),
services.get(IDialogService),
services.get(IProductService)
));
services.set(IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService)));
}
let result = callback(services);

View File

@@ -562,6 +562,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
// enums
DocumentHighlightKind: standaloneEnums.DocumentHighlightKind,
CompletionItemKind: standaloneEnums.CompletionItemKind,
CompletionItemKindModifier: standaloneEnums.CompletionItemKindModifier,
CompletionItemInsertTextRule: standaloneEnums.CompletionItemInsertTextRule,
SymbolKind: standaloneEnums.SymbolKind,
IndentAction: standaloneEnums.IndentAction,

View File

@@ -7,18 +7,13 @@ import { URI } from 'vs/base/common/uri';
import { OpenerService } from 'vs/editor/browser/services/openerService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { deepClone } from 'vs/base/common/objects';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/product';
import { IStorageService } from 'vs/platform/storage/common/storage';
suite('OpenerService', function () {
const editorService = new TestCodeEditorService();
let lastCommand: { id: string, args: any[] } | undefined;
let lastCommand: { id: string; args: any[] } | undefined;
const commandService = new class implements ICommandService {
const commandService = new (class implements ICommandService {
_serviceBrand: any;
onWillExecuteCommand = () => ({ dispose: () => { } });
onDidExecuteCommand = () => ({ dispose: () => { } });
@@ -26,79 +21,20 @@ suite('OpenerService', function () {
lastCommand = { id, args };
return Promise.resolve(undefined);
}
};
function getStorageService(trustedDomainsSetting: string[]) {
let _settings = deepClone(trustedDomainsSetting);
return new class implements IStorageService {
get = () => JSON.stringify(_settings);
store = (key: string, val: string) => _settings = JSON.parse(val);
// Don't care
_serviceBrand: any;
onDidChangeStorage = () => ({ dispose: () => { } });
onWillSaveState = () => ({ dispose: () => { } });
getBoolean = () => true;
getNumber = () => 0;
remove = () => { };
logStorage = () => { };
};
}
function getDialogService() {
return new class implements IDialogService {
_showInvoked = 0;
show = () => {
this._showInvoked++;
return Promise.resolve({} as any);
}
get confirmInvoked() { return this._showInvoked; }
// Don't care
_serviceBrand: any;
confirm = () => {
return Promise.resolve({} as any);
}
};
}
function getProductService(): IProductService {
return new class {
nameShort: 'VS Code';
_serviceBrand: any;
} as IProductService;
}
})();
setup(function () {
lastCommand = undefined;
});
test('delegate to editorService, scheme:///fff', function () {
const openerService = new OpenerService(
editorService,
NullCommandService,
getStorageService([]),
getDialogService(),
getProductService()
);
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('another:///somepath'));
assert.equal(editorService.lastInput!.options!.selection, undefined);
});
test('delegate to editorService, scheme:///fff#L123', function () {
const openerService = new OpenerService(
editorService,
NullCommandService,
getStorageService([]),
getDialogService(),
getProductService()
);
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#L23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
@@ -120,14 +56,7 @@ suite('OpenerService', function () {
});
test('delegate to editorService, scheme:///fff#123,123', function () {
const openerService = new OpenerService(
editorService,
NullCommandService,
getStorageService([]),
getDialogService(),
getProductService()
);
const openerService = new OpenerService(editorService, NullCommandService);
openerService.open(URI.parse('file:///somepath#23'));
assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23);
@@ -145,14 +74,7 @@ suite('OpenerService', function () {
});
test('delegate to commandsService, command:someid', function () {
const openerService = new OpenerService(
editorService,
commandService,
getStorageService([]),
getDialogService(),
getProductService()
);
const openerService = new OpenerService(editorService, commandService);
const id = `aCommand${Math.random()}`;
CommandsRegistry.registerCommand(id, function () { });
@@ -173,69 +95,107 @@ suite('OpenerService', function () {
assert.equal(lastCommand!.args[1], true);
});
test('links are protected by dialog.show', function () {
const dialogService = getDialogService();
const openerService = new OpenerService(
editorService,
commandService,
getStorageService([]),
dialogService,
getProductService()
);
test('links are protected by validators', async function () {
const openerService = new OpenerService(editorService, commandService);
openerService.open(URI.parse('https://www.microsoft.com'));
assert.equal(dialogService.confirmInvoked, 1);
openerService.registerValidator({ shouldOpen: () => Promise.resolve(false) });
const httpResult = await openerService.open(URI.parse('https://www.microsoft.com'));
const httpsResult = await openerService.open(URI.parse('https://www.microsoft.com'));
assert.equal(httpResult, false);
assert.equal(httpsResult, false);
});
test('links on the whitelisted domains can be opened without dialog.show', function () {
const dialogService = getDialogService();
const openerService = new OpenerService(
editorService,
commandService,
getStorageService(['https://microsoft.com']),
dialogService,
getProductService()
);
test('links validated by validators go to openers', async function () {
const openerService = new OpenerService(editorService, commandService);
openerService.open(URI.parse('https://microsoft.com'));
openerService.open(URI.parse('https://microsoft.com/'));
openerService.open(URI.parse('https://microsoft.com/en-us/'));
openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar'));
openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar#baz'));
openerService.registerValidator({ shouldOpen: () => Promise.resolve(true) });
assert.equal(dialogService.confirmInvoked, 0);
let openCount = 0;
openerService.registerOpener({
open: (resource: URI) => {
openCount++;
return Promise.resolve(true);
}
});
await openerService.open(URI.parse('http://microsoft.com'));
assert.equal(openCount, 1);
await openerService.open(URI.parse('https://microsoft.com'));
assert.equal(openCount, 2);
});
test('variations of links are protected by dialog confirmation', function () {
const dialogService = getDialogService();
const openerService = new OpenerService(
editorService,
commandService,
getStorageService(['https://microsoft.com']),
dialogService,
getProductService()
);
test('links validated by multiple validators', async function () {
const openerService = new OpenerService(editorService, commandService);
openerService.open(URI.parse('http://microsoft.com'));
openerService.open(URI.parse('https://www.microsoft.com'));
let v1 = 0;
openerService.registerValidator({
shouldOpen: () => {
v1++;
return Promise.resolve(true);
}
});
assert.equal(dialogService.confirmInvoked, 2);
let v2 = 0;
openerService.registerValidator({
shouldOpen: () => {
v2++;
return Promise.resolve(true);
}
});
let openCount = 0;
openerService.registerOpener({
open: (resource: URI) => {
openCount++;
return Promise.resolve(true);
}
});
await openerService.open(URI.parse('http://microsoft.com'));
assert.equal(openCount, 1);
assert.equal(v1, 1);
assert.equal(v2, 1);
await openerService.open(URI.parse('https://microsoft.com'));
assert.equal(openCount, 2);
assert.equal(v1, 2);
assert.equal(v2, 2);
});
test('* removes all link protection', function () {
const dialogService = getDialogService();
const openerService = new OpenerService(
editorService,
commandService,
getStorageService(['*']),
dialogService,
getProductService()
);
test('links invalidated by first validator do not continue validating', async function () {
const openerService = new OpenerService(editorService, commandService);
openerService.open(URI.parse('https://code.visualstudio.com/'));
openerService.open(URI.parse('https://www.microsoft.com'));
openerService.open(URI.parse('https://www.github.com'));
let v1 = 0;
openerService.registerValidator({
shouldOpen: () => {
v1++;
return Promise.resolve(false);
}
});
assert.equal(dialogService.confirmInvoked, 0);
let v2 = 0;
openerService.registerValidator({
shouldOpen: () => {
v2++;
return Promise.resolve(true);
}
});
let openCount = 0;
openerService.registerOpener({
open: (resource: URI) => {
openCount++;
return Promise.resolve(true);
}
});
await openerService.open(URI.parse('http://microsoft.com'));
assert.equal(openCount, 0);
assert.equal(v1, 1);
assert.equal(v2, 0);
await openerService.open(URI.parse('https://microsoft.com'));
assert.equal(openCount, 0);
assert.equal(v1, 2);
assert.equal(v2, 0);
});
});