mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 01:25:38 -05:00
Merge from vscode e558dc6ea73a75bd69d7a0b485f0e7e4194c66bf (#6864)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}; }`);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, '');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user