mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 13:27:25 +02:00
Update to VS Code 1.52.1
This commit is contained in:
@@ -355,13 +355,18 @@ class MouseDownOperation extends Disposable {
|
||||
e.buttons,
|
||||
createMouseMoveEventMerger(null),
|
||||
(e) => this._onMouseDownThenMove(e),
|
||||
() => {
|
||||
(browserEvent?: MouseEvent | KeyboardEvent) => {
|
||||
const position = this._findMousePosition(this._lastMouseEvent!, true);
|
||||
|
||||
this._viewController.emitMouseDrop({
|
||||
event: this._lastMouseEvent!,
|
||||
target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone
|
||||
});
|
||||
if (browserEvent && browserEvent instanceof KeyboardEvent) {
|
||||
// cancel
|
||||
this._viewController.emitMouseDropCanceled();
|
||||
} else {
|
||||
this._viewController.emitMouseDrop({
|
||||
event: this._lastMouseEvent!,
|
||||
target: (position ? this._createMouseTarget(this._lastMouseEvent!, true) : null) // Ignoring because position is unknown, e.g., Content View Zone
|
||||
});
|
||||
}
|
||||
|
||||
this._stop();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
|
||||
|
||||
export interface IViewZoneData {
|
||||
viewZoneId: string;
|
||||
@@ -239,6 +240,7 @@ export class HitTestContext {
|
||||
public readonly layoutInfo: EditorLayoutInfo;
|
||||
public readonly viewDomNode: HTMLElement;
|
||||
public readonly lineHeight: number;
|
||||
public readonly stickyTabStops: boolean;
|
||||
public readonly typicalHalfwidthCharacterWidth: number;
|
||||
public readonly lastRenderData: PointerHandlerLastRenderData;
|
||||
|
||||
@@ -251,6 +253,7 @@ export class HitTestContext {
|
||||
this.layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
this.viewDomNode = viewHelper.viewDomNode;
|
||||
this.lineHeight = options.get(EditorOption.lineHeight);
|
||||
this.stickyTabStops = options.get(EditorOption.stickyTabStops);
|
||||
this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth;
|
||||
this.lastRenderData = lastRenderData;
|
||||
this._context = context;
|
||||
@@ -329,6 +332,14 @@ export class HitTestContext {
|
||||
return this._context.viewLayout.isAfterLines(mouseVerticalOffset);
|
||||
}
|
||||
|
||||
public isInTopPadding(mouseVerticalOffset: number): boolean {
|
||||
return this._context.viewLayout.isInTopPadding(mouseVerticalOffset);
|
||||
}
|
||||
|
||||
public isInBottomPadding(mouseVerticalOffset: number): boolean {
|
||||
return this._context.viewLayout.isInBottomPadding(mouseVerticalOffset);
|
||||
}
|
||||
|
||||
public getVerticalOffsetForLineNumber(lineNumber: number): number {
|
||||
return this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber);
|
||||
}
|
||||
@@ -656,8 +667,12 @@ export class MouseTargetFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx.isInTopPadding(request.mouseVerticalOffset)) {
|
||||
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(1, 1), undefined, EMPTY_CONTENT_AFTER_LINES);
|
||||
}
|
||||
|
||||
// Check if it is below any lines and any view zones
|
||||
if (ctx.isAfterLines(request.mouseVerticalOffset)) {
|
||||
if (ctx.isAfterLines(request.mouseVerticalOffset) || ctx.isInBottomPadding(request.mouseVerticalOffset)) {
|
||||
// This most likely indicates it happened after the last view-line
|
||||
const lineCount = ctx.model.getLineCount();
|
||||
const maxLineColumn = ctx.model.getLineMaxColumn(lineCount);
|
||||
@@ -998,6 +1013,17 @@ export class MouseTargetFactory {
|
||||
};
|
||||
}
|
||||
|
||||
private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position {
|
||||
const minColumn = viewModel.getLineMinColumn(position.lineNumber);
|
||||
const lineContent = viewModel.getLineContent(position.lineNumber);
|
||||
const { tabSize } = viewModel.getTextModelOptions();
|
||||
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - minColumn, tabSize, Direction.Nearest);
|
||||
if (newPosition !== -1) {
|
||||
return new Position(position.lineNumber, newPosition + minColumn);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): IHitTestResult {
|
||||
// State of the art (18.10.2012):
|
||||
// The spec says browsers should support document.caretPositionFromPoint, but nobody implemented it (http://dev.w3.org/csswg/cssom-view/)
|
||||
@@ -1016,24 +1042,24 @@ export class MouseTargetFactory {
|
||||
|
||||
// Thank you browsers for making this so 'easy' :)
|
||||
|
||||
let result: IHitTestResult;
|
||||
if (typeof document.caretRangeFromPoint === 'function') {
|
||||
|
||||
return this._doHitTestWithCaretRangeFromPoint(ctx, request);
|
||||
|
||||
result = this._doHitTestWithCaretRangeFromPoint(ctx, request);
|
||||
} else if ((<any>document).caretPositionFromPoint) {
|
||||
|
||||
return this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
|
||||
|
||||
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
|
||||
} else if ((<any>document.body).createTextRange) {
|
||||
|
||||
return this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates());
|
||||
|
||||
result = this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates());
|
||||
} else {
|
||||
result = {
|
||||
position: null,
|
||||
hitTarget: null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
position: null,
|
||||
hitTarget: null
|
||||
};
|
||||
// Snap to the nearest soft tab boundary if atomic soft tabs are enabled.
|
||||
if (result.position && ctx.stickyTabStops) {
|
||||
result.position = this._snapToSoftTabBoundary(result.position, ctx.model);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1047,7 +1073,7 @@ export function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y:
|
||||
// Get the last child of the element until its firstChild is a text node
|
||||
// This assumes that the pointer is on the right of the line, out of the tokens
|
||||
// and that we want to get the offset of the last token of the line
|
||||
while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE) {
|
||||
while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE && el.lastChild && el.lastChild.firstChild) {
|
||||
el = <Element>el.lastChild;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ export class PointerEventHandler extends MouseHandler {
|
||||
}
|
||||
|
||||
public _onMouseDown(e: EditorMouseEvent): void {
|
||||
if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') {
|
||||
if ((e.browserEvent as any).pointerType === 'touch') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ime-input`);
|
||||
|
||||
this._viewController.compositionStart();
|
||||
this._context.model.onCompositionStart();
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => {
|
||||
@@ -297,6 +298,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
|
||||
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
|
||||
this._viewController.compositionEnd();
|
||||
this._context.model.onCompositionEnd();
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onFocus(() => {
|
||||
|
||||
@@ -295,9 +295,12 @@ export class TextAreaInput extends Disposable {
|
||||
this._onType.fire(typeInput);
|
||||
}
|
||||
|
||||
// Due to isEdgeOrIE (where the textarea was not cleared initially) and isChrome (the textarea is not updated correctly when composition ends)
|
||||
// Due to
|
||||
// isEdgeOrIE (where the textarea was not cleared initially)
|
||||
// and isChrome (the textarea is not updated correctly when composition ends)
|
||||
// and isFirefox (the textare ais not updated correctly after inserting emojis)
|
||||
// we cannot assume the text at the end consists only of the composited text
|
||||
if (browser.isEdge || browser.isChrome) {
|
||||
if (browser.isEdge || browser.isChrome || browser.isFirefox) {
|
||||
this._textAreaState = TextAreaState.readFromTextArea(this._textArea);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ export class MarkdownRenderer {
|
||||
}
|
||||
});
|
||||
|
||||
private readonly _onDidRenderCodeBlock = new Emitter<void>();
|
||||
readonly onDidRenderCodeBlock = this._onDidRenderCodeBlock.event;
|
||||
private readonly _onDidRenderAsync = new Emitter<void>();
|
||||
readonly onDidRenderAsync = this._onDidRenderAsync.event;
|
||||
|
||||
constructor(
|
||||
private readonly _options: IMarkdownRendererOptions,
|
||||
@@ -48,7 +48,7 @@ export class MarkdownRenderer {
|
||||
) { }
|
||||
|
||||
dispose(): void {
|
||||
this._onDidRenderCodeBlock.dispose();
|
||||
this._onDidRenderAsync.dispose();
|
||||
}
|
||||
|
||||
render(markdown: IMarkdownString | undefined, options?: MarkdownRenderOptions, markedOptions?: MarkedOptions): IMarkdownRenderResult {
|
||||
@@ -103,7 +103,7 @@ export class MarkdownRenderer {
|
||||
|
||||
return element;
|
||||
},
|
||||
codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire(),
|
||||
asyncRenderCallback: () => this._onDidRenderAsync.fire(),
|
||||
actionHandler: {
|
||||
callback: (content) => this._openerService.open(content, { fromUserGesture: true }).catch(onUnexpectedError),
|
||||
disposeables
|
||||
|
||||
@@ -494,6 +494,12 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
* @event
|
||||
*/
|
||||
onMouseDrop(listener: (e: IPartialEditorMouseEvent) => void): IDisposable;
|
||||
/**
|
||||
* An event emitted on a "mousedropcanceled".
|
||||
* @internal
|
||||
* @event
|
||||
*/
|
||||
onMouseDropCanceled(listener: () => void): IDisposable;
|
||||
/**
|
||||
* An event emitted on a "contextmenu".
|
||||
* @event
|
||||
@@ -678,10 +684,15 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
executeCommand(source: string | null | undefined, command: editorCommon.ICommand): void;
|
||||
|
||||
/**
|
||||
* Push an "undo stop" in the undo-redo stack.
|
||||
* Create an "undo stop" in the undo-redo stack.
|
||||
*/
|
||||
pushUndoStop(): boolean;
|
||||
|
||||
/**
|
||||
* Remove the "undo stop" in the undo-redo stack.
|
||||
*/
|
||||
popUndoStop(): boolean;
|
||||
|
||||
/**
|
||||
* Execute edits on the editor.
|
||||
* The edits will land on the undo-redo stack, but no "undo stop" will be pushed.
|
||||
|
||||
@@ -187,7 +187,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable {
|
||||
initialButtons: number,
|
||||
merger: EditorMouseEventMerger,
|
||||
mouseMoveCallback: (e: EditorMouseEvent) => void,
|
||||
onStopCallback: () => void
|
||||
onStopCallback: (browserEvent?: MouseEvent | KeyboardEvent) => void
|
||||
): void {
|
||||
|
||||
// Add a <<capture>> keydown event listener that will cancel the monitoring
|
||||
@@ -198,16 +198,16 @@ export class GlobalEditorMouseMoveMonitor extends Disposable {
|
||||
// Allow modifier keys
|
||||
return;
|
||||
}
|
||||
this._globalMouseMoveMonitor.stopMonitoring(true);
|
||||
this._globalMouseMoveMonitor.stopMonitoring(true, e.browserEvent);
|
||||
}, true);
|
||||
|
||||
const myMerger: dom.IEventMerger<EditorMouseEvent, MouseEvent> = (lastEvent: EditorMouseEvent | null, currentEvent: MouseEvent): EditorMouseEvent => {
|
||||
return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode));
|
||||
};
|
||||
|
||||
this._globalMouseMoveMonitor.startMonitoring(initialElement, initialButtons, myMerger, mouseMoveCallback, () => {
|
||||
this._globalMouseMoveMonitor.startMonitoring(initialElement, initialButtons, myMerger, mouseMoveCallback, (e) => {
|
||||
this._keydownListener!.dispose();
|
||||
onStopCallback();
|
||||
onStopCallback(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IPosition } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
@@ -409,47 +407,6 @@ export abstract class EditorAction2 extends Action2 {
|
||||
|
||||
// --- Registration of commands and actions
|
||||
|
||||
export function registerLanguageCommand<Args extends { [n: string]: any; }>(id: string, handler: (accessor: ServicesAccessor, args: Args) => any) {
|
||||
CommandsRegistry.registerCommand(id, (accessor, args) => handler(accessor, args || {}));
|
||||
}
|
||||
|
||||
interface IDefaultArgs {
|
||||
resource: URI;
|
||||
position: IPosition;
|
||||
[name: string]: any;
|
||||
}
|
||||
|
||||
export function registerDefaultLanguageCommand(id: string, handler: (model: ITextModel, position: Position, args: IDefaultArgs) => any) {
|
||||
registerLanguageCommand(id, function (accessor, args: IDefaultArgs) {
|
||||
|
||||
const { resource, position } = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument('resource');
|
||||
}
|
||||
if (!Position.isIPosition(position)) {
|
||||
throw illegalArgument('position');
|
||||
}
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (model) {
|
||||
const editorPosition = Position.lift(position);
|
||||
return handler(model, editorPosition, args);
|
||||
}
|
||||
|
||||
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = handler(reference.object.textEditorModel, Position.lift(position), args);
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}).finally(() => {
|
||||
reference.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function registerModelAndPositionCommand(id: string, handler: (model: ITextModel, position: Position, ...args: any[]) => any) {
|
||||
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
|
||||
|
||||
@@ -31,6 +31,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
|
||||
private readonly _onDidChangeTransientModelProperty: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
|
||||
public readonly onDidChangeTransientModelProperty: Event<ITextModel> = this._onDidChangeTransientModelProperty.event;
|
||||
|
||||
protected readonly _onDecorationTypeRegistered: Emitter<string> = this._register(new Emitter<string>());
|
||||
public onDecorationTypeRegistered: Event<string> = this._onDecorationTypeRegistered.event;
|
||||
|
||||
private readonly _codeEditors: { [editorId: string]: ICodeEditor; };
|
||||
private readonly _diffEditors: { [editorId: string]: IDiffEditor; };
|
||||
@@ -93,6 +95,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
|
||||
abstract registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void;
|
||||
abstract removeDecorationType(key: string): void;
|
||||
abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions;
|
||||
abstract resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null;
|
||||
|
||||
private readonly _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {};
|
||||
private readonly _modelProperties = new Map<string, Map<string, any>>();
|
||||
|
||||
@@ -10,6 +10,8 @@ import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const IBulkEditService = createDecorator<IBulkEditService>('IWorkspaceEditService');
|
||||
|
||||
@@ -65,9 +67,13 @@ export class ResourceFileEdit extends ResourceEdit {
|
||||
export interface IBulkEditOptions {
|
||||
editor?: ICodeEditor;
|
||||
progress?: IProgress<IProgressStep>;
|
||||
token?: CancellationToken;
|
||||
showPreview?: boolean;
|
||||
suppressPreview?: boolean;
|
||||
label?: string;
|
||||
quotableLabel?: string;
|
||||
undoRedoSource?: UndoRedoSource;
|
||||
undoRedoGroupId?: number;
|
||||
}
|
||||
|
||||
export interface IBulkEditResult {
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface ICodeEditorService {
|
||||
readonly onDiffEditorRemove: Event<IDiffEditor>;
|
||||
|
||||
readonly onDidChangeTransientModelProperty: Event<ITextModel>;
|
||||
readonly onDecorationTypeRegistered: Event<string>;
|
||||
|
||||
|
||||
addCodeEditor(editor: ICodeEditor): void;
|
||||
@@ -41,6 +42,7 @@ export interface ICodeEditorService {
|
||||
registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void;
|
||||
removeDecorationType(key: string): void;
|
||||
resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions;
|
||||
resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null;
|
||||
|
||||
setModelProperty(resource: URI, key: string, value: any): void;
|
||||
getModelProperty(resource: URI, key: string): any;
|
||||
|
||||
@@ -21,6 +21,10 @@ export class RefCountedStyleSheet {
|
||||
private readonly _styleSheet: HTMLStyleElement;
|
||||
private _refCount: number;
|
||||
|
||||
public get sheet() {
|
||||
return this._styleSheet.sheet as CSSStyleSheet;
|
||||
}
|
||||
|
||||
constructor(parent: CodeEditorServiceImpl, editorId: string, styleSheet: HTMLStyleElement) {
|
||||
this._parent = parent;
|
||||
this._editorId = editorId;
|
||||
@@ -53,6 +57,10 @@ export class RefCountedStyleSheet {
|
||||
export class GlobalStyleSheet {
|
||||
private readonly _styleSheet: HTMLStyleElement;
|
||||
|
||||
public get sheet() {
|
||||
return this._styleSheet.sheet as CSSStyleSheet;
|
||||
}
|
||||
|
||||
constructor(styleSheet: HTMLStyleElement) {
|
||||
this._styleSheet = styleSheet;
|
||||
}
|
||||
@@ -129,6 +137,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService {
|
||||
provider = new DecorationSubTypeOptionsProvider(this._themeService, styleSheet, providerArgs);
|
||||
}
|
||||
this._decorationOptionProviders.set(key, provider);
|
||||
this._onDecorationTypeRegistered.fire(key);
|
||||
}
|
||||
provider.refCount++;
|
||||
}
|
||||
@@ -153,6 +162,14 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService {
|
||||
return provider.getOptions(this, writable);
|
||||
}
|
||||
|
||||
public resolveDecorationCSSRules(decorationTypeKey: string) {
|
||||
const provider = this._decorationOptionProviders.get(decorationTypeKey);
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
return provider.resolveDecorationCSSRules();
|
||||
}
|
||||
|
||||
abstract getActiveCodeEditor(): ICodeEditor | null;
|
||||
abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
|
||||
}
|
||||
@@ -160,9 +177,10 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService {
|
||||
interface IModelDecorationOptionsProvider extends IDisposable {
|
||||
refCount: number;
|
||||
getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions;
|
||||
resolveDecorationCSSRules(): CSSRuleList;
|
||||
}
|
||||
|
||||
class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider {
|
||||
export class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider {
|
||||
|
||||
private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet;
|
||||
public refCount: number;
|
||||
@@ -192,6 +210,10 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide
|
||||
return options;
|
||||
}
|
||||
|
||||
public resolveDecorationCSSRules(): CSSRuleList {
|
||||
return this._styleSheet.sheet.cssRules;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._beforeContentRules) {
|
||||
this._beforeContentRules.dispose();
|
||||
@@ -213,7 +235,7 @@ interface ProviderArguments {
|
||||
}
|
||||
|
||||
|
||||
class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
|
||||
export class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet;
|
||||
@@ -295,6 +317,10 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider {
|
||||
};
|
||||
}
|
||||
|
||||
public resolveDecorationCSSRules(): CSSRuleList {
|
||||
return this._styleSheet.sheet.rules;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposables.dispose();
|
||||
this._styleSheet.unref();
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { EditorOpenContext } from 'vs/platform/editor/common/editor';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
|
||||
|
||||
class CommandOpener implements IOpener {
|
||||
@@ -74,7 +75,14 @@ class EditorOpener implements IOpener {
|
||||
}
|
||||
|
||||
await this._editorService.openCodeEditor(
|
||||
{ resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } },
|
||||
{
|
||||
resource: target,
|
||||
options: {
|
||||
selection,
|
||||
context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API,
|
||||
...options?.editorOptions
|
||||
}
|
||||
},
|
||||
this._editorService.getFocusedCodeEditor(),
|
||||
options?.openToSide
|
||||
);
|
||||
@@ -90,6 +98,7 @@ export class OpenerService implements IOpenerService {
|
||||
private readonly _openers = new LinkedList<IOpener>();
|
||||
private readonly _validators = new LinkedList<IValidator>();
|
||||
private readonly _resolvers = new LinkedList<IExternalUriResolver>();
|
||||
private readonly _resolvedUriTargets = new ResourceMap<URI>(uri => uri.with({ path: null, fragment: null, query: null }).toString());
|
||||
|
||||
private _externalOpener: IExternalOpener;
|
||||
|
||||
@@ -148,16 +157,18 @@ export class OpenerService implements IOpenerService {
|
||||
}
|
||||
|
||||
async open(target: URI | string, options?: OpenOptions): Promise<boolean> {
|
||||
|
||||
// check with contributed validators
|
||||
for (const validator of this._validators.toArray()) {
|
||||
if (!(await validator.shouldOpen(target))) {
|
||||
const targetURI = typeof target === 'string' ? URI.parse(target) : target;
|
||||
// validate against the original URI that this URI resolves to, if one exists
|
||||
const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target;
|
||||
for (const validator of this._validators) {
|
||||
if (!(await validator.shouldOpen(validationTarget))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check with contributed openers
|
||||
for (const opener of this._openers.toArray()) {
|
||||
for (const opener of this._openers) {
|
||||
const handled = await opener.open(target, options);
|
||||
if (handled) {
|
||||
return true;
|
||||
@@ -168,9 +179,10 @@ export class OpenerService implements IOpenerService {
|
||||
}
|
||||
|
||||
async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri> {
|
||||
for (const resolver of this._resolvers.toArray()) {
|
||||
for (const resolver of this._resolvers) {
|
||||
const result = await resolver.resolveExternalUri(resource, options);
|
||||
if (result) {
|
||||
this._resolvedUriTargets.set(result.resolved, resource);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -180,7 +192,7 @@ export class OpenerService implements IOpenerService {
|
||||
|
||||
private async _doOpenExternal(resource: URI | string, options: OpenOptions | undefined): Promise<boolean> {
|
||||
|
||||
//todo@joh IExternalUriResolver should support `uri: URI | string`
|
||||
//todo@jrieken IExternalUriResolver should support `uri: URI | string`
|
||||
const uri = typeof resource === 'string' ? URI.parse(resource) : resource;
|
||||
const { resolved } = await this.resolveExternalUri(uri, options);
|
||||
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Configuration } from 'vs/editor/browser/config/configuration';
|
||||
import { ILineBreaksComputer, LineBreakData } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
const ttPolicy = window.trustedTypes?.createPolicy('domLineBreaksComputer', { createHTML: value => value });
|
||||
|
||||
export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory {
|
||||
|
||||
@@ -107,7 +110,9 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe
|
||||
allCharOffsets[i] = tmp[0];
|
||||
allVisibleColumns[i] = tmp[1];
|
||||
}
|
||||
containerDomNode.innerHTML = sb.build();
|
||||
const html = sb.build();
|
||||
const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
|
||||
containerDomNode.innerHTML = trustedhtml as unknown as string;
|
||||
|
||||
containerDomNode.style.position = 'absolute';
|
||||
containerDomNode.style.top = '10000';
|
||||
|
||||
@@ -322,6 +322,10 @@ export class ViewController {
|
||||
this.userInputEvents.emitMouseDrop(e);
|
||||
}
|
||||
|
||||
public emitMouseDropCanceled(): void {
|
||||
this.userInputEvents.emitMouseDropCanceled();
|
||||
}
|
||||
|
||||
public emitMouseWheel(e: IMouseWheelEvent): void {
|
||||
this.userInputEvents.emitMouseWheel(e);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export class ViewUserInputEvents {
|
||||
public onMouseUp: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseDrag: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseDrop: EventCallback<IPartialEditorMouseEvent> | null = null;
|
||||
public onMouseDropCanceled: EventCallback<void> | null = null;
|
||||
public onMouseWheel: EventCallback<IMouseWheelEvent> | null = null;
|
||||
|
||||
private readonly _coordinatesConverter: ICoordinatesConverter;
|
||||
@@ -88,6 +89,12 @@ export class ViewUserInputEvents {
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseDropCanceled(): void {
|
||||
if (this.onMouseDropCanceled) {
|
||||
this.onMouseDropCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseWheel(e: IMouseWheelEvent): void {
|
||||
if (this.onMouseWheel) {
|
||||
this.onMouseWheel(e);
|
||||
|
||||
@@ -23,7 +23,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
protected _contentLeft: number;
|
||||
protected _contentWidth: number;
|
||||
protected _selectionIsEmpty: boolean;
|
||||
protected _renderLineHightlightOnlyWhenFocus: boolean;
|
||||
protected _renderLineHighlightOnlyWhenFocus: boolean;
|
||||
protected _focused: boolean;
|
||||
private _cursorLineNumbers: number[];
|
||||
private _selections: Selection[];
|
||||
@@ -37,7 +37,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
this._lineHeight = options.get(EditorOption.lineHeight);
|
||||
this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
|
||||
this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._contentLeft = layoutInfo.contentLeft;
|
||||
this._contentWidth = layoutInfo.contentWidth;
|
||||
this._selectionIsEmpty = true;
|
||||
@@ -85,7 +85,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
this._lineHeight = options.get(EditorOption.lineHeight);
|
||||
this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
|
||||
this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._renderLineHighlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._contentLeft = layoutInfo.contentLeft;
|
||||
this._contentWidth = layoutInfo.contentWidth;
|
||||
return true;
|
||||
@@ -110,7 +110,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
return true;
|
||||
}
|
||||
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
|
||||
if (!this._renderLineHightlightOnlyWhenFocus) {
|
||||
if (!this._renderLineHighlightOnlyWhenFocus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -170,33 +170,36 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay {
|
||||
return (
|
||||
(this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all')
|
||||
&& this._selectionIsEmpty
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
protected _shouldRenderOther(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all')
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOverlay {
|
||||
protected _renderOne(ctx: RenderingContext): string {
|
||||
const className = 'current-line current-line-margin' + (this._shouldRenderOther() ? ' current-line-margin-both' : '');
|
||||
const className = 'current-line' + (this._shouldRenderMargin() ? ' current-line-margin' : '') + (this._shouldRenderOther() ? ' current-line-margin-both' : '');
|
||||
return `<div class="${className}" style="width:${this._contentLeft}px; height:${this._lineHeight}px;"></div>`;
|
||||
}
|
||||
protected _shouldRenderThis(): boolean {
|
||||
protected _shouldRenderMargin(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all')
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
protected _shouldRenderThis(): boolean {
|
||||
return true;
|
||||
}
|
||||
protected _shouldRenderOther(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all')
|
||||
&& this._selectionIsEmpty
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
&& (!this._renderLineHighlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export class EditorScrollbar extends ViewPart {
|
||||
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity,
|
||||
fastScrollSensitivity: fastScrollSensitivity,
|
||||
scrollPredominantAxis: scrollPredominantAxis,
|
||||
scrollByPage: scrollbar.scrollByPage,
|
||||
};
|
||||
|
||||
this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable()));
|
||||
|
||||
@@ -226,8 +226,7 @@ class MinimapLayout {
|
||||
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
|
||||
*/
|
||||
public getDesiredScrollTopFromDelta(delta: number): number {
|
||||
const desiredSliderPosition = this.sliderTop + delta;
|
||||
return Math.round(desiredSliderPosition / this._computedSliderRatio);
|
||||
return Math.round(this.scrollTop + delta / this._computedSliderRatio);
|
||||
}
|
||||
|
||||
public getDesiredScrollTopFromTouchLocation(pageY: number): number {
|
||||
@@ -238,6 +237,7 @@ class MinimapLayout {
|
||||
options: MinimapOptions,
|
||||
viewportStartLineNumber: number,
|
||||
viewportEndLineNumber: number,
|
||||
viewportStartLineNumberVerticalOffset: number,
|
||||
viewportHeight: number,
|
||||
viewportContainsWhitespaceGaps: boolean,
|
||||
lineCount: number,
|
||||
@@ -332,8 +332,10 @@ class MinimapLayout {
|
||||
}
|
||||
|
||||
const endLineNumber = Math.min(lineCount, startLineNumber + minimapLinesFitting - 1);
|
||||
const partialLine = (scrollTop - viewportStartLineNumberVerticalOffset) / lineHeight;
|
||||
const sliderTopAligned = (viewportStartLineNumber - startLineNumber + partialLine) * minimapLineHeight / pixelRatio;
|
||||
|
||||
return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTop, sliderHeight, startLineNumber, endLineNumber);
|
||||
return new MinimapLayout(scrollTop, scrollHeight, true, computedSliderRatio, sliderTopAligned, sliderHeight, startLineNumber, endLineNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,6 +507,7 @@ interface IMinimapRenderingContext {
|
||||
|
||||
readonly viewportStartLineNumber: number;
|
||||
readonly viewportEndLineNumber: number;
|
||||
readonly viewportStartLineNumberVerticalOffset: number;
|
||||
|
||||
readonly scrollTop: number;
|
||||
readonly scrollLeft: number;
|
||||
@@ -891,6 +894,7 @@ export class Minimap extends ViewPart implements IMinimapModel {
|
||||
|
||||
viewportStartLineNumber: viewportStartLineNumber,
|
||||
viewportEndLineNumber: viewportEndLineNumber,
|
||||
viewportStartLineNumberVerticalOffset: ctx.getVerticalOffsetForLineNumber(viewportStartLineNumber),
|
||||
|
||||
scrollTop: ctx.scrollTop,
|
||||
scrollLeft: ctx.scrollLeft,
|
||||
@@ -1344,6 +1348,7 @@ class InnerMinimap extends Disposable {
|
||||
this._model.options,
|
||||
renderingCtx.viewportStartLineNumber,
|
||||
renderingCtx.viewportEndLineNumber,
|
||||
renderingCtx.viewportStartLineNumberVerticalOffset,
|
||||
renderingCtx.viewportHeight,
|
||||
renderingCtx.viewportContainsWhitespaceGaps,
|
||||
this._model.getLineCount(),
|
||||
|
||||
@@ -25,6 +25,7 @@ export class ViewCursors extends ViewPart {
|
||||
private _cursorStyle: TextEditorCursorStyle;
|
||||
private _cursorSmoothCaretAnimation: boolean;
|
||||
private _selectionIsEmpty: boolean;
|
||||
private _isComposingInput: boolean;
|
||||
|
||||
private _isVisible: boolean;
|
||||
|
||||
@@ -49,6 +50,7 @@ export class ViewCursors extends ViewPart {
|
||||
this._cursorStyle = options.get(EditorOption.cursorStyle);
|
||||
this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation);
|
||||
this._selectionIsEmpty = true;
|
||||
this._isComposingInput = false;
|
||||
|
||||
this._isVisible = false;
|
||||
|
||||
@@ -83,7 +85,16 @@ export class ViewCursors extends ViewPart {
|
||||
}
|
||||
|
||||
// --- begin event handlers
|
||||
|
||||
public onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean {
|
||||
this._isComposingInput = true;
|
||||
this._updateBlinking();
|
||||
return true;
|
||||
}
|
||||
public onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean {
|
||||
this._isComposingInput = false;
|
||||
this._updateBlinking();
|
||||
return true;
|
||||
}
|
||||
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
|
||||
const options = this._context.configuration.options;
|
||||
|
||||
@@ -195,6 +206,10 @@ export class ViewCursors extends ViewPart {
|
||||
// ---- blinking logic
|
||||
|
||||
private _getCursorBlinking(): TextEditorCursorBlinkingStyle {
|
||||
if (this._isComposingInput) {
|
||||
// avoid double cursors
|
||||
return TextEditorCursorBlinkingStyle.Hidden;
|
||||
}
|
||||
if (!this._editorHasFocus) {
|
||||
return TextEditorCursorBlinkingStyle.Hidden;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground, editorErrorBackground, editorInfoBackground, editorWarningBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
|
||||
@@ -176,6 +176,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
private readonly _onMouseDrop: Emitter<editorBrowser.IPartialEditorMouseEvent> = this._register(new Emitter<editorBrowser.IPartialEditorMouseEvent>());
|
||||
public readonly onMouseDrop: Event<editorBrowser.IPartialEditorMouseEvent> = this._onMouseDrop.event;
|
||||
|
||||
private readonly _onMouseDropCanceled: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onMouseDropCanceled: Event<void> = this._onMouseDropCanceled.event;
|
||||
|
||||
private readonly _onContextMenu: Emitter<editorBrowser.IEditorMouseEvent> = this._register(new Emitter<editorBrowser.IEditorMouseEvent>());
|
||||
public readonly onContextMenu: Event<editorBrowser.IEditorMouseEvent> = this._onContextMenu.event;
|
||||
|
||||
@@ -1024,6 +1027,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
if (this._triggerEditorCommand(source, handlerId, payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._commandService.executeCommand(handlerId, payload);
|
||||
}
|
||||
|
||||
private _startComposition(): void {
|
||||
@@ -1117,6 +1122,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
return true;
|
||||
}
|
||||
|
||||
public popUndoStop(): boolean {
|
||||
if (!this._modelData) {
|
||||
return false;
|
||||
}
|
||||
if (this._configuration.options.get(EditorOption.readOnly)) {
|
||||
// read only editor => sorry!
|
||||
return false;
|
||||
}
|
||||
this._modelData.model.popStackElement();
|
||||
return true;
|
||||
}
|
||||
|
||||
public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean {
|
||||
if (!this._modelData) {
|
||||
return false;
|
||||
@@ -1614,6 +1631,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
viewUserInputEvents.onMouseUp = (e) => this._onMouseUp.fire(e);
|
||||
viewUserInputEvents.onMouseDrag = (e) => this._onMouseDrag.fire(e);
|
||||
viewUserInputEvents.onMouseDrop = (e) => this._onMouseDrop.fire(e);
|
||||
viewUserInputEvents.onMouseDropCanceled = (e) => this._onMouseDropCanceled.fire(e);
|
||||
viewUserInputEvents.onMouseWheel = (e) => this._onMouseWheel.fire(e);
|
||||
|
||||
const view = new View(
|
||||
@@ -1716,6 +1734,7 @@ class EditorContextKeysManager extends Disposable {
|
||||
private readonly _editorTextFocus: IContextKey<boolean>;
|
||||
private readonly _editorTabMovesFocus: IContextKey<boolean>;
|
||||
private readonly _editorReadonly: IContextKey<boolean>;
|
||||
private readonly _inDiffEditor: IContextKey<boolean>;
|
||||
private readonly _editorColumnSelection: IContextKey<boolean>;
|
||||
private readonly _hasMultipleSelections: IContextKey<boolean>;
|
||||
private readonly _hasNonEmptySelection: IContextKey<boolean>;
|
||||
@@ -1738,6 +1757,7 @@ class EditorContextKeysManager extends Disposable {
|
||||
this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService);
|
||||
this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService);
|
||||
this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService);
|
||||
this._inDiffEditor = EditorContextKeys.inDiffEditor.bindTo(contextKeyService);
|
||||
this._editorColumnSelection = EditorContextKeys.columnSelection.bindTo(contextKeyService);
|
||||
this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService);
|
||||
this._hasNonEmptySelection = EditorContextKeys.hasNonEmptySelection.bindTo(contextKeyService);
|
||||
@@ -1766,6 +1786,7 @@ class EditorContextKeysManager extends Disposable {
|
||||
|
||||
this._editorTabMovesFocus.set(options.get(EditorOption.tabFocusMode));
|
||||
this._editorReadonly.set(options.get(EditorOption.readOnly));
|
||||
this._inDiffEditor.set(options.get(EditorOption.inDiffEditor));
|
||||
this._editorColumnSelection.set(options.get(EditorOption.columnSelection));
|
||||
}
|
||||
|
||||
@@ -1981,6 +2002,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (errorForeground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorErrorDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(errorForeground)}") repeat-x bottom left; }`);
|
||||
}
|
||||
const errorBackground = theme.getColor(editorErrorBackground);
|
||||
if (errorBackground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorErrorDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${errorBackground}; }`);
|
||||
}
|
||||
|
||||
const warningBorderColor = theme.getColor(editorWarningBorder);
|
||||
if (warningBorderColor) {
|
||||
@@ -1990,6 +2015,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (warningForeground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorWarningDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(warningForeground)}") repeat-x bottom left; }`);
|
||||
}
|
||||
const warningBackground = theme.getColor(editorWarningBackground);
|
||||
if (warningBackground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorWarningDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${warningBackground}; }`);
|
||||
}
|
||||
|
||||
const infoBorderColor = theme.getColor(editorInfoBorder);
|
||||
if (infoBorderColor) {
|
||||
@@ -1999,6 +2028,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
if (infoForeground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorInfoDecoration} { background: url("data:image/svg+xml,${getSquigglySVGData(infoForeground)}") repeat-x bottom left; }`);
|
||||
}
|
||||
const infoBackground = theme.getColor(editorInfoBackground);
|
||||
if (infoBackground) {
|
||||
collector.addRule(`.monaco-editor .${ClassName.EditorInfoDecoration}::before { display: block; content: ''; width: 100%; height: 100%; background: ${infoBackground}; }`);
|
||||
}
|
||||
|
||||
const hintBorderColor = theme.getColor(editorHintBorder);
|
||||
if (hintBorderColor) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,9 +29,10 @@ import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { registerIcon, Codicon } from 'vs/base/common/codicons';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
const DIFF_LINES_PADDING = 3;
|
||||
|
||||
@@ -73,9 +74,9 @@ class Diff {
|
||||
}
|
||||
}
|
||||
|
||||
const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add);
|
||||
const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove);
|
||||
const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close);
|
||||
const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add, nls.localize('diffReviewInsertIcon', 'Icon for \'Insert\' in diff review.'));
|
||||
const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, nls.localize('diffReviewRemoveIcon', 'Icon for \'Remove\' in diff review.'));
|
||||
const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close, nls.localize('diffReviewCloseIcon', 'Icon for \'Close\' in diff review.'));
|
||||
|
||||
export class DiffReview extends Disposable {
|
||||
|
||||
@@ -104,7 +105,7 @@ export class DiffReview extends Disposable {
|
||||
this.actionBarContainer.domNode
|
||||
));
|
||||
|
||||
this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + diffReviewCloseIcon.classNames, true, () => {
|
||||
this._actionBar.push(new Action('diffreview.close', nls.localize('label.close', "Close"), 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), true, () => {
|
||||
this.hide();
|
||||
return Promise.resolve(null);
|
||||
}), { label: false, icon: true });
|
||||
@@ -647,7 +648,7 @@ export class DiffReview extends Disposable {
|
||||
let rowClassName: string = 'diff-review-row';
|
||||
let lineNumbersExtraClassName: string = '';
|
||||
const spacerClassName: string = 'diff-review-spacer';
|
||||
let spacerIcon: Codicon | null = null;
|
||||
let spacerIcon: ThemeIcon | null = null;
|
||||
switch (type) {
|
||||
case DiffEntryType.Insert:
|
||||
rowClassName = 'diff-review-row line-insert';
|
||||
@@ -723,7 +724,7 @@ export class DiffReview extends Disposable {
|
||||
|
||||
if (spacerIcon) {
|
||||
const spacerCodicon = document.createElement('span');
|
||||
spacerCodicon.className = spacerIcon.classNames;
|
||||
spacerCodicon.className = ThemeIcon.asClassName(spacerIcon);
|
||||
spacerCodicon.innerText = '\u00a0\u00a0';
|
||||
spacer.appendChild(spacerCodicon);
|
||||
} else {
|
||||
|
||||
@@ -14,13 +14,15 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export interface IDiffLinesChange {
|
||||
readonly originalStartLineNumber: number;
|
||||
readonly originalEndLineNumber: number;
|
||||
readonly modifiedStartLineNumber: number;
|
||||
readonly modifiedEndLineNumber: number;
|
||||
readonly originalContent: string[];
|
||||
readonly originalModel: ITextModel;
|
||||
viewLineCounts: number[] | null;
|
||||
}
|
||||
|
||||
export class InlineDiffMargin extends Disposable {
|
||||
@@ -45,12 +47,12 @@ export class InlineDiffMargin extends Disposable {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _viewZoneId: string,
|
||||
private _marginDomNode: HTMLElement,
|
||||
public editor: CodeEditorWidget,
|
||||
public diff: IDiffLinesChange,
|
||||
private _contextMenuService: IContextMenuService,
|
||||
private _clipboardService: IClipboardService
|
||||
private readonly _viewZoneId: string,
|
||||
private readonly _marginDomNode: HTMLElement,
|
||||
public readonly editor: CodeEditorWidget,
|
||||
public readonly diff: IDiffLinesChange,
|
||||
private readonly _contextMenuService: IContextMenuService,
|
||||
private readonly _clipboardService: IClipboardService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -79,7 +81,9 @@ export class InlineDiffMargin extends Disposable {
|
||||
undefined,
|
||||
true,
|
||||
async () => {
|
||||
await this._clipboardService.writeText(diff.originalContent.join(lineFeed) + lineFeed);
|
||||
const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber + 1, 1);
|
||||
const deletedText = diff.originalModel.getValueInRange(range);
|
||||
await this._clipboardService.writeText(deletedText);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -92,7 +96,8 @@ export class InlineDiffMargin extends Disposable {
|
||||
undefined,
|
||||
true,
|
||||
async () => {
|
||||
await this._clipboardService.writeText(diff.originalContent[currentLineNumberOffset]);
|
||||
const lineContent = diff.originalModel.getLineContent(diff.originalStartLineNumber + currentLineNumberOffset);
|
||||
await this._clipboardService.writeText(lineContent);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -102,13 +107,15 @@ export class InlineDiffMargin extends Disposable {
|
||||
const readOnly = editor.getOption(EditorOption.readOnly);
|
||||
if (!readOnly) {
|
||||
actions.push(new Action('diff.inline.revertChange', nls.localize('diff.inline.revertChange.label', "Revert this change"), undefined, true, async () => {
|
||||
const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber, diff.originalModel.getLineMaxColumn(diff.originalEndLineNumber));
|
||||
const deletedText = diff.originalModel.getValueInRange(range);
|
||||
if (diff.modifiedEndLineNumber === 0) {
|
||||
// deletion only
|
||||
const column = editor.getModel()!.getLineMaxColumn(diff.modifiedStartLineNumber);
|
||||
editor.executeEdits('diffEditor', [
|
||||
{
|
||||
range: new Range(diff.modifiedStartLineNumber, column, diff.modifiedStartLineNumber, column),
|
||||
text: lineFeed + diff.originalContent.join(lineFeed)
|
||||
text: lineFeed + deletedText
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
@@ -116,7 +123,7 @@ export class InlineDiffMargin extends Disposable {
|
||||
editor.executeEdits('diffEditor', [
|
||||
{
|
||||
range: new Range(diff.modifiedStartLineNumber, 1, diff.modifiedEndLineNumber, column),
|
||||
text: diff.originalContent.join(lineFeed)
|
||||
text: deletedText
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -189,6 +196,15 @@ export class InlineDiffMargin extends Disposable {
|
||||
const lineNumberOffset = Math.floor(offset / lineHeight);
|
||||
const newTop = lineNumberOffset * lineHeight;
|
||||
this._diffActions.style.top = `${newTop}px`;
|
||||
if (this.diff.viewLineCounts) {
|
||||
let acc = 0;
|
||||
for (let i = 0; i < this.diff.viewLineCounts.length; i++) {
|
||||
acc += this.diff.viewLineCounts[i];
|
||||
if (lineNumberOffset < acc) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return lineNumberOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,6 +502,16 @@ const editorConfiguration: IConfigurationNode = {
|
||||
default: true,
|
||||
description: nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document.")
|
||||
},
|
||||
'editor.wordBasedSuggestionsMode': {
|
||||
enum: ['currentDocument', 'matchingDocuments', 'allDocuments'],
|
||||
default: 'matchingDocuments',
|
||||
enumDescriptions: [
|
||||
nls.localize('wordBasedSuggestionsMode.currentDocument', 'Only suggest words from the active document.'),
|
||||
nls.localize('wordBasedSuggestionsMode.matchingDocuments', 'Suggest words from all open documents of the same language.'),
|
||||
nls.localize('wordBasedSuggestionsMode.allDocuments', 'Suggest words from all open documents.')
|
||||
],
|
||||
description: nls.localize('wordBasedSuggestionsMode', "Controls form what documents word based completions are computed.")
|
||||
},
|
||||
'editor.semanticHighlighting.enabled': {
|
||||
enum: [true, false, 'configuredByTheme'],
|
||||
enumDescriptions: [
|
||||
@@ -546,6 +556,16 @@ const editorConfiguration: IConfigurationNode = {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.")
|
||||
},
|
||||
'diffEditor.wordWrap': {
|
||||
type: 'string',
|
||||
enum: ['off', 'on', 'inherit'],
|
||||
default: 'inherit',
|
||||
markdownEnumDescriptions: [
|
||||
nls.localize('wordWrap.off', "Lines will never wrap."),
|
||||
nls.localize('wordWrap.on', "Lines will wrap at the viewport width."),
|
||||
nls.localize('wordWrap.inherit', "Lines will wrap according to the `#editor.wordWrap#` setting."),
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,9 +144,13 @@ export interface IEditorOptions {
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* Rename matching regions on type.
|
||||
* Enable linked editing.
|
||||
* Defaults to false.
|
||||
*/
|
||||
linkedEditing?: boolean;
|
||||
/**
|
||||
* deprecated, use linkedEditing instead
|
||||
*/
|
||||
renameOnType?: boolean;
|
||||
/**
|
||||
* Should the editor render validation decorations.
|
||||
@@ -260,6 +264,14 @@ export interface IEditorOptions {
|
||||
* Defaults to "off".
|
||||
*/
|
||||
wordWrap?: 'off' | 'on' | 'wordWrapColumn' | 'bounded';
|
||||
/**
|
||||
* Override the `wordWrap` setting.
|
||||
*/
|
||||
wordWrapOverride1?: 'off' | 'on' | 'inherit';
|
||||
/**
|
||||
* Override the `wordWrapOverride1` setting.
|
||||
*/
|
||||
wordWrapOverride2?: 'off' | 'on' | 'inherit';
|
||||
/**
|
||||
* Control the wrapping of the editor.
|
||||
* When `wordWrap` = "off", the lines will never wrap.
|
||||
@@ -269,11 +281,6 @@ export interface IEditorOptions {
|
||||
* Defaults to 80.
|
||||
*/
|
||||
wordWrapColumn?: number;
|
||||
/**
|
||||
* Force word wrapping when the text appears to be of a minified/generated file.
|
||||
* Defaults to true.
|
||||
*/
|
||||
wordWrapMinified?: boolean;
|
||||
/**
|
||||
* Control indentation of wrapped lines. Can be: 'none', 'same', 'indent' or 'deepIndent'.
|
||||
* Defaults to 'same' in vscode and to 'none' in monaco-editor.
|
||||
@@ -370,6 +377,10 @@ export interface IEditorOptions {
|
||||
* Suggest options.
|
||||
*/
|
||||
suggest?: ISuggestOptions;
|
||||
/**
|
||||
* Smart select opptions;
|
||||
*/
|
||||
smartSelect?: ISmartSelectOptions;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -416,6 +427,11 @@ export interface IEditorOptions {
|
||||
* Defaults to advanced.
|
||||
*/
|
||||
autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full';
|
||||
/**
|
||||
* Emulate selection behaviour of tab characters when using spaces for indentation.
|
||||
* This means selection will stick to tab stops.
|
||||
*/
|
||||
stickyTabStops?: boolean;
|
||||
/**
|
||||
* Enable format on type.
|
||||
* Defaults to false.
|
||||
@@ -491,6 +507,14 @@ export interface IEditorOptions {
|
||||
* Defaults to true.
|
||||
*/
|
||||
codeLens?: boolean;
|
||||
/**
|
||||
* Code lens font family. Defaults to editor font family.
|
||||
*/
|
||||
codeLensFontFamily?: string;
|
||||
/**
|
||||
* Code lens font size. Default to 90% of the editor font size
|
||||
*/
|
||||
codeLensFontSize?: number;
|
||||
/**
|
||||
* Control the behavior and rendering of the code action lightbulb.
|
||||
*/
|
||||
@@ -644,15 +668,19 @@ export interface IDiffEditorOptions extends IEditorOptions {
|
||||
*/
|
||||
originalEditable?: boolean;
|
||||
/**
|
||||
* Original editor should be have code lens enabled?
|
||||
* Should the diff editor enable code lens?
|
||||
* Defaults to false.
|
||||
*/
|
||||
originalCodeLens?: boolean;
|
||||
diffCodeLens?: boolean;
|
||||
/**
|
||||
* Modified editor should be have code lens enabled?
|
||||
* Defaults to false.
|
||||
* Is the diff editor inside another editor
|
||||
* Defaults to false
|
||||
*/
|
||||
modifiedCodeLens?: boolean;
|
||||
isInEmbeddedEditor?: boolean;
|
||||
/**
|
||||
* Control the wrapping of the diff editor.
|
||||
*/
|
||||
diffWordWrap?: 'off' | 'on' | 'inherit';
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -828,18 +856,21 @@ class SimpleEditorOption<K1 extends EditorOption, V> implements IEditorOption<K1
|
||||
}
|
||||
}
|
||||
|
||||
class EditorBooleanOption<K1 extends EditorOption> extends SimpleEditorOption<K1, boolean> {
|
||||
|
||||
public static boolean(value: any, defaultValue: boolean): boolean {
|
||||
if (typeof value === 'undefined') {
|
||||
return defaultValue;
|
||||
}
|
||||
if (value === 'false') {
|
||||
// treat the string 'false' as false
|
||||
return false;
|
||||
}
|
||||
return Boolean(value);
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function boolean(value: any, defaultValue: boolean): boolean {
|
||||
if (typeof value === 'undefined') {
|
||||
return defaultValue;
|
||||
}
|
||||
if (value === 'false') {
|
||||
// treat the string 'false' as false
|
||||
return false;
|
||||
}
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
class EditorBooleanOption<K1 extends EditorOption> extends SimpleEditorOption<K1, boolean> {
|
||||
|
||||
constructor(id: K1, name: PossibleKeyName<boolean>, defaultValue: boolean, schema: IConfigurationPropertySchema | undefined = undefined) {
|
||||
if (typeof schema !== 'undefined') {
|
||||
@@ -850,7 +881,7 @@ class EditorBooleanOption<K1 extends EditorOption> extends SimpleEditorOption<K1
|
||||
}
|
||||
|
||||
public validate(input: any): boolean {
|
||||
return EditorBooleanOption.boolean(input, this.defaultValue);
|
||||
return boolean(input, this.defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,17 +981,20 @@ class EditorStringOption<K1 extends EditorOption> extends SimpleEditorOption<K1,
|
||||
}
|
||||
}
|
||||
|
||||
class EditorStringEnumOption<K1 extends EditorOption, V extends string> extends SimpleEditorOption<K1, V> {
|
||||
|
||||
public static stringSet<T>(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray<T>): T {
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
if (allowedValues.indexOf(value) === -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function stringSet<T>(value: T | undefined, defaultValue: T, allowedValues: ReadonlyArray<T>): T {
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
if (allowedValues.indexOf(value) === -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
class EditorStringEnumOption<K1 extends EditorOption, V extends string> extends SimpleEditorOption<K1, V> {
|
||||
|
||||
private readonly _allowedValues: ReadonlyArray<V>;
|
||||
|
||||
@@ -975,7 +1009,7 @@ class EditorStringEnumOption<K1 extends EditorOption, V extends string> extends
|
||||
}
|
||||
|
||||
public validate(input: any): V {
|
||||
return EditorStringEnumOption.stringSet<V>(input, this.defaultValue, this._allowedValues);
|
||||
return stringSet<V>(input, this.defaultValue, this._allowedValues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,8 +1147,8 @@ class EditorComments extends BaseEditorOption<EditorOption.comments, EditorComme
|
||||
}
|
||||
const input = _input as IEditorCommentsOptions;
|
||||
return {
|
||||
insertSpace: EditorBooleanOption.boolean(input.insertSpace, this.defaultValue.insertSpace),
|
||||
ignoreEmptyLines: EditorBooleanOption.boolean(input.ignoreEmptyLines, this.defaultValue.ignoreEmptyLines),
|
||||
insertSpace: boolean(input.insertSpace, this.defaultValue.insertSpace),
|
||||
ignoreEmptyLines: boolean(input.ignoreEmptyLines, this.defaultValue.ignoreEmptyLines),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1375,14 +1409,14 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
}
|
||||
const input = _input as IEditorFindOptions;
|
||||
return {
|
||||
cursorMoveOnType: EditorBooleanOption.boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType),
|
||||
seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection),
|
||||
cursorMoveOnType: boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType),
|
||||
seedSearchStringFromSelection: boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection),
|
||||
autoFindInSelection: typeof _input.autoFindInSelection === 'boolean'
|
||||
? (_input.autoFindInSelection ? 'always' : 'never')
|
||||
: EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']),
|
||||
globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard),
|
||||
addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop),
|
||||
loop: EditorBooleanOption.boolean(input.loop, this.defaultValue.loop),
|
||||
: stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']),
|
||||
globalFindClipboard: boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard),
|
||||
addExtraSpaceOnTop: boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop),
|
||||
loop: boolean(input.loop, this.defaultValue.loop),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1406,14 +1440,14 @@ export class EditorFontLigatures extends BaseEditorOption<EditorOption.fontLigat
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
description: nls.localize('fontLigatures', "Enables/Disables font ligatures."),
|
||||
description: nls.localize('fontLigatures', "Enables/Disables font ligatures ('calt' and 'liga' font features). Change this to a string for fine-grained control of the 'font-feature-settings' CSS property."),
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
description: nls.localize('fontFeatureSettings', "Explicit font-feature-settings.")
|
||||
description: nls.localize('fontFeatureSettings', "Explicit 'font-feature-settings' CSS property. A boolean can be passed instead if one only needs to turn on/off ligatures.")
|
||||
}
|
||||
],
|
||||
description: nls.localize('fontLigaturesGeneral', "Configures font ligatures or font features."),
|
||||
description: nls.localize('fontLigaturesGeneral', "Configures font ligatures or font features. Can be either a boolean to enable/disable ligatures or a string for the value of the CSS 'font-feature-settings' property."),
|
||||
default: false
|
||||
}
|
||||
);
|
||||
@@ -1644,12 +1678,12 @@ class EditorGoToLocation extends BaseEditorOption<EditorOption.gotoLocation, GoT
|
||||
}
|
||||
const input = _input as IGotoLocationOptions;
|
||||
return {
|
||||
multiple: EditorStringEnumOption.stringSet<GoToLocationValues>(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDefinitions: input.multipleDefinitions ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleTypeDefinitions: input.multipleTypeDefinitions ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDeclarations: input.multipleDeclarations ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleImplementations: input.multipleImplementations ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleReferences: input.multipleReferences ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multiple: stringSet<GoToLocationValues>(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDefinitions: input.multipleDefinitions ?? stringSet<GoToLocationValues>(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet<GoToLocationValues>(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDeclarations: input.multipleDeclarations ?? stringSet<GoToLocationValues>(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleImplementations: input.multipleImplementations ?? stringSet<GoToLocationValues>(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleReferences: input.multipleReferences ?? stringSet<GoToLocationValues>(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand),
|
||||
alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand),
|
||||
alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand),
|
||||
@@ -1722,9 +1756,9 @@ class EditorHover extends BaseEditorOption<EditorOption.hover, EditorHoverOption
|
||||
}
|
||||
const input = _input as IEditorHoverOptions;
|
||||
return {
|
||||
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled),
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled),
|
||||
delay: EditorIntOption.clampedInt(input.delay, this.defaultValue.delay, 0, 10000),
|
||||
sticky: EditorBooleanOption.boolean(input.sticky, this.defaultValue.sticky)
|
||||
sticky: boolean(input.sticky, this.defaultValue.sticky)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1928,7 +1962,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
EditorOption.glyphMargin, EditorOption.lineDecorationsWidth, EditorOption.folding,
|
||||
EditorOption.minimap, EditorOption.scrollbar, EditorOption.lineNumbers,
|
||||
EditorOption.lineNumbersMinChars, EditorOption.scrollBeyondLastLine,
|
||||
EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapMinified,
|
||||
EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapOverride1, EditorOption.wordWrapOverride2,
|
||||
EditorOption.accessibilitySupport
|
||||
]
|
||||
);
|
||||
@@ -2139,9 +2173,11 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
const pixelRatio = env.pixelRatio;
|
||||
const viewLineCount = env.viewLineCount;
|
||||
|
||||
const wordWrap = options.get(EditorOption.wordWrap);
|
||||
const wordWrapOverride2 = options.get(EditorOption.wordWrapOverride2);
|
||||
const wordWrapOverride1 = (wordWrapOverride2 === 'inherit' ? options.get(EditorOption.wordWrapOverride1) : wordWrapOverride2);
|
||||
const wordWrap = (wordWrapOverride1 === 'inherit' ? options.get(EditorOption.wordWrap) : wordWrapOverride1);
|
||||
|
||||
const wordWrapColumn = options.get(EditorOption.wordWrapColumn);
|
||||
const wordWrapMinified = options.get(EditorOption.wordWrapMinified);
|
||||
const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
|
||||
const isDominatedByLongLines = env.isDominatedByLongLines;
|
||||
|
||||
@@ -2198,7 +2234,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
// Never enable wrapping when a screen reader is attached
|
||||
// because arrow down etc. will not move the cursor in the way
|
||||
// a screen reader expects.
|
||||
if (wordWrapMinified && isDominatedByLongLines) {
|
||||
if (wordWrapOverride1 === 'inherit' && isDominatedByLongLines) {
|
||||
// Force viewport width wrapping if model is dominated by long lines
|
||||
isWordWrapMinified = true;
|
||||
isViewportWrapping = true;
|
||||
@@ -2321,7 +2357,7 @@ class EditorLightbulb extends BaseEditorOption<EditorOption.lightbulb, EditorLig
|
||||
}
|
||||
const input = _input as IEditorLightbulbOptions;
|
||||
return {
|
||||
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled)
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2465,11 +2501,11 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
|
||||
}
|
||||
const input = _input as IEditorMinimapOptions;
|
||||
return {
|
||||
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled),
|
||||
size: EditorStringEnumOption.stringSet<'proportional' | 'fill' | 'fit'>(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']),
|
||||
side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']),
|
||||
showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']),
|
||||
renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters),
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled),
|
||||
size: stringSet<'proportional' | 'fill' | 'fit'>(input.size, this.defaultValue.size, ['proportional', 'fill', 'fit']),
|
||||
side: stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']),
|
||||
showSlider: stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']),
|
||||
renderCharacters: boolean(input.renderCharacters, this.defaultValue.renderCharacters),
|
||||
scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3),
|
||||
maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000),
|
||||
};
|
||||
@@ -2598,8 +2634,8 @@ class EditorParameterHints extends BaseEditorOption<EditorOption.parameterHints,
|
||||
}
|
||||
const input = _input as IEditorParameterHintOptions;
|
||||
return {
|
||||
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled),
|
||||
cycle: EditorBooleanOption.boolean(input.cycle, this.defaultValue.cycle)
|
||||
enabled: boolean(input.enabled, this.defaultValue.enabled),
|
||||
cycle: boolean(input.cycle, this.defaultValue.cycle)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2686,9 +2722,9 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
|
||||
if (_input && typeof _input === 'object') {
|
||||
const input = _input as IQuickSuggestionsOptions;
|
||||
const opts = {
|
||||
other: EditorBooleanOption.boolean(input.other, this.defaultValue.other),
|
||||
comments: EditorBooleanOption.boolean(input.comments, this.defaultValue.comments),
|
||||
strings: EditorBooleanOption.boolean(input.strings, this.defaultValue.strings),
|
||||
other: boolean(input.other, this.defaultValue.other),
|
||||
comments: boolean(input.comments, this.defaultValue.comments),
|
||||
strings: boolean(input.strings, this.defaultValue.strings),
|
||||
};
|
||||
if (opts.other && opts.comments && opts.strings) {
|
||||
return true; // all on
|
||||
@@ -2916,6 +2952,11 @@ export interface IEditorScrollbarOptions {
|
||||
* Defaults to `horizontalScrollbarSize`.
|
||||
*/
|
||||
horizontalSliderSize?: number;
|
||||
/**
|
||||
* Scroll gutter clicks move by page vs jump to position.
|
||||
* Defaults to false.
|
||||
*/
|
||||
scrollByPage?: boolean;
|
||||
}
|
||||
|
||||
export interface InternalEditorScrollbarOptions {
|
||||
@@ -2931,6 +2972,7 @@ export interface InternalEditorScrollbarOptions {
|
||||
readonly horizontalSliderSize: number;
|
||||
readonly verticalScrollbarSize: number;
|
||||
readonly verticalSliderSize: number;
|
||||
readonly scrollByPage: boolean;
|
||||
}
|
||||
|
||||
function _scrollbarVisibilityFromString(visibility: string | undefined, defaultValue: ScrollbarVisibility): ScrollbarVisibility {
|
||||
@@ -2961,7 +3003,8 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
|
||||
verticalScrollbarSize: 14,
|
||||
verticalSliderSize: 14,
|
||||
handleMouseWheel: true,
|
||||
alwaysConsumeMouseWheel: true
|
||||
alwaysConsumeMouseWheel: true,
|
||||
scrollByPage: false
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2977,15 +3020,16 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
|
||||
arrowSize: EditorIntOption.clampedInt(input.arrowSize, this.defaultValue.arrowSize, 0, 1000),
|
||||
vertical: _scrollbarVisibilityFromString(input.vertical, this.defaultValue.vertical),
|
||||
horizontal: _scrollbarVisibilityFromString(input.horizontal, this.defaultValue.horizontal),
|
||||
useShadows: EditorBooleanOption.boolean(input.useShadows, this.defaultValue.useShadows),
|
||||
verticalHasArrows: EditorBooleanOption.boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows),
|
||||
horizontalHasArrows: EditorBooleanOption.boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows),
|
||||
handleMouseWheel: EditorBooleanOption.boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel),
|
||||
alwaysConsumeMouseWheel: EditorBooleanOption.boolean(input.alwaysConsumeMouseWheel, this.defaultValue.alwaysConsumeMouseWheel),
|
||||
useShadows: boolean(input.useShadows, this.defaultValue.useShadows),
|
||||
verticalHasArrows: boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows),
|
||||
horizontalHasArrows: boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows),
|
||||
handleMouseWheel: boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel),
|
||||
alwaysConsumeMouseWheel: boolean(input.alwaysConsumeMouseWheel, this.defaultValue.alwaysConsumeMouseWheel),
|
||||
horizontalScrollbarSize: horizontalScrollbarSize,
|
||||
horizontalSliderSize: EditorIntOption.clampedInt(input.horizontalSliderSize, horizontalScrollbarSize, 0, 1000),
|
||||
verticalScrollbarSize: verticalScrollbarSize,
|
||||
verticalSliderSize: EditorIntOption.clampedInt(input.verticalSliderSize, verticalScrollbarSize, 0, 1000),
|
||||
scrollByPage: boolean(input.scrollByPage, this.defaultValue.scrollByPage),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3026,6 +3070,10 @@ export interface ISuggestOptions {
|
||||
* Enable or disable the suggest status bar.
|
||||
*/
|
||||
showStatusBar?: boolean;
|
||||
/**
|
||||
* Show details inline with the label. Defaults to true.
|
||||
*/
|
||||
showInlineDetails?: boolean;
|
||||
/**
|
||||
* Show method-suggestions.
|
||||
*/
|
||||
@@ -3149,6 +3197,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
showStatusBar: false,
|
||||
showInlineDetails: true,
|
||||
showMethods: true,
|
||||
showFunctions: true,
|
||||
showConstructors: true,
|
||||
@@ -3220,6 +3269,12 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
default: defaults.showStatusBar,
|
||||
description: nls.localize('suggest.showStatusBar', "Controls the visibility of the status bar at the bottom of the suggest widget.")
|
||||
},
|
||||
|
||||
'editor.suggest.showInlineDetails': {
|
||||
type: 'boolean',
|
||||
default: defaults.showInlineDetails,
|
||||
description: nls.localize('suggest.showInlineDetails', "Controls whether sugget details show inline with the label or only in the details widget")
|
||||
},
|
||||
'editor.suggest.maxVisibleSuggestions': {
|
||||
type: 'number',
|
||||
deprecationMessage: nls.localize('suggest.maxVisibleSuggestions.dep', "This setting is deprecated. The suggest widget can now be resized."),
|
||||
@@ -3378,40 +3433,79 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
}
|
||||
const input = _input as ISuggestOptions;
|
||||
return {
|
||||
insertMode: EditorStringEnumOption.stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']),
|
||||
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
|
||||
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
|
||||
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
|
||||
shareSuggestSelections: EditorBooleanOption.boolean(input.shareSuggestSelections, this.defaultValue.shareSuggestSelections),
|
||||
showIcons: EditorBooleanOption.boolean(input.showIcons, this.defaultValue.showIcons),
|
||||
showStatusBar: EditorBooleanOption.boolean(input.showStatusBar, this.defaultValue.showStatusBar),
|
||||
showMethods: EditorBooleanOption.boolean(input.showMethods, this.defaultValue.showMethods),
|
||||
showFunctions: EditorBooleanOption.boolean(input.showFunctions, this.defaultValue.showFunctions),
|
||||
showConstructors: EditorBooleanOption.boolean(input.showConstructors, this.defaultValue.showConstructors),
|
||||
showFields: EditorBooleanOption.boolean(input.showFields, this.defaultValue.showFields),
|
||||
showVariables: EditorBooleanOption.boolean(input.showVariables, this.defaultValue.showVariables),
|
||||
showClasses: EditorBooleanOption.boolean(input.showClasses, this.defaultValue.showClasses),
|
||||
showStructs: EditorBooleanOption.boolean(input.showStructs, this.defaultValue.showStructs),
|
||||
showInterfaces: EditorBooleanOption.boolean(input.showInterfaces, this.defaultValue.showInterfaces),
|
||||
showModules: EditorBooleanOption.boolean(input.showModules, this.defaultValue.showModules),
|
||||
showProperties: EditorBooleanOption.boolean(input.showProperties, this.defaultValue.showProperties),
|
||||
showEvents: EditorBooleanOption.boolean(input.showEvents, this.defaultValue.showEvents),
|
||||
showOperators: EditorBooleanOption.boolean(input.showOperators, this.defaultValue.showOperators),
|
||||
showUnits: EditorBooleanOption.boolean(input.showUnits, this.defaultValue.showUnits),
|
||||
showValues: EditorBooleanOption.boolean(input.showValues, this.defaultValue.showValues),
|
||||
showConstants: EditorBooleanOption.boolean(input.showConstants, this.defaultValue.showConstants),
|
||||
showEnums: EditorBooleanOption.boolean(input.showEnums, this.defaultValue.showEnums),
|
||||
showEnumMembers: EditorBooleanOption.boolean(input.showEnumMembers, this.defaultValue.showEnumMembers),
|
||||
showKeywords: EditorBooleanOption.boolean(input.showKeywords, this.defaultValue.showKeywords),
|
||||
showWords: EditorBooleanOption.boolean(input.showWords, this.defaultValue.showWords),
|
||||
showColors: EditorBooleanOption.boolean(input.showColors, this.defaultValue.showColors),
|
||||
showFiles: EditorBooleanOption.boolean(input.showFiles, this.defaultValue.showFiles),
|
||||
showReferences: EditorBooleanOption.boolean(input.showReferences, this.defaultValue.showReferences),
|
||||
showFolders: EditorBooleanOption.boolean(input.showFolders, this.defaultValue.showFolders),
|
||||
showTypeParameters: EditorBooleanOption.boolean(input.showTypeParameters, this.defaultValue.showTypeParameters),
|
||||
showSnippets: EditorBooleanOption.boolean(input.showSnippets, this.defaultValue.showSnippets),
|
||||
showUsers: EditorBooleanOption.boolean(input.showUsers, this.defaultValue.showUsers),
|
||||
showIssues: EditorBooleanOption.boolean(input.showIssues, this.defaultValue.showIssues),
|
||||
insertMode: stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']),
|
||||
filterGraceful: boolean(input.filterGraceful, this.defaultValue.filterGraceful),
|
||||
snippetsPreventQuickSuggestions: boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
|
||||
localityBonus: boolean(input.localityBonus, this.defaultValue.localityBonus),
|
||||
shareSuggestSelections: boolean(input.shareSuggestSelections, this.defaultValue.shareSuggestSelections),
|
||||
showIcons: boolean(input.showIcons, this.defaultValue.showIcons),
|
||||
showStatusBar: boolean(input.showStatusBar, this.defaultValue.showStatusBar),
|
||||
showInlineDetails: boolean(input.showInlineDetails, this.defaultValue.showInlineDetails),
|
||||
showMethods: boolean(input.showMethods, this.defaultValue.showMethods),
|
||||
showFunctions: boolean(input.showFunctions, this.defaultValue.showFunctions),
|
||||
showConstructors: boolean(input.showConstructors, this.defaultValue.showConstructors),
|
||||
showFields: boolean(input.showFields, this.defaultValue.showFields),
|
||||
showVariables: boolean(input.showVariables, this.defaultValue.showVariables),
|
||||
showClasses: boolean(input.showClasses, this.defaultValue.showClasses),
|
||||
showStructs: boolean(input.showStructs, this.defaultValue.showStructs),
|
||||
showInterfaces: boolean(input.showInterfaces, this.defaultValue.showInterfaces),
|
||||
showModules: boolean(input.showModules, this.defaultValue.showModules),
|
||||
showProperties: boolean(input.showProperties, this.defaultValue.showProperties),
|
||||
showEvents: boolean(input.showEvents, this.defaultValue.showEvents),
|
||||
showOperators: boolean(input.showOperators, this.defaultValue.showOperators),
|
||||
showUnits: boolean(input.showUnits, this.defaultValue.showUnits),
|
||||
showValues: boolean(input.showValues, this.defaultValue.showValues),
|
||||
showConstants: boolean(input.showConstants, this.defaultValue.showConstants),
|
||||
showEnums: boolean(input.showEnums, this.defaultValue.showEnums),
|
||||
showEnumMembers: boolean(input.showEnumMembers, this.defaultValue.showEnumMembers),
|
||||
showKeywords: boolean(input.showKeywords, this.defaultValue.showKeywords),
|
||||
showWords: boolean(input.showWords, this.defaultValue.showWords),
|
||||
showColors: boolean(input.showColors, this.defaultValue.showColors),
|
||||
showFiles: boolean(input.showFiles, this.defaultValue.showFiles),
|
||||
showReferences: boolean(input.showReferences, this.defaultValue.showReferences),
|
||||
showFolders: boolean(input.showFolders, this.defaultValue.showFolders),
|
||||
showTypeParameters: boolean(input.showTypeParameters, this.defaultValue.showTypeParameters),
|
||||
showSnippets: boolean(input.showSnippets, this.defaultValue.showSnippets),
|
||||
showUsers: boolean(input.showUsers, this.defaultValue.showUsers),
|
||||
showIssues: boolean(input.showIssues, this.defaultValue.showIssues),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region smart select
|
||||
|
||||
export interface ISmartSelectOptions {
|
||||
selectLeadingAndTrailingWhitespace?: boolean
|
||||
}
|
||||
|
||||
export type SmartSelectOptions = Readonly<Required<ISmartSelectOptions>>;
|
||||
|
||||
class SmartSelect extends BaseEditorOption<EditorOption.smartSelect, SmartSelectOptions> {
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
EditorOption.smartSelect, 'smartSelect',
|
||||
{
|
||||
selectLeadingAndTrailingWhitespace: true
|
||||
},
|
||||
{
|
||||
'editor.smartSelect.selectLeadingAndTrailingWhitespace': {
|
||||
description: nls.localize('selectLeadingAndTrailingWhitespace', "Whether leading and trailing whitespace should always be selected."),
|
||||
default: true,
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public validate(input: any): Readonly<Required<ISmartSelectOptions>> {
|
||||
if (!input || typeof input !== 'object') {
|
||||
return this.defaultValue;
|
||||
}
|
||||
return {
|
||||
selectLeadingAndTrailingWhitespace: boolean((input as ISmartSelectOptions).selectLeadingAndTrailingWhitespace, this.defaultValue.selectLeadingAndTrailingWhitespace)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3552,6 +3646,8 @@ export const enum EditorOption {
|
||||
automaticLayout,
|
||||
autoSurround,
|
||||
codeLens,
|
||||
codeLensFontFamily,
|
||||
codeLensFontSize,
|
||||
colorDecorators,
|
||||
columnSelection,
|
||||
comments,
|
||||
@@ -3594,6 +3690,7 @@ export const enum EditorOption {
|
||||
lineHeight,
|
||||
lineNumbers,
|
||||
lineNumbersMinChars,
|
||||
linkedEditing,
|
||||
links,
|
||||
matchBrackets,
|
||||
minimap,
|
||||
@@ -3634,7 +3731,9 @@ export const enum EditorOption {
|
||||
showFoldingControls,
|
||||
showUnused,
|
||||
snippetSuggestions,
|
||||
smartSelect,
|
||||
smoothScrolling,
|
||||
stickyTabStops,
|
||||
stopRenderingLineAfter,
|
||||
suggest,
|
||||
suggestFontSize,
|
||||
@@ -3650,7 +3749,8 @@ export const enum EditorOption {
|
||||
wordWrapBreakAfterCharacters,
|
||||
wordWrapBreakBeforeCharacters,
|
||||
wordWrapColumn,
|
||||
wordWrapMinified,
|
||||
wordWrapOverride1,
|
||||
wordWrapOverride2,
|
||||
wrappingIndent,
|
||||
wrappingStrategy,
|
||||
showDeprecated,
|
||||
@@ -3776,10 +3876,25 @@ export const EditorOptions = {
|
||||
description: nls.localize('autoSurround', "Controls whether the editor should automatically surround selections when typing quotes or brackets.")
|
||||
}
|
||||
)),
|
||||
stickyTabStops: register(new EditorBooleanOption(
|
||||
EditorOption.stickyTabStops, 'stickyTabStops', false,
|
||||
{ description: nls.localize('stickyTabStops', "Emulate selection behaviour of tab characters when using spaces for indentation. Selection will stick to tab stops.") }
|
||||
)),
|
||||
codeLens: register(new EditorBooleanOption(
|
||||
EditorOption.codeLens, 'codeLens', true,
|
||||
{ description: nls.localize('codeLens', "Controls whether the editor shows CodeLens.") }
|
||||
)),
|
||||
codeLensFontFamily: register(new EditorStringOption(
|
||||
EditorOption.codeLensFontFamily, 'codeLensFontFamily', '',
|
||||
{ description: nls.localize('codeLensFontFamily', "Controls the font family for CodeLens.") }
|
||||
)),
|
||||
codeLensFontSize: register(new EditorIntOption(EditorOption.codeLensFontSize, 'codeLensFontSize', 0, 0, 100, {
|
||||
type: 'number',
|
||||
default: 0,
|
||||
minimum: 0,
|
||||
maximum: 100,
|
||||
description: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, the 90% of `#editor.fontSize#` is used.")
|
||||
})),
|
||||
colorDecorators: register(new EditorBooleanOption(
|
||||
EditorOption.colorDecorators, 'colorDecorators', true,
|
||||
{ description: nls.localize('colorDecorators', "Controls whether the editor should render the inline color decorators and color picker.") }
|
||||
@@ -3914,7 +4029,7 @@ export const EditorOptions = {
|
||||
)),
|
||||
hover: register(new EditorHover()),
|
||||
inDiffEditor: register(new EditorBooleanOption(
|
||||
EditorOption.inDiffEditor, 'inDiffEditor', false,
|
||||
EditorOption.inDiffEditor, 'inDiffEditor', false
|
||||
)),
|
||||
letterSpacing: register(new EditorFloatOption(
|
||||
EditorOption.letterSpacing, 'letterSpacing',
|
||||
@@ -3929,6 +4044,10 @@ export const EditorOptions = {
|
||||
EditorOption.lineNumbersMinChars, 'lineNumbersMinChars',
|
||||
5, 1, 300
|
||||
)),
|
||||
linkedEditing: register(new EditorBooleanOption(
|
||||
EditorOption.linkedEditing, 'linkedEditing', false,
|
||||
{ description: nls.localize('linkedEditing', "Controls whether the editor has linked editing enabled. Depending on the language, related symbols, e.g. HTML tags, are updated while editing.") }
|
||||
)),
|
||||
links: register(new EditorBooleanOption(
|
||||
EditorOption.links, 'links', true,
|
||||
{ description: nls.localize('links', "Controls whether the editor should detect links and make them clickable.") }
|
||||
@@ -4030,7 +4149,7 @@ export const EditorOptions = {
|
||||
)),
|
||||
renameOnType: register(new EditorBooleanOption(
|
||||
EditorOption.renameOnType, 'renameOnType', false,
|
||||
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }
|
||||
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type."), markdownDeprecationMessage: nls.localize('renameOnTypeDeprecate', "Deprecated, use `editor.linkedEditing` instead.") }
|
||||
)),
|
||||
renderControlCharacters: register(new EditorBooleanOption(
|
||||
EditorOption.renderControlCharacters, 'renderControlCharacters', false,
|
||||
@@ -4153,6 +4272,7 @@ export const EditorOptions = {
|
||||
description: nls.localize('snippetSuggestions', "Controls whether snippets are shown with other suggestions and how they are sorted.")
|
||||
}
|
||||
)),
|
||||
smartSelect: register(new SmartSelect()),
|
||||
smoothScrolling: register(new EditorBooleanOption(
|
||||
EditorOption.smoothScrolling, 'smoothScrolling', false,
|
||||
{ description: nls.localize('smoothScrolling', "Controls whether the editor will scroll using an animation.") }
|
||||
@@ -4170,7 +4290,7 @@ export const EditorOptions = {
|
||||
suggestLineHeight: register(new EditorIntOption(
|
||||
EditorOption.suggestLineHeight, 'suggestLineHeight',
|
||||
0, 0, 1000,
|
||||
{ markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used.") }
|
||||
{ markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used. The minimum value is 8.") }
|
||||
)),
|
||||
suggestOnTriggerCharacters: register(new EditorBooleanOption(
|
||||
EditorOption.suggestOnTriggerCharacters, 'suggestOnTriggerCharacters', true,
|
||||
@@ -4279,8 +4399,15 @@ export const EditorOptions = {
|
||||
}, "Controls the wrapping column of the editor when `#editor.wordWrap#` is `wordWrapColumn` or `bounded`.")
|
||||
}
|
||||
)),
|
||||
wordWrapMinified: register(new EditorBooleanOption(
|
||||
EditorOption.wordWrapMinified, 'wordWrapMinified', true,
|
||||
wordWrapOverride1: register(new EditorStringEnumOption(
|
||||
EditorOption.wordWrapOverride1, 'wordWrapOverride1',
|
||||
'inherit' as 'off' | 'on' | 'inherit',
|
||||
['off', 'on', 'inherit'] as const
|
||||
)),
|
||||
wordWrapOverride2: register(new EditorStringEnumOption(
|
||||
EditorOption.wordWrapOverride2, 'wordWrapOverride2',
|
||||
'inherit' as 'off' | 'on' | 'inherit',
|
||||
['off', 'on', 'inherit'] as const
|
||||
)),
|
||||
wrappingIndent: register(new EditorEnumOption(
|
||||
EditorOption.wrappingIndent, 'wrappingIndent',
|
||||
|
||||
@@ -531,7 +531,7 @@ export class Cursor extends Disposable {
|
||||
}
|
||||
const closeChar = m[1];
|
||||
|
||||
const autoClosingPairsCandidates = this.context.cursorConfig.autoClosingPairsClose2.get(closeChar);
|
||||
const autoClosingPairsCandidates = this.context.cursorConfig.autoClosingPairs.autoClosingPairsCloseSingleChar.get(closeChar);
|
||||
if (!autoClosingPairsCandidates || autoClosingPairsCandidates.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
||||
|
||||
export const enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
Nearest,
|
||||
}
|
||||
|
||||
export class AtomicTabMoveOperations {
|
||||
/**
|
||||
* Get the visible column at the position. If we get to a non-whitespace character first
|
||||
* or past the end of string then return -1.
|
||||
*
|
||||
* **Note** `position` and the return value are 0-based.
|
||||
*/
|
||||
public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number): [number, number, number] {
|
||||
const lineLength = lineContent.length;
|
||||
let visibleColumn = 0;
|
||||
let prevTabStopPosition = -1;
|
||||
let prevTabStopVisibleColumn = -1;
|
||||
for (let i = 0; i < lineLength; i++) {
|
||||
if (i === position) {
|
||||
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
|
||||
}
|
||||
if (visibleColumn % tabSize === 0) {
|
||||
prevTabStopPosition = i;
|
||||
prevTabStopVisibleColumn = visibleColumn;
|
||||
}
|
||||
const chCode = lineContent.charCodeAt(i);
|
||||
switch (chCode) {
|
||||
case CharCode.Space:
|
||||
visibleColumn += 1;
|
||||
break;
|
||||
case CharCode.Tab:
|
||||
// Skip to the next multiple of tabSize.
|
||||
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
|
||||
break;
|
||||
default:
|
||||
return [-1, -1, -1];
|
||||
}
|
||||
}
|
||||
if (position === lineLength) {
|
||||
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
|
||||
}
|
||||
return [-1, -1, -1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position that should result from a move left, right or to the
|
||||
* nearest tab, if atomic tabs are enabled. Left and right are used for the
|
||||
* arrow key movements, nearest is used for mouse selection. It returns
|
||||
* -1 if atomic tabs are not relevant and you should fall back to normal
|
||||
* behaviour.
|
||||
*
|
||||
* **Note**: `position` and the return value are 0-based.
|
||||
*/
|
||||
public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number {
|
||||
const lineLength = lineContent.length;
|
||||
|
||||
// Get the 0-based visible column corresponding to the position, or return
|
||||
// -1 if it is not in the initial whitespace.
|
||||
const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize);
|
||||
|
||||
if (visibleColumn === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Is the output left or right of the current position. The case for nearest
|
||||
// where it is the same as the current position is handled in the switch.
|
||||
let left: boolean;
|
||||
switch (direction) {
|
||||
case Direction.Left:
|
||||
left = true;
|
||||
break;
|
||||
case Direction.Right:
|
||||
left = false;
|
||||
break;
|
||||
case Direction.Nearest:
|
||||
// The code below assumes the output position is either left or right
|
||||
// of the input position. If it is the same, return immediately.
|
||||
if (visibleColumn % tabSize === 0) {
|
||||
return position;
|
||||
}
|
||||
// Go to the nearest indentation.
|
||||
left = visibleColumn % tabSize <= (tabSize / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
// If going left, we can just use the info about the last tab stop position and
|
||||
// last tab stop visible column that we computed in the first walk over the whitespace.
|
||||
if (left) {
|
||||
if (prevTabStopPosition === -1) {
|
||||
return -1;
|
||||
}
|
||||
// If the direction is left, we need to keep scanning right to ensure
|
||||
// that targetVisibleColumn + tabSize is before non-whitespace.
|
||||
// This is so that when we press left at the end of a partial
|
||||
// indentation it only goes one character. For example ' foo' with
|
||||
// tabSize 4, should jump from position 6 to position 5, not 4.
|
||||
let currentVisibleColumn = prevTabStopVisibleColumn;
|
||||
for (let i = prevTabStopPosition; i < lineLength; ++i) {
|
||||
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
|
||||
// It is a full indentation.
|
||||
return prevTabStopPosition;
|
||||
}
|
||||
|
||||
const chCode = lineContent.charCodeAt(i);
|
||||
switch (chCode) {
|
||||
case CharCode.Space:
|
||||
currentVisibleColumn += 1;
|
||||
break;
|
||||
case CharCode.Tab:
|
||||
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
|
||||
return prevTabStopPosition;
|
||||
}
|
||||
// It must have been a partial indentation.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We are going right.
|
||||
const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
|
||||
|
||||
// We can just continue from where whitespaceVisibleColumn got to.
|
||||
let currentVisibleColumn = visibleColumn;
|
||||
for (let i = position; i < lineLength; i++) {
|
||||
if (currentVisibleColumn === targetVisibleColumn) {
|
||||
return i;
|
||||
}
|
||||
|
||||
const chCode = lineContent.charCodeAt(i);
|
||||
switch (chCode) {
|
||||
case CharCode.Space:
|
||||
currentVisibleColumn += 1;
|
||||
break;
|
||||
case CharCode.Tab:
|
||||
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// This condition handles when the target column is at the end of the line.
|
||||
if (currentVisibleColumn === targetVisibleColumn) {
|
||||
return lineLength;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { ICommand, IConfiguration } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { AutoClosingPairs, IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
@@ -62,6 +62,7 @@ export class CursorConfiguration {
|
||||
public readonly tabSize: number;
|
||||
public readonly indentSize: number;
|
||||
public readonly insertSpaces: boolean;
|
||||
public readonly stickyTabStops: boolean;
|
||||
public readonly pageSize: number;
|
||||
public readonly lineHeight: number;
|
||||
public readonly useTabStops: boolean;
|
||||
@@ -75,8 +76,7 @@ export class CursorConfiguration {
|
||||
public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy;
|
||||
public readonly autoSurround: EditorAutoSurroundStrategy;
|
||||
public readonly autoIndent: EditorAutoIndentStrategy;
|
||||
public readonly autoClosingPairsOpen2: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
public readonly autoClosingPairsClose2: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
public readonly autoClosingPairs: AutoClosingPairs;
|
||||
public readonly surroundingPairs: CharacterMap;
|
||||
public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean };
|
||||
|
||||
@@ -114,6 +114,7 @@ export class CursorConfiguration {
|
||||
this.tabSize = modelOptions.tabSize;
|
||||
this.indentSize = modelOptions.indentSize;
|
||||
this.insertSpaces = modelOptions.insertSpaces;
|
||||
this.stickyTabStops = options.get(EditorOption.stickyTabStops);
|
||||
this.lineHeight = options.get(EditorOption.lineHeight);
|
||||
this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
|
||||
this.useTabStops = options.get(EditorOption.useTabStops);
|
||||
@@ -136,9 +137,7 @@ export class CursorConfiguration {
|
||||
bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets)
|
||||
};
|
||||
|
||||
const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
|
||||
this.autoClosingPairsOpen2 = autoClosingPairs.autoClosingPairsOpen;
|
||||
this.autoClosingPairsClose2 = autoClosingPairs.autoClosingPairsClose;
|
||||
this.autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
|
||||
|
||||
let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier);
|
||||
if (surroundingPairs) {
|
||||
@@ -557,14 +556,14 @@ export class CursorColumns {
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
|
||||
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
|
||||
*/
|
||||
public static prevRenderTabStop(column: number, tabSize: number): number {
|
||||
return column - 1 - (column - 1) % tabSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
|
||||
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
|
||||
*/
|
||||
public static prevIndentTabStop(column: number, indentSize: number): number {
|
||||
return column - 1 - (column - 1) % indentSize;
|
||||
|
||||
@@ -122,7 +122,7 @@ export class DeleteOperations {
|
||||
|
||||
public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, Array<ICommand | null>] {
|
||||
|
||||
if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairsOpen2, model, selections)) {
|
||||
if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairs.autoClosingPairsOpenByEnd, model, selections)) {
|
||||
return this._runAutoClosingPairDelete(config, model, selections);
|
||||
}
|
||||
|
||||
|
||||
@@ -412,7 +412,11 @@ export class CursorMoveCommands {
|
||||
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
|
||||
let newViewState = MoveOperations.moveLeft(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
|
||||
|
||||
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
if (skipWrappingPointStop
|
||||
&& noOfColumns === 1
|
||||
&& cursor.viewState.position.column === viewModel.getLineMinColumn(cursor.viewState.position.lineNumber)
|
||||
&& newViewState.position.lineNumber !== cursor.viewState.position.lineNumber
|
||||
) {
|
||||
// moved over to the previous view line
|
||||
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
|
||||
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
|
||||
@@ -445,7 +449,11 @@ export class CursorMoveCommands {
|
||||
const skipWrappingPointStop = hasMultipleCursors || !cursor.viewState.hasSelection();
|
||||
let newViewState = MoveOperations.moveRight(viewModel.cursorConfig, viewModel, cursor.viewState, inSelectionMode, noOfColumns);
|
||||
|
||||
if (skipWrappingPointStop && noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) {
|
||||
if (skipWrappingPointStop
|
||||
&& noOfColumns === 1
|
||||
&& cursor.viewState.position.column === viewModel.getLineMaxColumn(cursor.viewState.position.lineNumber)
|
||||
&& newViewState.position.lineNumber !== cursor.viewState.position.lineNumber
|
||||
) {
|
||||
// moved over to the next view line
|
||||
const newViewModelPosition = viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position);
|
||||
if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/controller/cursorAtomicMoveOperations';
|
||||
|
||||
export class CursorPosition {
|
||||
_cursorPositionBrand: void;
|
||||
@@ -35,8 +36,20 @@ export class MoveOperations {
|
||||
return new Position(lineNumber, column);
|
||||
}
|
||||
|
||||
public static leftPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number): Position {
|
||||
const minColumn = model.getLineMinColumn(lineNumber);
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Left);
|
||||
if (newPosition === -1) {
|
||||
return this.leftPosition(model, lineNumber, column);
|
||||
}
|
||||
return new Position(lineNumber, minColumn + newPosition);
|
||||
}
|
||||
|
||||
public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
|
||||
const pos = MoveOperations.leftPosition(model, lineNumber, column);
|
||||
const pos = config.stickyTabStops
|
||||
? MoveOperations.leftPositionAtomicSoftTabs(model, lineNumber, column, config.tabSize)
|
||||
: MoveOperations.leftPosition(model, lineNumber, column);
|
||||
return new CursorPosition(pos.lineNumber, pos.column, 0);
|
||||
}
|
||||
|
||||
@@ -67,8 +80,20 @@ export class MoveOperations {
|
||||
return new Position(lineNumber, column);
|
||||
}
|
||||
|
||||
public static rightPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position {
|
||||
const minColumn = model.getLineMinColumn(lineNumber);
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - minColumn, tabSize, Direction.Right);
|
||||
if (newPosition === -1) {
|
||||
return this.rightPosition(model, lineNumber, column);
|
||||
}
|
||||
return new Position(lineNumber, minColumn + newPosition);
|
||||
}
|
||||
|
||||
public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
|
||||
const pos = MoveOperations.rightPosition(model, lineNumber, column);
|
||||
const pos = config.stickyTabStops
|
||||
? MoveOperations.rightPositionAtomicSoftTabs(model, lineNumber, column, config.tabSize, config.indentSize)
|
||||
: MoveOperations.rightPosition(model, lineNumber, column);
|
||||
return new CursorPosition(pos.lineNumber, pos.column, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ export class TypeOperations {
|
||||
if (text.charCodeAt(text.length - 1) === CharCode.CarriageReturn) {
|
||||
text = text.substr(0, text.length - 1);
|
||||
}
|
||||
let lines = text.split(/\r\n|\r|\n/);
|
||||
let lines = strings.splitLines(text);
|
||||
if (lines.length === selections.length) {
|
||||
return lines;
|
||||
}
|
||||
@@ -439,7 +439,7 @@ export class TypeOperations {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.autoClosingPairsClose2.has(ch)) {
|
||||
if (!config.autoClosingPairs.autoClosingPairsCloseSingleChar.has(ch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -498,31 +498,20 @@ export class TypeOperations {
|
||||
});
|
||||
}
|
||||
|
||||
private static _autoClosingPairIsSymmetric(autoClosingPair: StandardAutoClosingPairConditional): boolean {
|
||||
const { open, close } = autoClosingPair;
|
||||
return (open.indexOf(close) >= 0 || close.indexOf(open) >= 0);
|
||||
}
|
||||
private static _isBeforeClosingBrace(config: CursorConfiguration, lineAfter: string) {
|
||||
// If the start of lineAfter can be interpretted as both a starting or ending brace, default to returning false
|
||||
const nextChar = lineAfter.charAt(0);
|
||||
const potentialStartingBraces = config.autoClosingPairs.autoClosingPairsOpenByStart.get(nextChar) || [];
|
||||
const potentialClosingBraces = config.autoClosingPairs.autoClosingPairsCloseByStart.get(nextChar) || [];
|
||||
|
||||
private static _isBeforeClosingBrace(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional, characterAfter: string) {
|
||||
const otherAutoClosingPairs = config.autoClosingPairsClose2.get(characterAfter);
|
||||
if (!otherAutoClosingPairs) {
|
||||
return false;
|
||||
}
|
||||
const isBeforeStartingBrace = potentialStartingBraces.some(x => lineAfter.startsWith(x.open));
|
||||
const isBeforeClosingBrace = potentialClosingBraces.some(x => lineAfter.startsWith(x.close));
|
||||
|
||||
const thisBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(autoClosingPair);
|
||||
for (const otherAutoClosingPair of otherAutoClosingPairs) {
|
||||
const otherBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(otherAutoClosingPair);
|
||||
if (!thisBraceIsSymmetric && otherBraceIsSymmetric) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !isBeforeStartingBrace && isBeforeClosingBrace;
|
||||
}
|
||||
|
||||
private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null {
|
||||
const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(ch);
|
||||
const autoClosingPairCandidates = config.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch);
|
||||
if (!autoClosingPairCandidates) {
|
||||
return null;
|
||||
}
|
||||
@@ -548,7 +537,29 @@ export class TypeOperations {
|
||||
return autoClosingPair;
|
||||
}
|
||||
|
||||
private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): StandardAutoClosingPairConditional | null {
|
||||
private static _findSubAutoClosingPairClose(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional): string {
|
||||
if (autoClosingPair.open.length <= 1) {
|
||||
return '';
|
||||
}
|
||||
const lastChar = autoClosingPair.close.charAt(autoClosingPair.close.length - 1);
|
||||
// get candidates with the same last character as close
|
||||
const subPairCandidates = config.autoClosingPairs.autoClosingPairsCloseByEnd.get(lastChar) || [];
|
||||
let subPairMatch: StandardAutoClosingPairConditional | null = null;
|
||||
for (const x of subPairCandidates) {
|
||||
if (x.open !== autoClosingPair.open && autoClosingPair.open.includes(x.open) && autoClosingPair.close.endsWith(x.close)) {
|
||||
if (!subPairMatch || x.open.length > subPairMatch.open.length) {
|
||||
subPairMatch = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (subPairMatch) {
|
||||
return subPairMatch.close;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): string | null {
|
||||
const chIsQuote = isQuote(ch);
|
||||
const autoCloseConfig = chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets;
|
||||
if (autoCloseConfig === 'never') {
|
||||
@@ -560,6 +571,9 @@ export class TypeOperations {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subAutoClosingPairClose = this._findSubAutoClosingPairClose(config, autoClosingPair);
|
||||
let isSubAutoClosingPairPresent = true;
|
||||
|
||||
const shouldAutoCloseBefore = chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket;
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
@@ -570,11 +584,16 @@ export class TypeOperations {
|
||||
|
||||
const position = selection.getPosition();
|
||||
const lineText = model.getLineContent(position.lineNumber);
|
||||
const lineAfter = lineText.substring(position.column - 1);
|
||||
|
||||
// Only consider auto closing the pair if a space follows or if another autoclosed pair follows
|
||||
if (!lineAfter.startsWith(subAutoClosingPairClose)) {
|
||||
isSubAutoClosingPairPresent = false;
|
||||
}
|
||||
|
||||
// Only consider auto closing the pair if an allowed character follows or if another autoclosed pair closing brace follows
|
||||
if (lineText.length > position.column - 1) {
|
||||
const characterAfter = lineText.charAt(position.column - 1);
|
||||
const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, autoClosingPair, characterAfter);
|
||||
const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, lineAfter);
|
||||
|
||||
if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) {
|
||||
return null;
|
||||
@@ -612,14 +631,18 @@ export class TypeOperations {
|
||||
}
|
||||
}
|
||||
|
||||
return autoClosingPair;
|
||||
if (isSubAutoClosingPairPresent) {
|
||||
return autoClosingPair.close.substring(0, autoClosingPair.close.length - subAutoClosingPairClose.length);
|
||||
} else {
|
||||
return autoClosingPair.close;
|
||||
}
|
||||
}
|
||||
|
||||
private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPair: StandardAutoClosingPairConditional): EditOperationResult {
|
||||
private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPairClose: string): EditOperationResult {
|
||||
let commands: ICommand[] = [];
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i];
|
||||
commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPair.close);
|
||||
commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPairClose);
|
||||
}
|
||||
return new EditOperationResult(EditOperationType.Typing, commands, {
|
||||
shouldPushStackElementBefore: true,
|
||||
@@ -794,9 +817,9 @@ export class TypeOperations {
|
||||
});
|
||||
}
|
||||
|
||||
const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, false);
|
||||
if (autoClosingPairOpenCharType) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairOpenCharType);
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false);
|
||||
if (autoClosingPairClose !== null) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairClose);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -838,9 +861,9 @@ export class TypeOperations {
|
||||
}
|
||||
|
||||
if (!isDoingComposition) {
|
||||
const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true);
|
||||
if (autoClosingPairOpenCharType) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType);
|
||||
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, true);
|
||||
if (autoClosingPairClose) {
|
||||
return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairClose);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@ export class WordOperations {
|
||||
return selection;
|
||||
}
|
||||
|
||||
if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpen, ctx.model, [ctx.selection])) {
|
||||
if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpenByEnd, ctx.model, [ctx.selection])) {
|
||||
const position = ctx.selection.getPosition();
|
||||
return new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1);
|
||||
}
|
||||
@@ -438,6 +438,122 @@ export class WordOperations {
|
||||
return new Range(lineNumber, column, position.lineNumber, position.column);
|
||||
}
|
||||
|
||||
public static deleteInsideWord(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection): Range {
|
||||
if (!selection.isEmpty()) {
|
||||
return selection;
|
||||
}
|
||||
|
||||
const position = new Position(selection.positionLineNumber, selection.positionColumn);
|
||||
|
||||
let r = this._deleteInsideWordWhitespace(model, position);
|
||||
if (r) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return this._deleteInsideWordDetermineDeleteRange(wordSeparators, model, position);
|
||||
}
|
||||
|
||||
private static _charAtIsWhitespace(str: string, index: number): boolean {
|
||||
const charCode = str.charCodeAt(index);
|
||||
return (charCode === CharCode.Space || charCode === CharCode.Tab);
|
||||
}
|
||||
|
||||
private static _deleteInsideWordWhitespace(model: ICursorSimpleModel, position: Position): Range | null {
|
||||
const lineContent = model.getLineContent(position.lineNumber);
|
||||
const lineContentLength = lineContent.length;
|
||||
|
||||
if (lineContentLength === 0) {
|
||||
// empty line
|
||||
return null;
|
||||
}
|
||||
|
||||
let leftIndex = Math.max(position.column - 2, 0);
|
||||
if (!this._charAtIsWhitespace(lineContent, leftIndex)) {
|
||||
// touches a non-whitespace character to the left
|
||||
return null;
|
||||
}
|
||||
|
||||
let rightIndex = Math.min(position.column - 1, lineContentLength - 1);
|
||||
if (!this._charAtIsWhitespace(lineContent, rightIndex)) {
|
||||
// touches a non-whitespace character to the right
|
||||
return null;
|
||||
}
|
||||
|
||||
// walk over whitespace to the left
|
||||
while (leftIndex > 0 && this._charAtIsWhitespace(lineContent, leftIndex - 1)) {
|
||||
leftIndex--;
|
||||
}
|
||||
|
||||
// walk over whitespace to the right
|
||||
while (rightIndex + 1 < lineContentLength && this._charAtIsWhitespace(lineContent, rightIndex + 1)) {
|
||||
rightIndex++;
|
||||
}
|
||||
|
||||
return new Range(position.lineNumber, leftIndex + 1, position.lineNumber, rightIndex + 2);
|
||||
}
|
||||
|
||||
private static _deleteInsideWordDetermineDeleteRange(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): Range {
|
||||
const lineContent = model.getLineContent(position.lineNumber);
|
||||
const lineLength = lineContent.length;
|
||||
if (lineLength === 0) {
|
||||
// empty line
|
||||
if (position.lineNumber > 1) {
|
||||
return new Range(position.lineNumber - 1, model.getLineMaxColumn(position.lineNumber - 1), position.lineNumber, 1);
|
||||
} else {
|
||||
if (position.lineNumber < model.getLineCount()) {
|
||||
return new Range(position.lineNumber, 1, position.lineNumber + 1, 1);
|
||||
} else {
|
||||
// empty model
|
||||
return new Range(position.lineNumber, 1, position.lineNumber, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const touchesWord = (word: IFindWordResult) => {
|
||||
return (word.start + 1 <= position.column && position.column <= word.end + 1);
|
||||
};
|
||||
const createRangeWithPosition = (startColumn: number, endColumn: number) => {
|
||||
startColumn = Math.min(startColumn, position.column);
|
||||
endColumn = Math.max(endColumn, position.column);
|
||||
return new Range(position.lineNumber, startColumn, position.lineNumber, endColumn);
|
||||
};
|
||||
const deleteWordAndAdjacentWhitespace = (word: IFindWordResult) => {
|
||||
let startColumn = word.start + 1;
|
||||
let endColumn = word.end + 1;
|
||||
let expandedToTheRight = false;
|
||||
while (endColumn - 1 < lineLength && this._charAtIsWhitespace(lineContent, endColumn - 1)) {
|
||||
expandedToTheRight = true;
|
||||
endColumn++;
|
||||
}
|
||||
if (!expandedToTheRight) {
|
||||
while (startColumn > 1 && this._charAtIsWhitespace(lineContent, startColumn - 2)) {
|
||||
startColumn--;
|
||||
}
|
||||
}
|
||||
return createRangeWithPosition(startColumn, endColumn);
|
||||
};
|
||||
|
||||
const prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, position);
|
||||
if (prevWordOnLine && touchesWord(prevWordOnLine)) {
|
||||
return deleteWordAndAdjacentWhitespace(prevWordOnLine);
|
||||
}
|
||||
const nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, position);
|
||||
if (nextWordOnLine && touchesWord(nextWordOnLine)) {
|
||||
return deleteWordAndAdjacentWhitespace(nextWordOnLine);
|
||||
}
|
||||
if (prevWordOnLine && nextWordOnLine) {
|
||||
return createRangeWithPosition(prevWordOnLine.end + 1, nextWordOnLine.start + 1);
|
||||
}
|
||||
if (prevWordOnLine) {
|
||||
return createRangeWithPosition(prevWordOnLine.start + 1, prevWordOnLine.end + 1);
|
||||
}
|
||||
if (nextWordOnLine) {
|
||||
return createRangeWithPosition(nextWordOnLine.start + 1, nextWordOnLine.end + 1);
|
||||
}
|
||||
|
||||
return createRangeWithPosition(1, lineLength + 1);
|
||||
}
|
||||
|
||||
public static _deleteWordPartLeft(model: ICursorSimpleModel, selection: Selection): Range {
|
||||
if (!selection.isEmpty()) {
|
||||
return selection;
|
||||
|
||||
@@ -24,6 +24,7 @@ export namespace EditorContextKeys {
|
||||
export const textInputFocus = new RawContextKey<boolean>('textInputFocus', false);
|
||||
|
||||
export const readOnly = new RawContextKey<boolean>('editorReadonly', false);
|
||||
export const inDiffEditor = new RawContextKey<boolean>('inDiffEditor', false);
|
||||
export const columnSelection = new RawContextKey<boolean>('editorColumnSelection', false);
|
||||
export const writable = readOnly.toNegated();
|
||||
export const hasNonEmptySelection = new RawContextKey<boolean>('editorHasSelection', false);
|
||||
|
||||
@@ -695,6 +695,11 @@ export interface ITextModel {
|
||||
*/
|
||||
getEOL(): string;
|
||||
|
||||
/**
|
||||
* Get the end of line sequence predominantly used in the text buffer.
|
||||
*/
|
||||
getEndOfLineSequence(): EndOfLineSequence;
|
||||
|
||||
/**
|
||||
* Get the minimum legal column for line at `lineNumber`
|
||||
*/
|
||||
@@ -850,7 +855,12 @@ export interface ITextModel {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hasSemanticTokens(): boolean;
|
||||
hasCompleteSemanticTokens(): boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hasSomeSemanticTokens(): boolean;
|
||||
|
||||
/**
|
||||
* Flush all tokenization state.
|
||||
@@ -1089,12 +1099,17 @@ export interface ITextModel {
|
||||
detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void;
|
||||
|
||||
/**
|
||||
* Push a stack element onto the undo stack. This acts as an undo/redo point.
|
||||
* The idea is to use `pushEditOperations` to edit the model and then to
|
||||
* `pushStackElement` to create an undo/redo stop point.
|
||||
* Close the current undo-redo element.
|
||||
* This offers a way to create an undo/redo stop point.
|
||||
*/
|
||||
pushStackElement(): void;
|
||||
|
||||
/**
|
||||
* Open the current undo-redo element.
|
||||
* This offers a way to remove the current undo/redo stop point.
|
||||
*/
|
||||
popStackElement(): void;
|
||||
|
||||
/**
|
||||
* Push edit operations, basically editing the model. This is the preferred way
|
||||
* of editing the model. The edit operations will land on the undo stack.
|
||||
@@ -1138,7 +1153,7 @@ export interface ITextModel {
|
||||
_applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
|
||||
|
||||
/**
|
||||
* Undo edit operations until the first previous stop point created by `pushStackElement`.
|
||||
* Undo edit operations until the previous undo/redo point.
|
||||
* The inverse edit operations will be pushed on the redo stack.
|
||||
* @internal
|
||||
*/
|
||||
@@ -1151,7 +1166,7 @@ export interface ITextModel {
|
||||
canUndo(): boolean;
|
||||
|
||||
/**
|
||||
* Redo edit operations until the next stop point created by `pushStackElement`.
|
||||
* Redo edit operations until the next undo/redo point.
|
||||
* The inverse edit operations will be pushed on the undo stack.
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -199,6 +199,12 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
|
||||
}
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
if (!(this._data instanceof SingleModelEditStackData)) {
|
||||
this._data = SingleModelEditStackData.deserialize(this._data);
|
||||
}
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
if (URI.isUri(this.model)) {
|
||||
// don't have a model
|
||||
@@ -315,6 +321,10 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement {
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
// cannot reopen
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
this._isOpen = false;
|
||||
|
||||
@@ -386,6 +396,13 @@ export class EditStack {
|
||||
}
|
||||
}
|
||||
|
||||
public popStackElement(): void {
|
||||
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
|
||||
if (isEditStackElement(lastElement)) {
|
||||
lastElement.open();
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._undoRedoService.removeElements(this._model.uri);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { splitLines } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
@@ -131,7 +132,7 @@ export class MirrorTextModel {
|
||||
// Nothing to insert
|
||||
return;
|
||||
}
|
||||
let insertLines = insertText.split(/\r\n|\r|\n/);
|
||||
let insertLines = splitLines(insertText);
|
||||
if (insertLines.length === 1) {
|
||||
// Inserting text on one line
|
||||
this._setLineText(position.lineNumber - 1,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTex
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { countEOL, StringEOL } from 'vs/editor/common/model/tokensStore';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IValidatedEditOperation {
|
||||
sortIndex: number;
|
||||
@@ -32,26 +32,24 @@ export interface IReverseSingleEditOperation extends IValidEditOperation {
|
||||
sortIndex: number;
|
||||
}
|
||||
|
||||
export class PieceTreeTextBuffer implements ITextBuffer, IDisposable {
|
||||
private readonly _pieceTree: PieceTreeBase;
|
||||
export class PieceTreeTextBuffer extends Disposable implements ITextBuffer {
|
||||
private _pieceTree: PieceTreeBase;
|
||||
private readonly _BOM: string;
|
||||
private _mightContainRTL: boolean;
|
||||
private _mightContainUnusualLineTerminators: boolean;
|
||||
private _mightContainNonBasicASCII: boolean;
|
||||
|
||||
private readonly _onDidChangeContent: Emitter<void> = new Emitter<void>();
|
||||
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
|
||||
|
||||
constructor(chunks: StringBuffer[], BOM: string, eol: '\r\n' | '\n', containsRTL: boolean, containsUnusualLineTerminators: boolean, isBasicASCII: boolean, eolNormalized: boolean) {
|
||||
super();
|
||||
this._BOM = BOM;
|
||||
this._mightContainNonBasicASCII = !isBasicASCII;
|
||||
this._mightContainRTL = containsRTL;
|
||||
this._mightContainUnusualLineTerminators = containsUnusualLineTerminators;
|
||||
this._pieceTree = new PieceTreeBase(chunks, eol, eolNormalized);
|
||||
}
|
||||
dispose(): void {
|
||||
this._onDidChangeContent.dispose();
|
||||
}
|
||||
|
||||
// #region TextBuffer
|
||||
public equals(other: ITextBuffer): boolean {
|
||||
|
||||
@@ -387,6 +387,9 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._isDisposed = true;
|
||||
super.dispose();
|
||||
this._isDisposing = false;
|
||||
// Manually release reference to previous text buffer to avoid large leaks
|
||||
// in case someone leaks a TextModel reference
|
||||
this._buffer = createTextBuffer('', this._options.defaultEOL);
|
||||
}
|
||||
|
||||
private _assertNotDisposed(): void {
|
||||
@@ -830,6 +833,15 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return this._buffer.getEOL();
|
||||
}
|
||||
|
||||
public getEndOfLineSequence(): model.EndOfLineSequence {
|
||||
this._assertNotDisposed();
|
||||
return (
|
||||
this._buffer.getEOL() === '\n'
|
||||
? model.EndOfLineSequence.LF
|
||||
: model.EndOfLineSequence.CRLF
|
||||
);
|
||||
}
|
||||
|
||||
public getLineMinColumn(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
return 1;
|
||||
@@ -1213,6 +1225,10 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._commandManager.pushStackElement();
|
||||
}
|
||||
|
||||
public popStackElement(): void {
|
||||
this._commandManager.popStackElement();
|
||||
}
|
||||
|
||||
public pushEOL(eol: model.EndOfLineSequence): void {
|
||||
const currentEOL = (this.getEOL() === '\n' ? model.EndOfLineSequence.LF : model.EndOfLineSequence.CRLF);
|
||||
if (currentEOL === eol) {
|
||||
@@ -1868,12 +1884,16 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
});
|
||||
}
|
||||
|
||||
public hasSemanticTokens(): boolean {
|
||||
public hasCompleteSemanticTokens(): boolean {
|
||||
return this._tokens2.isComplete();
|
||||
}
|
||||
|
||||
public hasSomeSemanticTokens(): boolean {
|
||||
return !this._tokens2.isEmpty();
|
||||
}
|
||||
|
||||
public setPartialSemanticTokens(range: Range, tokens: MultilineTokens2[]): void {
|
||||
if (this.hasSemanticTokens()) {
|
||||
if (this.hasCompleteSemanticTokens()) {
|
||||
return;
|
||||
}
|
||||
const changedRange = this._tokens2.setPartial(range, tokens);
|
||||
|
||||
@@ -884,6 +884,10 @@ export class TokensStore2 {
|
||||
this._isComplete = false;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return (this._pieces.length === 0);
|
||||
}
|
||||
|
||||
public set(pieces: MultilineTokens2[] | null, isComplete: boolean): void {
|
||||
this._pieces = pieces || [];
|
||||
this._isComplete = isComplete;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationReg
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { iconRegistry, Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
/**
|
||||
* Open ended enum at runtime
|
||||
* @internal
|
||||
@@ -819,17 +819,32 @@ export interface DocumentHighlightProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* The rename provider interface defines the contract between extensions and
|
||||
* the live-rename feature.
|
||||
* The linked editing range provider interface defines the contract between extensions and
|
||||
* the linked editing feature.
|
||||
*/
|
||||
export interface OnTypeRenameProvider {
|
||||
|
||||
wordPattern?: RegExp;
|
||||
export interface LinkedEditingRangeProvider {
|
||||
|
||||
/**
|
||||
* Provide a list of ranges that can be live-renamed together.
|
||||
* Provide a list of ranges that can be edited together.
|
||||
*/
|
||||
provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ ranges: IRange[]; wordPattern?: RegExp; }>;
|
||||
provideLinkedEditingRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<LinkedEditingRanges>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a list of ranges that can be edited together along with a word pattern to describe valid contents.
|
||||
*/
|
||||
export interface LinkedEditingRanges {
|
||||
/**
|
||||
* A list of ranges that can be edited together. The ranges must have
|
||||
* identical length and text content. The ranges cannot overlap
|
||||
*/
|
||||
ranges: IRange[];
|
||||
|
||||
/**
|
||||
* An optional word pattern that describes valid contents for the given ranges.
|
||||
* If no pattern is provided, the language configuration's word pattern will be used.
|
||||
*/
|
||||
wordPattern?: RegExp;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1359,7 +1374,10 @@ export interface WorkspaceEditMetadata {
|
||||
needsConfirmation: boolean;
|
||||
label: string;
|
||||
description?: string;
|
||||
iconPath?: { id: string } | URI | { light: URI, dark: URI };
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
iconPath?: ThemeIcon | URI | { light: URI, dark: URI };
|
||||
}
|
||||
|
||||
export interface WorkspaceFileEditOptions {
|
||||
@@ -1367,6 +1385,10 @@ export interface WorkspaceFileEditOptions {
|
||||
ignoreIfNotExists?: boolean;
|
||||
ignoreIfExists?: boolean;
|
||||
recursive?: boolean;
|
||||
copy?: boolean;
|
||||
folder?: boolean;
|
||||
skipTrashBin?: boolean;
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
export interface WorkspaceFileEdit {
|
||||
@@ -1715,7 +1737,7 @@ export const DocumentHighlightProviderRegistry = new LanguageFeatureRegistry<Doc
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const OnTypeRenameProviderRegistry = new LanguageFeatureRegistry<OnTypeRenameProvider>();
|
||||
export const LinkedEditingRangeProviderRegistry = new LanguageFeatureRegistry<LinkedEditingRangeProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@@ -294,17 +294,32 @@ export class StandardAutoClosingPairConditional {
|
||||
* @internal
|
||||
*/
|
||||
export class AutoClosingPairs {
|
||||
// it is useful to be able to get pairs using either end of open and close
|
||||
|
||||
public readonly autoClosingPairsOpen: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
public readonly autoClosingPairsClose: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
/** Key is first character of open */
|
||||
public readonly autoClosingPairsOpenByStart: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
/** Key is last character of open */
|
||||
public readonly autoClosingPairsOpenByEnd: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
/** Key is first character of close */
|
||||
public readonly autoClosingPairsCloseByStart: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
/** Key is last character of close */
|
||||
public readonly autoClosingPairsCloseByEnd: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
/** Key is close. Only has pairs that are a single character */
|
||||
public readonly autoClosingPairsCloseSingleChar: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
|
||||
constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) {
|
||||
this.autoClosingPairsOpen = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsClose = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsOpenByStart = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsOpenByEnd = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsCloseByStart = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsCloseByEnd = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
this.autoClosingPairsCloseSingleChar = new Map<string, StandardAutoClosingPairConditional[]>();
|
||||
for (const pair of autoClosingPairs) {
|
||||
appendEntry(this.autoClosingPairsOpen, pair.open.charAt(pair.open.length - 1), pair);
|
||||
if (pair.close.length === 1) {
|
||||
appendEntry(this.autoClosingPairsClose, pair.close, pair);
|
||||
appendEntry(this.autoClosingPairsOpenByStart, pair.open.charAt(0), pair);
|
||||
appendEntry(this.autoClosingPairsOpenByEnd, pair.open.charAt(pair.open.length - 1), pair);
|
||||
appendEntry(this.autoClosingPairsCloseByStart, pair.close.charAt(0), pair);
|
||||
appendEntry(this.autoClosingPairsCloseByEnd, pair.close.charAt(pair.close.length - 1), pair);
|
||||
if (pair.close.length === 1 && pair.open.length === 1) {
|
||||
appendEntry(this.autoClosingPairsCloseSingleChar, pair.close, pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +622,12 @@ export class LanguageConfigurationRegistryImpl {
|
||||
return null;
|
||||
}
|
||||
const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
|
||||
|
||||
if (scopedLineTokens.firstCharOffset) {
|
||||
// this line has mixed languages and indentation rules will not work
|
||||
return null;
|
||||
}
|
||||
|
||||
const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
|
||||
@@ -12,6 +12,14 @@ export const enum IndentConsts {
|
||||
UNINDENT_MASK = 0b00001000,
|
||||
}
|
||||
|
||||
function resetGlobalRegex(reg: RegExp) {
|
||||
if (reg.global) {
|
||||
reg.lastIndex = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export class IndentRulesSupport {
|
||||
|
||||
private readonly _indentationRules: IndentationRule;
|
||||
@@ -22,7 +30,7 @@ export class IndentRulesSupport {
|
||||
|
||||
public shouldIncrease(text: string): boolean {
|
||||
if (this._indentationRules) {
|
||||
if (this._indentationRules.increaseIndentPattern && this._indentationRules.increaseIndentPattern.test(text)) {
|
||||
if (this._indentationRules.increaseIndentPattern && resetGlobalRegex(this._indentationRules.increaseIndentPattern) && this._indentationRules.increaseIndentPattern.test(text)) {
|
||||
return true;
|
||||
}
|
||||
// if (this._indentationRules.indentNextLinePattern && this._indentationRules.indentNextLinePattern.test(text)) {
|
||||
@@ -33,14 +41,14 @@ export class IndentRulesSupport {
|
||||
}
|
||||
|
||||
public shouldDecrease(text: string): boolean {
|
||||
if (this._indentationRules && this._indentationRules.decreaseIndentPattern && this._indentationRules.decreaseIndentPattern.test(text)) {
|
||||
if (this._indentationRules && this._indentationRules.decreaseIndentPattern && resetGlobalRegex(this._indentationRules.decreaseIndentPattern) && this._indentationRules.decreaseIndentPattern.test(text)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public shouldIndentNextLine(text: string): boolean {
|
||||
if (this._indentationRules && this._indentationRules.indentNextLinePattern && this._indentationRules.indentNextLinePattern.test(text)) {
|
||||
if (this._indentationRules && this._indentationRules.indentNextLinePattern && resetGlobalRegex(this._indentationRules.indentNextLinePattern) && this._indentationRules.indentNextLinePattern.test(text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -49,7 +57,7 @@ export class IndentRulesSupport {
|
||||
|
||||
public shouldIgnore(text: string): boolean {
|
||||
// the text matches `unIndentedLinePattern`
|
||||
if (this._indentationRules && this._indentationRules.unIndentedLinePattern && this._indentationRules.unIndentedLinePattern.test(text)) {
|
||||
if (this._indentationRules && this._indentationRules.unIndentedLinePattern && resetGlobalRegex(this._indentationRules.unIndentedLinePattern) && this._indentationRules.unIndentedLinePattern.test(text)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
|
||||
|
||||
function _tokenizeToString(text: string, tokenizationSupport: IReducedTokenizationSupport): string {
|
||||
let result = `<div class="monaco-tokenized-source">`;
|
||||
let lines = text.split(/\r\n|\r|\n/);
|
||||
let lines = strings.splitLines(text);
|
||||
let currentState = tokenizationSupport.getInitialState();
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
let line = lines[i];
|
||||
|
||||
@@ -530,36 +530,30 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
||||
|
||||
private static readonly _suggestionsLimit = 10000;
|
||||
|
||||
public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<{ words: string[], duration: number } | null> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[], duration: number } | null> {
|
||||
|
||||
const sw = new StopWatch(true);
|
||||
const words: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
|
||||
const seen = new Set<string>();
|
||||
|
||||
const wordAt = model.getWordAtPosition(position, wordDefRegExp);
|
||||
if (wordAt) {
|
||||
seen.add(model.getValueInRange(wordAt));
|
||||
}
|
||||
|
||||
for (let word of model.words(wordDefRegExp)) {
|
||||
if (seen.has(word)) {
|
||||
outer: for (let url of modelUrls) {
|
||||
const model = this._getModel(url);
|
||||
if (!model) {
|
||||
continue;
|
||||
}
|
||||
seen.add(word);
|
||||
if (!isNaN(Number(word))) {
|
||||
continue;
|
||||
}
|
||||
words.push(word);
|
||||
if (seen.size > EditorSimpleWorker._suggestionsLimit) {
|
||||
break;
|
||||
|
||||
for (let word of model.words(wordDefRegExp)) {
|
||||
if (word === leadingWord || !isNaN(Number(word))) {
|
||||
continue;
|
||||
}
|
||||
seen.add(word);
|
||||
if (seen.size > EditorSimpleWorker._suggestionsLimit) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { words, duration: sw.elapsed() };
|
||||
|
||||
return { words: Array.from(seen), duration: sw.elapsed() };
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IntervalTimer } from 'vs/base/common/async';
|
||||
import { IntervalTimer, timeout } from 'vs/base/common/async';
|
||||
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker';
|
||||
import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { IChange } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -105,7 +105,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
|
||||
const sw = StopWatch.create(true);
|
||||
const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits));
|
||||
result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
|
||||
return result;
|
||||
return Promise.race([result, timeout(1000).then(() => edits)]);
|
||||
|
||||
} else {
|
||||
return Promise.resolve(undefined);
|
||||
@@ -148,20 +148,47 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider {
|
||||
}
|
||||
|
||||
async provideCompletionItems(model: ITextModel, position: Position): Promise<modes.CompletionList | undefined> {
|
||||
const { wordBasedSuggestions } = this._configurationService.getValue<{ wordBasedSuggestions?: boolean }>(model.uri, position, 'editor');
|
||||
if (!wordBasedSuggestions) {
|
||||
type WordBasedSuggestionsConfig = {
|
||||
wordBasedSuggestions?: boolean,
|
||||
wordBasedSuggestionsMode?: 'currentDocument' | 'matchingDocuments' | 'allDocuments'
|
||||
};
|
||||
const config = this._configurationService.getValue<WordBasedSuggestionsConfig>(model.uri, position, 'editor');
|
||||
if (!config.wordBasedSuggestions) {
|
||||
return undefined;
|
||||
}
|
||||
if (!canSyncModel(this._modelService, model.uri)) {
|
||||
return undefined; // File too large
|
||||
|
||||
const models: URI[] = [];
|
||||
if (config.wordBasedSuggestionsMode === 'currentDocument') {
|
||||
// only current file and only if not too large
|
||||
if (canSyncModel(this._modelService, model.uri)) {
|
||||
models.push(model.uri);
|
||||
}
|
||||
} else {
|
||||
// either all files or files of same language
|
||||
for (const candidate of this._modelService.getModels()) {
|
||||
if (!canSyncModel(this._modelService, candidate.uri)) {
|
||||
continue;
|
||||
}
|
||||
if (candidate === model) {
|
||||
models.unshift(candidate.uri);
|
||||
|
||||
} else if (config.wordBasedSuggestionsMode === 'allDocuments' || candidate.getLanguageIdentifier().id === model.getLanguageIdentifier().id) {
|
||||
models.push(candidate.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (models.length === 0) {
|
||||
return undefined; // File too large, no other files
|
||||
}
|
||||
|
||||
const wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
const word = model.getWordAtPosition(position);
|
||||
const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
|
||||
const insert = replace.setEndPosition(position.lineNumber, position.column);
|
||||
|
||||
const client = await this._workerManager.withWorker();
|
||||
const data = await client.textualSuggest(model.uri, position);
|
||||
const data = await client.textualSuggest(models, word?.word, wordDefRegExp);
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -463,17 +490,11 @@ export class EditorWorkerClient extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
public textualSuggest(resource: URI, position: IPosition): Promise<{ words: string[], duration: number } | null> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
let model = this._modelService.getModel(resource);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
let wordDef = wordDefRegExp.source;
|
||||
let wordDefFlags = regExpFlags(wordDefRegExp);
|
||||
return proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags);
|
||||
});
|
||||
public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[], duration: number } | null> {
|
||||
const proxy = await this._withSyncedResources(resources);
|
||||
const wordDef = wordDefRegExp.source;
|
||||
const wordDefFlags = regExpFlags(wordDefRegExp);
|
||||
return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags);
|
||||
}
|
||||
|
||||
computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> {
|
||||
|
||||
@@ -93,6 +93,6 @@ export function detectModeId(modelService: IModelService, modeService: IModeServ
|
||||
return modeService.getModeIdByFilepathOrFirstLine(resource);
|
||||
}
|
||||
|
||||
export function cssEscape(val: string): string {
|
||||
return val.replace(/\s/g, '\\$&'); // make sure to not introduce CSS classes from files that contain whitespace
|
||||
export function cssEscape(str: string): string {
|
||||
return str.replace(/[\11\12\14\15\40]/g, '/'); // HTML class names can not contain certain whitespace characters, use / instead, which doesn't exist in file names.
|
||||
}
|
||||
|
||||
@@ -790,6 +790,10 @@ class ModelSemanticColoring extends Disposable {
|
||||
}
|
||||
const provider = this._getSemanticColoringProvider();
|
||||
if (!provider) {
|
||||
if (this._currentDocumentResponse) {
|
||||
// there are semantic tokens set
|
||||
this._model.setSemanticTokens(null, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._currentDocumentRequestCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
@@ -179,113 +179,119 @@ export enum EditorOption {
|
||||
automaticLayout = 9,
|
||||
autoSurround = 10,
|
||||
codeLens = 11,
|
||||
colorDecorators = 12,
|
||||
columnSelection = 13,
|
||||
comments = 14,
|
||||
contextmenu = 15,
|
||||
copyWithSyntaxHighlighting = 16,
|
||||
cursorBlinking = 17,
|
||||
cursorSmoothCaretAnimation = 18,
|
||||
cursorStyle = 19,
|
||||
cursorSurroundingLines = 20,
|
||||
cursorSurroundingLinesStyle = 21,
|
||||
cursorWidth = 22,
|
||||
disableLayerHinting = 23,
|
||||
disableMonospaceOptimizations = 24,
|
||||
dragAndDrop = 25,
|
||||
emptySelectionClipboard = 26,
|
||||
extraEditorClassName = 27,
|
||||
fastScrollSensitivity = 28,
|
||||
find = 29,
|
||||
fixedOverflowWidgets = 30,
|
||||
folding = 31,
|
||||
foldingStrategy = 32,
|
||||
foldingHighlight = 33,
|
||||
unfoldOnClickAfterEndOfLine = 34,
|
||||
fontFamily = 35,
|
||||
fontInfo = 36,
|
||||
fontLigatures = 37,
|
||||
fontSize = 38,
|
||||
fontWeight = 39,
|
||||
formatOnPaste = 40,
|
||||
formatOnType = 41,
|
||||
glyphMargin = 42,
|
||||
gotoLocation = 43,
|
||||
hideCursorInOverviewRuler = 44,
|
||||
highlightActiveIndentGuide = 45,
|
||||
hover = 46,
|
||||
inDiffEditor = 47,
|
||||
letterSpacing = 48,
|
||||
lightbulb = 49,
|
||||
lineDecorationsWidth = 50,
|
||||
lineHeight = 51,
|
||||
lineNumbers = 52,
|
||||
lineNumbersMinChars = 53,
|
||||
links = 54,
|
||||
matchBrackets = 55,
|
||||
minimap = 56,
|
||||
mouseStyle = 57,
|
||||
mouseWheelScrollSensitivity = 58,
|
||||
mouseWheelZoom = 59,
|
||||
multiCursorMergeOverlapping = 60,
|
||||
multiCursorModifier = 61,
|
||||
multiCursorPaste = 62,
|
||||
occurrencesHighlight = 63,
|
||||
overviewRulerBorder = 64,
|
||||
overviewRulerLanes = 65,
|
||||
padding = 66,
|
||||
parameterHints = 67,
|
||||
peekWidgetDefaultFocus = 68,
|
||||
definitionLinkOpensInPeek = 69,
|
||||
quickSuggestions = 70,
|
||||
quickSuggestionsDelay = 71,
|
||||
readOnly = 72,
|
||||
renameOnType = 73,
|
||||
renderControlCharacters = 74,
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderLineHighlightOnlyWhenFocus = 78,
|
||||
renderValidationDecorations = 79,
|
||||
renderWhitespace = 80,
|
||||
revealHorizontalRightPadding = 81,
|
||||
roundedSelection = 82,
|
||||
rulers = 83,
|
||||
scrollbar = 84,
|
||||
scrollBeyondLastColumn = 85,
|
||||
scrollBeyondLastLine = 86,
|
||||
scrollPredominantAxis = 87,
|
||||
selectionClipboard = 88,
|
||||
selectionHighlight = 89,
|
||||
selectOnLineNumbers = 90,
|
||||
showFoldingControls = 91,
|
||||
showUnused = 92,
|
||||
snippetSuggestions = 93,
|
||||
smoothScrolling = 94,
|
||||
stopRenderingLineAfter = 95,
|
||||
suggest = 96,
|
||||
suggestFontSize = 97,
|
||||
suggestLineHeight = 98,
|
||||
suggestOnTriggerCharacters = 99,
|
||||
suggestSelection = 100,
|
||||
tabCompletion = 101,
|
||||
tabIndex = 102,
|
||||
unusualLineTerminators = 103,
|
||||
useTabStops = 104,
|
||||
wordSeparators = 105,
|
||||
wordWrap = 106,
|
||||
wordWrapBreakAfterCharacters = 107,
|
||||
wordWrapBreakBeforeCharacters = 108,
|
||||
wordWrapColumn = 109,
|
||||
wordWrapMinified = 110,
|
||||
wrappingIndent = 111,
|
||||
wrappingStrategy = 112,
|
||||
showDeprecated = 113,
|
||||
editorClassName = 114,
|
||||
pixelRatio = 115,
|
||||
tabFocusMode = 116,
|
||||
layoutInfo = 117,
|
||||
wrappingInfo = 118
|
||||
codeLensFontFamily = 12,
|
||||
codeLensFontSize = 13,
|
||||
colorDecorators = 14,
|
||||
columnSelection = 15,
|
||||
comments = 16,
|
||||
contextmenu = 17,
|
||||
copyWithSyntaxHighlighting = 18,
|
||||
cursorBlinking = 19,
|
||||
cursorSmoothCaretAnimation = 20,
|
||||
cursorStyle = 21,
|
||||
cursorSurroundingLines = 22,
|
||||
cursorSurroundingLinesStyle = 23,
|
||||
cursorWidth = 24,
|
||||
disableLayerHinting = 25,
|
||||
disableMonospaceOptimizations = 26,
|
||||
dragAndDrop = 27,
|
||||
emptySelectionClipboard = 28,
|
||||
extraEditorClassName = 29,
|
||||
fastScrollSensitivity = 30,
|
||||
find = 31,
|
||||
fixedOverflowWidgets = 32,
|
||||
folding = 33,
|
||||
foldingStrategy = 34,
|
||||
foldingHighlight = 35,
|
||||
unfoldOnClickAfterEndOfLine = 36,
|
||||
fontFamily = 37,
|
||||
fontInfo = 38,
|
||||
fontLigatures = 39,
|
||||
fontSize = 40,
|
||||
fontWeight = 41,
|
||||
formatOnPaste = 42,
|
||||
formatOnType = 43,
|
||||
glyphMargin = 44,
|
||||
gotoLocation = 45,
|
||||
hideCursorInOverviewRuler = 46,
|
||||
highlightActiveIndentGuide = 47,
|
||||
hover = 48,
|
||||
inDiffEditor = 49,
|
||||
letterSpacing = 50,
|
||||
lightbulb = 51,
|
||||
lineDecorationsWidth = 52,
|
||||
lineHeight = 53,
|
||||
lineNumbers = 54,
|
||||
lineNumbersMinChars = 55,
|
||||
linkedEditing = 56,
|
||||
links = 57,
|
||||
matchBrackets = 58,
|
||||
minimap = 59,
|
||||
mouseStyle = 60,
|
||||
mouseWheelScrollSensitivity = 61,
|
||||
mouseWheelZoom = 62,
|
||||
multiCursorMergeOverlapping = 63,
|
||||
multiCursorModifier = 64,
|
||||
multiCursorPaste = 65,
|
||||
occurrencesHighlight = 66,
|
||||
overviewRulerBorder = 67,
|
||||
overviewRulerLanes = 68,
|
||||
padding = 69,
|
||||
parameterHints = 70,
|
||||
peekWidgetDefaultFocus = 71,
|
||||
definitionLinkOpensInPeek = 72,
|
||||
quickSuggestions = 73,
|
||||
quickSuggestionsDelay = 74,
|
||||
readOnly = 75,
|
||||
renameOnType = 76,
|
||||
renderControlCharacters = 77,
|
||||
renderIndentGuides = 78,
|
||||
renderFinalNewline = 79,
|
||||
renderLineHighlight = 80,
|
||||
renderLineHighlightOnlyWhenFocus = 81,
|
||||
renderValidationDecorations = 82,
|
||||
renderWhitespace = 83,
|
||||
revealHorizontalRightPadding = 84,
|
||||
roundedSelection = 85,
|
||||
rulers = 86,
|
||||
scrollbar = 87,
|
||||
scrollBeyondLastColumn = 88,
|
||||
scrollBeyondLastLine = 89,
|
||||
scrollPredominantAxis = 90,
|
||||
selectionClipboard = 91,
|
||||
selectionHighlight = 92,
|
||||
selectOnLineNumbers = 93,
|
||||
showFoldingControls = 94,
|
||||
showUnused = 95,
|
||||
snippetSuggestions = 96,
|
||||
smartSelect = 97,
|
||||
smoothScrolling = 98,
|
||||
stickyTabStops = 99,
|
||||
stopRenderingLineAfter = 100,
|
||||
suggest = 101,
|
||||
suggestFontSize = 102,
|
||||
suggestLineHeight = 103,
|
||||
suggestOnTriggerCharacters = 104,
|
||||
suggestSelection = 105,
|
||||
tabCompletion = 106,
|
||||
tabIndex = 107,
|
||||
unusualLineTerminators = 108,
|
||||
useTabStops = 109,
|
||||
wordSeparators = 110,
|
||||
wordWrap = 111,
|
||||
wordWrapBreakAfterCharacters = 112,
|
||||
wordWrapBreakBeforeCharacters = 113,
|
||||
wordWrapColumn = 114,
|
||||
wordWrapOverride1 = 115,
|
||||
wordWrapOverride2 = 116,
|
||||
wrappingIndent = 117,
|
||||
wrappingStrategy = 118,
|
||||
showDeprecated = 119,
|
||||
editorClassName = 120,
|
||||
pixelRatio = 121,
|
||||
tabFocusMode = 122,
|
||||
layoutInfo = 123,
|
||||
wrappingInfo = 124
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,8 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
|
||||
export const enum ViewEventType {
|
||||
ViewCompositionStart,
|
||||
ViewCompositionEnd,
|
||||
ViewConfigurationChanged,
|
||||
ViewCursorStateChanged,
|
||||
ViewDecorationsChanged,
|
||||
@@ -29,6 +31,16 @@ export const enum ViewEventType {
|
||||
ViewZonesChanged,
|
||||
}
|
||||
|
||||
export class ViewCompositionStartEvent {
|
||||
public readonly type = ViewEventType.ViewCompositionStart;
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
export class ViewCompositionEndEvent {
|
||||
public readonly type = ViewEventType.ViewCompositionEnd;
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
export class ViewConfigurationChangedEvent {
|
||||
|
||||
public readonly type = ViewEventType.ViewConfigurationChanged;
|
||||
@@ -285,7 +297,9 @@ export class ViewZonesChangedEvent {
|
||||
}
|
||||
|
||||
export type ViewEvent = (
|
||||
ViewConfigurationChangedEvent
|
||||
ViewCompositionStartEvent
|
||||
| ViewCompositionEndEvent
|
||||
| ViewConfigurationChangedEvent
|
||||
| ViewCursorStateChangedEvent
|
||||
| ViewDecorationsChangedEvent
|
||||
| ViewFlushedEvent
|
||||
|
||||
@@ -42,6 +42,24 @@ export class LineDecoration {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static extractWrapped(arr: LineDecoration[], startOffset: number, endOffset: number): LineDecoration[] {
|
||||
if (arr.length === 0) {
|
||||
return arr;
|
||||
}
|
||||
const startColumn = startOffset + 1;
|
||||
const endColumn = endOffset + 1;
|
||||
const lineLength = endOffset - startOffset;
|
||||
const r = [];
|
||||
let rLength = 0;
|
||||
for (const dec of arr) {
|
||||
if (dec.endColumn <= startColumn || dec.startColumn >= endColumn) {
|
||||
continue;
|
||||
}
|
||||
r[rLength++] = new LineDecoration(Math.max(1, dec.startColumn - startColumn + 1), Math.min(lineLength + 1, dec.endColumn - startColumn + 1), dec.className, dec.type);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public static filter(lineDecorations: InlineDecoration[], lineNumber: number, minLineColumn: number, maxLineColumn: number): LineDecoration[] {
|
||||
if (lineDecorations.length === 0) {
|
||||
return [];
|
||||
|
||||
@@ -546,6 +546,23 @@ export class LinesLayout {
|
||||
return verticalOffset > totalHeight;
|
||||
}
|
||||
|
||||
public isInTopPadding(verticalOffset: number): boolean {
|
||||
if (this._paddingTop === 0) {
|
||||
return false;
|
||||
}
|
||||
this._checkPendingChanges();
|
||||
return (verticalOffset < this._paddingTop);
|
||||
}
|
||||
|
||||
public isInBottomPadding(verticalOffset: number): boolean {
|
||||
if (this._paddingBottom === 0) {
|
||||
return false;
|
||||
}
|
||||
this._checkPendingChanges();
|
||||
const totalHeight = this.getLinesTotalHeight();
|
||||
return (verticalOffset >= totalHeight - this._paddingBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first line number that is at or after vertical offset `verticalOffset`.
|
||||
* i.e. if getVerticalOffsetForLine(line) is x and getVerticalOffsetForLine(line + 1) is y, then
|
||||
|
||||
@@ -364,6 +364,13 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
public isAfterLines(verticalOffset: number): boolean {
|
||||
return this._linesLayout.isAfterLines(verticalOffset);
|
||||
}
|
||||
public isInTopPadding(verticalOffset: number): boolean {
|
||||
return this._linesLayout.isInTopPadding(verticalOffset);
|
||||
}
|
||||
isInBottomPadding(verticalOffset: number): boolean {
|
||||
return this._linesLayout.isInBottomPadding(verticalOffset);
|
||||
}
|
||||
|
||||
public getLineNumberAtVerticalOffset(verticalOffset: number): number {
|
||||
return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ export class RenderLineInput {
|
||||
this.containsRTL = containsRTL;
|
||||
this.fauxIndentLength = fauxIndentLength;
|
||||
this.lineTokens = lineTokens;
|
||||
this.lineDecorations = lineDecorations;
|
||||
this.lineDecorations = lineDecorations.sort(LineDecoration.compare);
|
||||
this.tabSize = tabSize;
|
||||
this.startVisibleColumn = startVisibleColumn;
|
||||
this.spaceWidth = spaceWidth;
|
||||
|
||||
@@ -7,8 +7,9 @@ import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
|
||||
import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { ILineBreaksComputer, LineBreakData } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
const enum CharacterClass {
|
||||
NONE = 0,
|
||||
|
||||
@@ -12,69 +12,11 @@ import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDe
|
||||
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, LineBreakData, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
|
||||
export class OutputPosition {
|
||||
outputLineIndex: number;
|
||||
outputOffset: number;
|
||||
|
||||
constructor(outputLineIndex: number, outputOffset: number) {
|
||||
this.outputLineIndex = outputLineIndex;
|
||||
this.outputOffset = outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineBreakData {
|
||||
constructor(
|
||||
public breakOffsets: number[],
|
||||
public breakOffsetsVisibleColumn: number[],
|
||||
public wrappedTextIndentLength: number
|
||||
) { }
|
||||
|
||||
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
|
||||
if (outputLineIndex === 0) {
|
||||
return outputOffset;
|
||||
} else {
|
||||
return breakOffsets[outputLineIndex - 1] + outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition {
|
||||
let low = 0;
|
||||
let high = breakOffsets.length - 1;
|
||||
let mid = 0;
|
||||
let midStart = 0;
|
||||
|
||||
while (low <= high) {
|
||||
mid = low + ((high - low) / 2) | 0;
|
||||
|
||||
const midStop = breakOffsets[mid];
|
||||
midStart = mid > 0 ? breakOffsets[mid - 1] : 0;
|
||||
|
||||
if (inputOffset < midStart) {
|
||||
high = mid - 1;
|
||||
} else if (inputOffset >= midStop) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new OutputPosition(mid, inputOffset - midStart);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ILineBreaksComputer {
|
||||
/**
|
||||
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
|
||||
*/
|
||||
addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void;
|
||||
finalize(): (LineBreakData | null)[];
|
||||
}
|
||||
|
||||
export interface ILineBreaksComputerFactory {
|
||||
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
|
||||
}
|
||||
@@ -174,6 +116,10 @@ export class CoordinatesConverter implements ICoordinatesConverter {
|
||||
public modelPositionIsVisible(modelPosition: Position): boolean {
|
||||
return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column);
|
||||
}
|
||||
|
||||
public getModelLineViewLineCount(modelLineNumber: number): number {
|
||||
return this._lines.getModelLineViewLineCount(modelLineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
const enum IndentGuideRepeatOption {
|
||||
@@ -473,6 +419,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
return this.lines[modelLineNumber - 1].isVisible();
|
||||
}
|
||||
|
||||
public getModelLineViewLineCount(modelLineNumber: number): number {
|
||||
if (modelLineNumber < 1 || modelLineNumber > this.lines.length) {
|
||||
// invalid arguments
|
||||
return 1;
|
||||
}
|
||||
return this.lines[modelLineNumber - 1].getViewLineCount();
|
||||
}
|
||||
|
||||
public setTabSize(newTabSize: number): boolean {
|
||||
if (this.tabSize === newTabSize) {
|
||||
return false;
|
||||
@@ -1431,6 +1385,9 @@ export class IdentityCoordinatesConverter implements ICoordinatesConverter {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getModelLineViewLineCount(modelLineNumber: number): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
export class IdentityLinesCollection implements IViewModelLinesCollection {
|
||||
|
||||
@@ -33,10 +33,15 @@ export class ViewEventHandler extends Disposable {
|
||||
|
||||
// --- begin event handlers
|
||||
|
||||
public onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onCompositionEnd(e: viewEvents.ViewCompositionEndEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
@@ -94,6 +99,18 @@ export class ViewEventHandler extends Disposable {
|
||||
|
||||
switch (e.type) {
|
||||
|
||||
case viewEvents.ViewEventType.ViewCompositionStart:
|
||||
if (this.onCompositionStart(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewCompositionEnd:
|
||||
if (this.onCompositionEnd(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewConfigurationChanged:
|
||||
if (this.onConfigurationChanged(e)) {
|
||||
shouldRender = true;
|
||||
|
||||
@@ -61,6 +61,8 @@ export interface IViewLayout {
|
||||
getWhitespaces(): IEditorWhitespace[];
|
||||
|
||||
isAfterLines(verticalOffset: number): boolean;
|
||||
isInTopPadding(verticalOffset: number): boolean;
|
||||
isInBottomPadding(verticalOffset: number): boolean;
|
||||
getLineNumberAtVerticalOffset(verticalOffset: number): number;
|
||||
getVerticalOffsetForLineNumber(lineNumber: number): number;
|
||||
getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null;
|
||||
@@ -82,6 +84,65 @@ export interface ICoordinatesConverter {
|
||||
convertModelPositionToViewPosition(modelPosition: Position): Position;
|
||||
convertModelRangeToViewRange(modelRange: Range): Range;
|
||||
modelPositionIsVisible(modelPosition: Position): boolean;
|
||||
getModelLineViewLineCount(modelLineNumber: number): number;
|
||||
}
|
||||
|
||||
export class OutputPosition {
|
||||
outputLineIndex: number;
|
||||
outputOffset: number;
|
||||
|
||||
constructor(outputLineIndex: number, outputOffset: number) {
|
||||
this.outputLineIndex = outputLineIndex;
|
||||
this.outputOffset = outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineBreakData {
|
||||
constructor(
|
||||
public breakOffsets: number[],
|
||||
public breakOffsetsVisibleColumn: number[],
|
||||
public wrappedTextIndentLength: number
|
||||
) { }
|
||||
|
||||
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
|
||||
if (outputLineIndex === 0) {
|
||||
return outputOffset;
|
||||
} else {
|
||||
return breakOffsets[outputLineIndex - 1] + outputOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition {
|
||||
let low = 0;
|
||||
let high = breakOffsets.length - 1;
|
||||
let mid = 0;
|
||||
let midStart = 0;
|
||||
|
||||
while (low <= high) {
|
||||
mid = low + ((high - low) / 2) | 0;
|
||||
|
||||
const midStop = breakOffsets[mid];
|
||||
midStart = mid > 0 ? breakOffsets[mid - 1] : 0;
|
||||
|
||||
if (inputOffset < midStart) {
|
||||
high = mid - 1;
|
||||
} else if (inputOffset >= midStop) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new OutputPosition(mid, inputOffset - midStart);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ILineBreaksComputer {
|
||||
/**
|
||||
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
|
||||
*/
|
||||
addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void;
|
||||
finalize(): (LineBreakData | null)[];
|
||||
}
|
||||
|
||||
export interface IViewModel extends ICursorSimpleModel {
|
||||
@@ -103,6 +164,8 @@ export interface IViewModel extends ICursorSimpleModel {
|
||||
setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void;
|
||||
tokenizeViewport(): void;
|
||||
setHasFocus(hasFocus: boolean): void;
|
||||
onCompositionStart(): void;
|
||||
onCompositionEnd(): void;
|
||||
onDidColorThemeChange(): void;
|
||||
|
||||
getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[];
|
||||
@@ -142,6 +205,7 @@ export interface IViewModel extends ICursorSimpleModel {
|
||||
|
||||
//#endregion
|
||||
|
||||
createLineBreaksComputer(): ILineBreaksComputer;
|
||||
|
||||
//#region cursor
|
||||
getPrimaryCursorState(): CursorState;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTok
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
|
||||
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ICoordinatesConverter, ILineBreaksComputer, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -153,6 +153,10 @@ export class ViewModel extends Disposable implements IViewModel {
|
||||
this._eventDispatcher.dispose();
|
||||
}
|
||||
|
||||
public createLineBreaksComputer(): ILineBreaksComputer {
|
||||
return this._lines.createLineBreaksComputer();
|
||||
}
|
||||
|
||||
public addViewEventHandler(eventHandler: ViewEventHandler): void {
|
||||
this._eventDispatcher.addViewEventHandler(eventHandler);
|
||||
}
|
||||
@@ -179,6 +183,14 @@ export class ViewModel extends Disposable implements IViewModel {
|
||||
this._eventDispatcher.emitOutgoingEvent(new FocusChangedEvent(!hasFocus, hasFocus));
|
||||
}
|
||||
|
||||
public onCompositionStart(): void {
|
||||
this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionStartEvent());
|
||||
}
|
||||
|
||||
public onCompositionEnd(): void {
|
||||
this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent());
|
||||
}
|
||||
|
||||
public onDidColorThemeChange(): void {
|
||||
this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewThemeChangedEvent());
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } fr
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -17,6 +16,7 @@ import * as modes from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types';
|
||||
import { IProgress, Progress } from 'vs/platform/progress/common/progress';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
export const codeActionCommandId = 'editor.action.codeAction';
|
||||
export const refactorCommandId = 'editor.action.refactor';
|
||||
@@ -223,8 +223,8 @@ function getDocumentation(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise<ReadonlyArray<modes.CodeAction>> {
|
||||
const { resource, rangeOrSelection, kind, itemResolveCount } = args;
|
||||
CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (accessor, ...args): Promise<ReadonlyArray<modes.CodeAction>> {
|
||||
const [resource, rangeOrSelection, kind, itemResolveCount] = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
@@ -35,10 +35,14 @@ class CodeActionAction extends Action {
|
||||
public readonly action: CodeAction,
|
||||
callback: () => Promise<void>,
|
||||
) {
|
||||
super(action.command ? action.command.id : action.title, action.title, undefined, !action.disabled, callback);
|
||||
super(action.command ? action.command.id : action.title, stripNewlines(action.title), undefined, !action.disabled, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function stripNewlines(str: string): string {
|
||||
return str.replace(/\r\n|\r|\n/g, ' ');
|
||||
}
|
||||
|
||||
export interface CodeActionShowOptions {
|
||||
readonly includeDisabledActions: boolean;
|
||||
}
|
||||
@@ -224,3 +228,5 @@ export class CodeActionKeybindingResolver {
|
||||
}, undefined as ResolveCodeActionKeybinding | undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr
|
||||
commandArgs,
|
||||
undefined,
|
||||
false,
|
||||
null);
|
||||
null,
|
||||
false);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { CodeLensModel } from 'vs/editor/contrib/codelens/codelens';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes';
|
||||
import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { runWhenIdle } from 'vs/base/common/async';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
@@ -62,7 +62,7 @@ export class CodeLensCache implements ICodeLensCache {
|
||||
// store lens data on shutdown
|
||||
once(storageService.onWillSaveState)(e => {
|
||||
if (e.reason === WillSaveStateReason.SHUTDOWN) {
|
||||
storageService.store(key, this._serialize(), StorageScope.WORKSPACE);
|
||||
storageService.store(key, this._serialize(), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import { mergeSort } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { CodeLensProvider, CodeLensProviderRegistry, CodeLens, CodeLensList } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
|
||||
export interface CodeLensItem {
|
||||
symbol: CodeLens;
|
||||
@@ -79,14 +80,12 @@ export async function getCodeLensModel(model: ITextModel, token: CancellationTok
|
||||
return result;
|
||||
}
|
||||
|
||||
registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) {
|
||||
CommandsRegistry.registerCommand('_executeCodeLensProvider', function (accessor, ...args: [URI, number | undefined | null]) {
|
||||
let [uri, itemResolveCount] = args;
|
||||
assertType(URI.isUri(uri));
|
||||
assertType(typeof itemResolveCount === 'number' || !itemResolveCount);
|
||||
|
||||
let { resource, itemResolveCount } = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
const model = accessor.get(IModelService).getModel(uri);
|
||||
if (!model) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
@@ -99,7 +98,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) {
|
||||
let resolve: Promise<any>[] = [];
|
||||
|
||||
for (const item of value.lenses) {
|
||||
if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) {
|
||||
if (itemResolveCount === undefined || itemResolveCount === null || Boolean(item.symbol.command)) {
|
||||
result.push(item.symbol);
|
||||
} else if (itemResolveCount-- > 0 && item.provider.resolveCodeLens) {
|
||||
resolve.push(Promise.resolve(item.provider.resolveCodeLens(model, item.symbol, CancellationToken.None)).then(symbol => result.push(symbol || item.symbol)));
|
||||
|
||||
@@ -53,7 +53,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange()));
|
||||
this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange()));
|
||||
this._disposables.add(this._editor.onDidChangeConfiguration((e) => {
|
||||
if (e.hasChanged(EditorOption.fontInfo)) {
|
||||
if (e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.codeLensFontSize) || e.hasChanged(EditorOption.codeLensFontFamily)) {
|
||||
this._updateLensStyle();
|
||||
}
|
||||
if (e.hasChanged(EditorOption.codeLens)) {
|
||||
@@ -77,21 +77,42 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
this._disposables.dispose();
|
||||
this._oldCodeLensModels.dispose();
|
||||
this._currentCodeLensModel?.dispose();
|
||||
this._styleElement.remove();
|
||||
}
|
||||
|
||||
private _getLayoutInfo() {
|
||||
let fontSize = this._editor.getOption(EditorOption.codeLensFontSize);
|
||||
let codeLensHeight: number;
|
||||
if (!fontSize || fontSize < 5) {
|
||||
fontSize = (this._editor.getOption(EditorOption.fontSize) * .9) | 0;
|
||||
codeLensHeight = this._editor.getOption(EditorOption.lineHeight);
|
||||
} else {
|
||||
codeLensHeight = (fontSize * Math.max(1.3, this._editor.getOption(EditorOption.lineHeight) / this._editor.getOption(EditorOption.fontSize))) | 0;
|
||||
}
|
||||
return { codeLensHeight, fontSize };
|
||||
}
|
||||
|
||||
private _updateLensStyle(): void {
|
||||
const options = this._editor.getOptions();
|
||||
const fontInfo = options.get(EditorOption.fontInfo);
|
||||
const lineHeight = options.get(EditorOption.lineHeight);
|
||||
|
||||
const { codeLensHeight, fontSize } = this._getLayoutInfo();
|
||||
const fontFamily = this._editor.getOption(EditorOption.codeLensFontFamily);
|
||||
const editorFontInfo = this._editor.getOption(EditorOption.fontInfo);
|
||||
|
||||
const height = Math.round(lineHeight * 1.1);
|
||||
const fontSize = Math.round(fontInfo.fontSize * 0.9);
|
||||
const newStyle = `
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} { height: ${height}px; line-height: ${lineHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontInfo.fontSize * 0.45)}px;}
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} > a > .codicon { line-height: ${lineHeight}px; font-size: ${fontSize}px; }
|
||||
let newStyle = `
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; padding-right: ${Math.round(fontSize * 0.5)}px; font-feature-settings: ${editorFontInfo.fontFeatureSettings} }
|
||||
.monaco-editor .codelens-decoration.${this._styleClassName} span.codicon { line-height: ${codeLensHeight}px; font-size: ${fontSize}px; }
|
||||
`;
|
||||
if (fontFamily) {
|
||||
newStyle += `.monaco-editor .codelens-decoration.${this._styleClassName} { font-family: '${fontFamily}'}`;
|
||||
}
|
||||
this._styleElement.textContent = newStyle;
|
||||
|
||||
//
|
||||
this._editor.changeViewZones(accessor => {
|
||||
for (let lens of this._lenses) {
|
||||
lens.updateHeight(codeLensHeight, accessor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _localDispose(): void {
|
||||
@@ -165,7 +186,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
|
||||
// render lenses
|
||||
this._renderCodeLensSymbols(result);
|
||||
this._resolveCodeLensesInViewportSoon();
|
||||
this._resolveCodeLensesInViewport();
|
||||
}, onUnexpectedError);
|
||||
|
||||
}, this._getCodeLensModelDelays.get(model));
|
||||
@@ -199,11 +220,12 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
});
|
||||
});
|
||||
|
||||
// Compute new `visible` code lenses
|
||||
this._resolveCodeLensesInViewportSoon();
|
||||
// Ask for all references again
|
||||
scheduler.schedule();
|
||||
}));
|
||||
this._localToDispose.add(this._editor.onDidFocusEditorWidget(() => {
|
||||
scheduler.schedule();
|
||||
}));
|
||||
this._localToDispose.add(this._editor.onDidScrollChange(e => {
|
||||
if (e.scrollTopChanged && this._lenses.length > 0) {
|
||||
this._resolveCodeLensesInViewportSoon();
|
||||
@@ -283,6 +305,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
}
|
||||
|
||||
const scrollState = StableEditorScrollState.capture(this._editor);
|
||||
const layoutInfo = this._getLayoutInfo();
|
||||
|
||||
this._editor.changeDecorations(decorationsAccessor => {
|
||||
this._editor.changeViewZones(viewZoneAccessor => {
|
||||
@@ -304,7 +327,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
groupsIndex++;
|
||||
codeLensIndex++;
|
||||
} else {
|
||||
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon()));
|
||||
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon()));
|
||||
codeLensIndex++;
|
||||
groupsIndex++;
|
||||
}
|
||||
@@ -318,7 +341,7 @@ export class CodeLensContribution implements IEditorContribution {
|
||||
|
||||
// Create extra symbols
|
||||
while (groupsIndex < groups.length) {
|
||||
this._lenses.push(new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon()));
|
||||
this._lenses.push(new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, layoutInfo.codeLensHeight, () => this._resolveCodeLensesInViewportSoon()));
|
||||
groupsIndex++;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,20 +18,20 @@ import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
|
||||
class CodeLensViewZone implements IViewZone {
|
||||
|
||||
readonly heightInLines: number;
|
||||
readonly suppressMouseDown: boolean;
|
||||
readonly domNode: HTMLElement;
|
||||
|
||||
afterLineNumber: number;
|
||||
heightInPx: number;
|
||||
|
||||
private _lastHeight?: number;
|
||||
private readonly _onHeight: Function;
|
||||
private readonly _onHeight: () => void;
|
||||
|
||||
constructor(afterLineNumber: number, onHeight: Function) {
|
||||
constructor(afterLineNumber: number, heightInPx: number, onHeight: () => void) {
|
||||
this.afterLineNumber = afterLineNumber;
|
||||
this._onHeight = onHeight;
|
||||
this.heightInPx = heightInPx;
|
||||
|
||||
this.heightInLines = 1;
|
||||
this._onHeight = onHeight;
|
||||
this.suppressMouseDown = true;
|
||||
this.domNode = document.createElement('div');
|
||||
}
|
||||
@@ -179,8 +179,8 @@ export class CodeLensWidget {
|
||||
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private readonly _className: string;
|
||||
private readonly _viewZone!: CodeLensViewZone;
|
||||
private readonly _viewZoneId!: string;
|
||||
private readonly _viewZone: CodeLensViewZone;
|
||||
private readonly _viewZoneId: string;
|
||||
|
||||
private _contentWidget?: CodeLensContentWidget;
|
||||
private _decorationIds: string[];
|
||||
@@ -193,7 +193,8 @@ export class CodeLensWidget {
|
||||
className: string,
|
||||
helper: CodeLensHelper,
|
||||
viewZoneChangeAccessor: IViewZoneChangeAccessor,
|
||||
updateCallback: Function
|
||||
heightInPx: number,
|
||||
updateCallback: () => void
|
||||
) {
|
||||
this._editor = editor;
|
||||
this._className = className;
|
||||
@@ -224,7 +225,7 @@ export class CodeLensWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, updateCallback);
|
||||
this._viewZone = new CodeLensViewZone(range!.startLineNumber - 1, heightInPx, updateCallback);
|
||||
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
|
||||
|
||||
if (lenses.length > 0) {
|
||||
@@ -236,7 +237,9 @@ export class CodeLensWidget {
|
||||
private _createContentWidgetIfNecessary(): void {
|
||||
if (!this._contentWidget) {
|
||||
this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1);
|
||||
this._editor.addContentWidget(this._contentWidget!);
|
||||
this._editor.addContentWidget(this._contentWidget);
|
||||
} else {
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +280,14 @@ export class CodeLensWidget {
|
||||
});
|
||||
}
|
||||
|
||||
updateHeight(height: number, viewZoneChangeAccessor: IViewZoneChangeAccessor): void {
|
||||
this._viewZone.heightInPx = height;
|
||||
viewZoneChangeAccessor.layoutZone(this._viewZoneId);
|
||||
if (this._contentWidget) {
|
||||
this._editor.layoutContentWidget(this._contentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
computeIfNecessary(model: ITextModel): CodeLensItem[] | null {
|
||||
if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) {
|
||||
return null;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ColorProviderRegistry, DocumentColorProvider, IColorInformation, IColorPresentation } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
|
||||
export interface IColorData {
|
||||
@@ -36,9 +36,9 @@ export function getColorPresentations(model: ITextModel, colorInfo: IColorInform
|
||||
return Promise.resolve(provider.provideColorPresentations(model, colorInfo, token));
|
||||
}
|
||||
|
||||
registerLanguageCommand('_executeDocumentColorProvider', function (accessor, args) {
|
||||
CommandsRegistry.registerCommand('_executeDocumentColorProvider', function (accessor, ...args) {
|
||||
|
||||
const { resource } = args;
|
||||
const [resource] = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
@@ -62,15 +62,16 @@ registerLanguageCommand('_executeDocumentColorProvider', function (accessor, arg
|
||||
});
|
||||
|
||||
|
||||
registerLanguageCommand('_executeColorPresentationProvider', function (accessor, args) {
|
||||
CommandsRegistry.registerCommand('_executeColorPresentationProvider', function (accessor, ...args) {
|
||||
|
||||
const { resource, color, range } = args;
|
||||
if (!(resource instanceof URI) || !Array.isArray(color) || color.length !== 4 || !Range.isIRange(range)) {
|
||||
const [color, context] = args;
|
||||
const { uri, range } = context;
|
||||
if (!(uri instanceof URI) || !Array.isArray(color) || color.length !== 4 || !Range.isIRange(range)) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
const [red, green, blue, alpha] = color;
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
const model = accessor.get(IModelService).getModel(uri);
|
||||
if (!model) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// import color detector contribution
|
||||
import 'vs/editor/contrib/colorPicker/colorDetector';
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ModesHoverController } from 'vs/editor/contrib/hover/hover';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation';
|
||||
|
||||
export class ColorContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID: string = 'editor.contrib.colorContribution';
|
||||
|
||||
static readonly RECOMPUTE_TIME = 1000; // ms
|
||||
|
||||
constructor(private readonly _editor: ICodeEditor,
|
||||
) {
|
||||
super();
|
||||
this._register(_editor.onMouseDown((e) => this.onMouseDown(e)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private onMouseDown(mouseEvent: IEditorMouseEvent) {
|
||||
const targetType = mouseEvent.target.type;
|
||||
|
||||
if (targetType !== MouseTargetType.CONTENT_TEXT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox'));
|
||||
if (!hoverOnColorDecorator) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mouseEvent.target.range) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hoverController = this._editor.getContribution<ModesHoverController>(ModesHoverController.ID);
|
||||
if (!hoverController.contentWidget.isColorPickerVisible()) {
|
||||
const range = new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1);
|
||||
hoverController.showContentHover(range, HoverStartMode.Delayed, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(ColorContribution.ID, ColorContribution);
|
||||
@@ -46,13 +46,13 @@ export class ColorDetector extends Disposable implements IEditorContribution {
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this._register(_editor.onDidChangeModel((e) => {
|
||||
this._register(_editor.onDidChangeModel(() => {
|
||||
this._isEnabled = this.isEnabled();
|
||||
this.onModelChanged();
|
||||
}));
|
||||
this._register(_editor.onDidChangeModelLanguage((e) => this.onModelChanged()));
|
||||
this._register(ColorProviderRegistry.onDidChange((e) => this.onModelChanged()));
|
||||
this._register(_editor.onDidChangeConfiguration((e) => {
|
||||
this._register(_editor.onDidChangeModelLanguage(() => this.onModelChanged()));
|
||||
this._register(ColorProviderRegistry.onDidChange(() => this.onModelChanged()));
|
||||
this._register(_editor.onDidChangeConfiguration(() => {
|
||||
let prevIsEnabled = this._isEnabled;
|
||||
this._isEnabled = this.isEnabled();
|
||||
if (prevIsEnabled !== this._isEnabled) {
|
||||
@@ -110,7 +110,7 @@ export class ColorDetector extends Disposable implements IEditorContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
this._localToDispose.add(this._editor.onDidChangeModelContent((e) => {
|
||||
this._localToDispose.add(this._editor.onDidChangeModelContent(() => {
|
||||
if (!this._timeoutTimer) {
|
||||
this._timeoutTimer = new TimeoutTimer();
|
||||
this._timeoutTimer.cancelAndSet(() => {
|
||||
|
||||
@@ -335,7 +335,7 @@ export class ColorPickerWidget extends Widget {
|
||||
|
||||
body: ColorPickerBody;
|
||||
|
||||
constructor(container: Node, private readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) {
|
||||
constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) {
|
||||
super();
|
||||
|
||||
this._register(onDidChangeZoomLevel(() => this.layout()));
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICommand } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand';
|
||||
@@ -31,17 +32,38 @@ abstract class CommentLineAction extends EditorAction {
|
||||
|
||||
const model = editor.getModel();
|
||||
const commands: ICommand[] = [];
|
||||
const selections = editor.getSelections();
|
||||
const modelOptions = model.getOptions();
|
||||
const commentsOptions = editor.getOption(EditorOption.comments);
|
||||
|
||||
const selections = editor.getSelections().map((selection, index) => ({ selection, index, ignoreFirstLine: false }));
|
||||
selections.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection));
|
||||
|
||||
// Remove selections that would result in copying the same line
|
||||
let prev = selections[0];
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const curr = selections[i];
|
||||
if (prev.selection.endLineNumber === curr.selection.startLineNumber) {
|
||||
// these two selections would copy the same line
|
||||
if (prev.index < curr.index) {
|
||||
// prev wins
|
||||
curr.ignoreFirstLine = true;
|
||||
} else {
|
||||
// curr wins
|
||||
prev.ignoreFirstLine = true;
|
||||
prev = curr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const selection of selections) {
|
||||
commands.push(new LineCommentCommand(
|
||||
selection,
|
||||
selection.selection,
|
||||
modelOptions.tabSize,
|
||||
this._type,
|
||||
commentsOptions.insertSpace,
|
||||
commentsOptions.ignoreEmptyLines
|
||||
commentsOptions.ignoreEmptyLines,
|
||||
selection.ignoreFirstLine
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -57,13 +57,15 @@ export class LineCommentCommand implements ICommand {
|
||||
private _selectionId: string | null;
|
||||
private _deltaColumn: number;
|
||||
private _moveEndPositionDown: boolean;
|
||||
private _ignoreFirstLine: boolean;
|
||||
|
||||
constructor(
|
||||
selection: Selection,
|
||||
tabSize: number,
|
||||
type: Type,
|
||||
insertSpace: boolean,
|
||||
ignoreEmptyLines: boolean
|
||||
ignoreEmptyLines: boolean,
|
||||
ignoreFirstLine?: boolean
|
||||
) {
|
||||
this._selection = selection;
|
||||
this._tabSize = tabSize;
|
||||
@@ -73,6 +75,7 @@ export class LineCommentCommand implements ICommand {
|
||||
this._deltaColumn = 0;
|
||||
this._moveEndPositionDown = false;
|
||||
this._ignoreEmptyLines = ignoreEmptyLines;
|
||||
this._ignoreFirstLine = ignoreFirstLine || false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +111,7 @@ export class LineCommentCommand implements ICommand {
|
||||
* Analyze lines and decide which lines are relevant and what the toggle should do.
|
||||
* Also, build up several offsets and lengths useful in the generation of editor operations.
|
||||
*/
|
||||
public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean): IPreflightData {
|
||||
public static _analyzeLines(type: Type, insertSpace: boolean, model: ISimpleModel, lines: ILinePreflightData[], startLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData {
|
||||
let onlyWhitespaceLines = true;
|
||||
|
||||
let shouldRemoveComments: boolean;
|
||||
@@ -124,6 +127,12 @@ export class LineCommentCommand implements ICommand {
|
||||
const lineData = lines[i];
|
||||
const lineNumber = startLineNumber + i;
|
||||
|
||||
if (lineNumber === startLineNumber && ignoreFirstLine) {
|
||||
// first line ignored
|
||||
lineData.ignore = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const lineContentStartOffset = strings.firstNonWhitespaceIndex(lineContent);
|
||||
|
||||
@@ -178,7 +187,7 @@ export class LineCommentCommand implements ICommand {
|
||||
/**
|
||||
* Analyze all lines and decide exactly what to do => not supported | insert line comments | remove line comments
|
||||
*/
|
||||
public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean): IPreflightData {
|
||||
public static _gatherPreflightData(type: Type, insertSpace: boolean, model: ITextModel, startLineNumber: number, endLineNumber: number, ignoreEmptyLines: boolean, ignoreFirstLine: boolean): IPreflightData {
|
||||
const lines = LineCommentCommand._gatherPreflightCommentStrings(model, startLineNumber, endLineNumber);
|
||||
if (lines === null) {
|
||||
return {
|
||||
@@ -186,7 +195,7 @@ export class LineCommentCommand implements ICommand {
|
||||
};
|
||||
}
|
||||
|
||||
return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines);
|
||||
return LineCommentCommand._analyzeLines(type, insertSpace, model, lines, startLineNumber, ignoreEmptyLines, ignoreFirstLine);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,6 +332,12 @@ export class LineCommentCommand implements ICommand {
|
||||
let s = this._selection;
|
||||
this._moveEndPositionDown = false;
|
||||
|
||||
if (s.startLineNumber === s.endLineNumber && this._ignoreFirstLine) {
|
||||
builder.addEditOperation(new Range(s.startLineNumber, model.getLineMaxColumn(s.startLineNumber), s.startLineNumber + 1, 1), s.startLineNumber === model.getLineCount() ? '' : '\n');
|
||||
this._selectionId = builder.trackSelection(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) {
|
||||
this._moveEndPositionDown = true;
|
||||
s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1));
|
||||
@@ -334,7 +349,8 @@ export class LineCommentCommand implements ICommand {
|
||||
model,
|
||||
s.startLineNumber,
|
||||
s.endLineNumber,
|
||||
this._ignoreEmptyLines
|
||||
this._ignoreEmptyLines,
|
||||
this._ignoreFirstLine
|
||||
);
|
||||
|
||||
if (data.supported) {
|
||||
|
||||
@@ -91,7 +91,7 @@ suite('Editor Contrib - Line Comment Command', () => {
|
||||
' ',
|
||||
' c',
|
||||
'\t\td'
|
||||
]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true);
|
||||
]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false);
|
||||
if (!r.supported) {
|
||||
throw new Error(`unexpected`);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ suite('Editor Contrib - Line Comment Command', () => {
|
||||
' rem ',
|
||||
' !@# c',
|
||||
'\t\t!@#d'
|
||||
]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true);
|
||||
]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false);
|
||||
if (!r.supported) {
|
||||
throw new Error(`unexpected`);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu
|
||||
this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
|
||||
this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e)));
|
||||
this._register(this._editor.onMouseDrop((e: IPartialEditorMouseEvent) => this._onEditorMouseDrop(e)));
|
||||
this._register(this._editor.onMouseDropCanceled(() => this._onEditorMouseDropCanceled()));
|
||||
this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e)));
|
||||
this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e)));
|
||||
this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur()));
|
||||
@@ -144,6 +145,16 @@ export class DragAndDropController extends Disposable implements IEditorContribu
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorMouseDropCanceled() {
|
||||
this._editor.updateOptions({
|
||||
mouseStyle: 'text'
|
||||
});
|
||||
|
||||
this._removeDecoration();
|
||||
this._dragSelection = null;
|
||||
this._mouseDown = false;
|
||||
}
|
||||
|
||||
private _onEditorMouseDrop(mouseEvent: IPartialEditorMouseEvent): void {
|
||||
if (mouseEvent.target && (this._hitContent(mouseEvent.target) || this._hitMargin(mouseEvent.target)) && mouseEvent.target.position) {
|
||||
let newCursorPosition = new Position(mouseEvent.target.position.lineNumber, mouseEvent.target.position.column);
|
||||
|
||||
@@ -22,22 +22,23 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
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 { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
const SEARCH_STRING_MAX_LENGTH = 524288;
|
||||
|
||||
export function getSelectionSearchString(editor: ICodeEditor): string | null {
|
||||
export function getSelectionSearchString(editor: ICodeEditor, seedSearchStringFromSelection: 'single' | 'multiple' = 'single'): string | null {
|
||||
if (!editor.hasModel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selection = editor.getSelection();
|
||||
// if selection spans multiple lines, default search string to empty
|
||||
if (selection.startLineNumber === selection.endLineNumber) {
|
||||
|
||||
if ((seedSearchStringFromSelection === 'single' && selection.startLineNumber === selection.endLineNumber)
|
||||
|| seedSearchStringFromSelection === 'multiple') {
|
||||
if (selection.isEmpty()) {
|
||||
const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition());
|
||||
if (wordAtPosition) {
|
||||
@@ -61,7 +62,7 @@ export const enum FindStartFocusAction {
|
||||
|
||||
export interface IFindStartOptions {
|
||||
forceRevealReplace: boolean;
|
||||
seedSearchStringFromSelection: boolean;
|
||||
seedSearchStringFromSelection: 'none' | 'single' | 'multiple';
|
||||
seedSearchStringFromGlobalClipboard: boolean;
|
||||
shouldFocus: FindStartFocusAction;
|
||||
shouldAnimate: boolean;
|
||||
@@ -82,6 +83,10 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
private readonly _clipboardService: IClipboardService;
|
||||
protected readonly _contextKeyService: IContextKeyService;
|
||||
|
||||
get editor() {
|
||||
return this._editor;
|
||||
}
|
||||
|
||||
public static get(editor: ICodeEditor): CommonFindController {
|
||||
return editor.getContribution<CommonFindController>(CommonFindController.ID);
|
||||
}
|
||||
@@ -122,7 +127,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
if (shouldRestartFind) {
|
||||
this._start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false && this._editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
@@ -163,16 +168,16 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
|
||||
private saveQueryState(e: FindReplaceStateChangedEvent) {
|
||||
if (e.isRegex) {
|
||||
this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE);
|
||||
this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
if (e.wholeWord) {
|
||||
this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE);
|
||||
this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
if (e.matchCase) {
|
||||
this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE);
|
||||
this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
if (e.preserveCase) {
|
||||
this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE);
|
||||
this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +267,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
this._state.change({ searchString: searchString }, false);
|
||||
}
|
||||
|
||||
public highlightFindOptions(): void {
|
||||
public highlightFindOptions(ignoreWhenVisible: boolean = false): void {
|
||||
// overwritten in subclass
|
||||
}
|
||||
|
||||
@@ -278,8 +283,8 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
isRevealed: true
|
||||
};
|
||||
|
||||
if (opts.seedSearchStringFromSelection) {
|
||||
let selectionSearchString = getSelectionSearchString(this._editor);
|
||||
if (opts.seedSearchStringFromSelection === 'single') {
|
||||
let selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection);
|
||||
if (selectionSearchString) {
|
||||
if (this._state.isRegex) {
|
||||
stateChanges.searchString = strings.escapeRegExpCharacters(selectionSearchString);
|
||||
@@ -287,6 +292,11 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
stateChanges.searchString = selectionSearchString;
|
||||
}
|
||||
}
|
||||
} else if (opts.seedSearchStringFromSelection === 'multiple' && !opts.updateSearchScope) {
|
||||
let selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection);
|
||||
if (selectionSearchString) {
|
||||
stateChanges.searchString = selectionSearchString;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) {
|
||||
@@ -404,7 +414,6 @@ export class FindController extends CommonFindController implements IFindControl
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IStorageService _storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
) {
|
||||
super(editor, _contextKeyService, _storageService, clipboardService);
|
||||
@@ -449,11 +458,11 @@ export class FindController extends CommonFindController implements IFindControl
|
||||
}
|
||||
}
|
||||
|
||||
public highlightFindOptions(): void {
|
||||
public highlightFindOptions(ignoreWhenVisible: boolean = false): void {
|
||||
if (!this._widget) {
|
||||
this._createFindWidget();
|
||||
}
|
||||
if (this._state.isRevealed) {
|
||||
if (this._state.isRevealed && !ignoreWhenVisible) {
|
||||
this._widget!.highlightFindOptions();
|
||||
} else {
|
||||
this._findOptionsWidget!.highlightFindOptions();
|
||||
@@ -461,9 +470,17 @@ export class FindController extends CommonFindController implements IFindControl
|
||||
}
|
||||
|
||||
private _createFindWidget() {
|
||||
this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService, this._storageKeysSyncRegistryService));
|
||||
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));
|
||||
}
|
||||
|
||||
saveViewState(): any {
|
||||
return this._widget?.getViewState();
|
||||
}
|
||||
|
||||
restoreViewState(state: any): void {
|
||||
this._widget?.setViewState(state);
|
||||
}
|
||||
}
|
||||
|
||||
export class StartFindAction extends MultiEditorAction {
|
||||
@@ -493,7 +510,7 @@ export class StartFindAction extends MultiEditorAction {
|
||||
if (controller) {
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none',
|
||||
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard,
|
||||
shouldFocus: FindStartFocusAction.FocusFindInput,
|
||||
shouldAnimate: true,
|
||||
@@ -523,12 +540,12 @@ export class StartFindWithSelectionAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise<void> {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: true,
|
||||
seedSearchStringFromSelection: 'multiple',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: true,
|
||||
@@ -546,7 +563,7 @@ export abstract class MatchFindAction extends EditorAction {
|
||||
if (controller && !this._run(controller)) {
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none',
|
||||
seedSearchStringFromGlobalClipboard: true,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: true,
|
||||
@@ -578,7 +595,13 @@ export class NextMatchFindAction extends MatchFindAction {
|
||||
}
|
||||
|
||||
protected _run(controller: CommonFindController): boolean {
|
||||
return controller.moveToNextMatch();
|
||||
const result = controller.moveToNextMatch();
|
||||
if (result) {
|
||||
controller.editor.pushUndoStop();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,7 +622,13 @@ export class NextMatchFindAction2 extends MatchFindAction {
|
||||
}
|
||||
|
||||
protected _run(controller: CommonFindController): boolean {
|
||||
return controller.moveToNextMatch();
|
||||
const result = controller.moveToNextMatch();
|
||||
if (result) {
|
||||
controller.editor.pushUndoStop();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +688,7 @@ export abstract class SelectionMatchFindAction extends EditorAction {
|
||||
if (!this._run(controller)) {
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection ? 'single' : 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: true,
|
||||
@@ -765,7 +794,7 @@ export class StartFindReplaceAction extends MultiEditorAction {
|
||||
if (controller) {
|
||||
await controller.start({
|
||||
forceRevealReplace: true,
|
||||
seedSearchStringFromSelection: seedSearchStringFromSelection,
|
||||
seedSearchStringFromSelection: seedSearchStringFromSelection ? 'single' : 'none',
|
||||
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
shouldFocus: shouldFocus,
|
||||
shouldAnimate: true,
|
||||
|
||||
@@ -213,7 +213,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
collector.addRule(`.monaco-editor .findOptionsWidget { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`);
|
||||
}
|
||||
|
||||
const hcBorder = theme.getColor(contrastBorder);
|
||||
|
||||
@@ -31,23 +31,22 @@ import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contri
|
||||
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, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
const findSelectionIcon = registerIcon('find-selection', Codicon.selection);
|
||||
const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight);
|
||||
const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown);
|
||||
const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.'));
|
||||
const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.'));
|
||||
const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.'));
|
||||
|
||||
export const findCloseIcon = registerIcon('find-close', Codicon.close);
|
||||
export const findReplaceIcon = registerIcon('find-replace', Codicon.replace);
|
||||
export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll);
|
||||
export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp);
|
||||
export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown);
|
||||
export const findReplaceIcon = registerIcon('find-replace', Codicon.replace, nls.localize('findReplaceIcon', 'Icon for \'Replace\' in the editor find widget.'));
|
||||
export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll, nls.localize('findReplaceAllIcon', 'Icon for \'Replace All\' in the editor find widget.'));
|
||||
export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp, nls.localize('findPreviousMatchIcon', 'Icon for \'Find Previous\' in the editor find widget.'));
|
||||
export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown, nls.localize('findNextMatchIcon', 'Icon for \'Find Next\' in the editor find widget.'));
|
||||
|
||||
export interface IFindController {
|
||||
replace(): void;
|
||||
@@ -164,7 +163,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
themeService: IThemeService,
|
||||
storageService: IStorageService,
|
||||
notificationService: INotificationService,
|
||||
storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
super();
|
||||
this._codeEditor = codeEditor;
|
||||
@@ -176,7 +174,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
this._storageService = storageService;
|
||||
this._notificationService = notificationService;
|
||||
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ctrlEnterReplaceAllWarningPromptedKey, version: 1 });
|
||||
this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL);
|
||||
|
||||
this._isVisible = false;
|
||||
@@ -228,7 +225,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
if (this._isVisible) {
|
||||
let globalBufferTerm = await this._controller.getGlobalBufferTerm();
|
||||
if (globalBufferTerm && globalBufferTerm !== this._state.searchString) {
|
||||
this._state.change({ searchString: globalBufferTerm }, true);
|
||||
this._state.change({ searchString: globalBufferTerm }, false);
|
||||
this._findInput.select();
|
||||
}
|
||||
}
|
||||
@@ -383,7 +380,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
}
|
||||
|
||||
private _delayedUpdateHistory() {
|
||||
this._updateHistoryDelayer.trigger(this._updateHistory.bind(this));
|
||||
this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)).then(undefined, onUnexpectedError);
|
||||
}
|
||||
|
||||
private _updateHistory() {
|
||||
@@ -487,7 +484,15 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
this._toggleReplaceBtn.setEnabled(this._isVisible && canReplace);
|
||||
}
|
||||
|
||||
private _revealTimeouts: any[] = [];
|
||||
|
||||
private _reveal(): void {
|
||||
this._revealTimeouts.forEach(e => {
|
||||
clearTimeout(e);
|
||||
});
|
||||
|
||||
this._revealTimeouts = [];
|
||||
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
|
||||
@@ -512,15 +517,15 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
this._tryUpdateWidgetWidth();
|
||||
this._updateButtons();
|
||||
|
||||
setTimeout(() => {
|
||||
this._revealTimeouts.push(setTimeout(() => {
|
||||
this._domNode.classList.add('visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'false');
|
||||
}, 0);
|
||||
}, 0));
|
||||
|
||||
// validate query again as it's being dismissed when we hide the find widget.
|
||||
setTimeout(() => {
|
||||
this._revealTimeouts.push(setTimeout(() => {
|
||||
this._findInput.validate();
|
||||
}, 200);
|
||||
}, 200));
|
||||
|
||||
this._codeEditor.layoutOverlayWidget(this);
|
||||
|
||||
@@ -555,6 +560,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
}
|
||||
|
||||
private _hide(focusTheEditor: boolean): void {
|
||||
this._revealTimeouts.forEach(e => {
|
||||
clearTimeout(e);
|
||||
});
|
||||
|
||||
this._revealTimeouts = [];
|
||||
|
||||
if (this._isVisible) {
|
||||
this._isVisible = false;
|
||||
|
||||
@@ -571,7 +582,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
}
|
||||
}
|
||||
|
||||
private _layoutViewZone() {
|
||||
private _layoutViewZone(targetScrollTop?: number) {
|
||||
const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;
|
||||
|
||||
if (!addExtraSpaceOnTop) {
|
||||
@@ -591,7 +602,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
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);
|
||||
this._codeEditor.setScrollTop(targetScrollTop || this._codeEditor.getScrollTop() + viewZone.heightInPx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -880,7 +891,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
);
|
||||
|
||||
this._ctrlEnterReplaceAllWarningPrompted = true;
|
||||
this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL);
|
||||
this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
|
||||
}
|
||||
|
||||
@@ -1006,7 +1017,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
// Previous button
|
||||
this._prevBtn = this._register(new SimpleButton({
|
||||
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
|
||||
className: findPreviousMatchIcon.classNames,
|
||||
icon: findPreviousMatchIcon,
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
}
|
||||
@@ -1015,7 +1026,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
// Next button
|
||||
this._nextBtn = this._register(new SimpleButton({
|
||||
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
|
||||
className: findNextMatchIcon.classNames,
|
||||
icon: findNextMatchIcon,
|
||||
onTrigger: () => {
|
||||
this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError);
|
||||
}
|
||||
@@ -1033,7 +1044,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
|
||||
// Toggle selection button
|
||||
this._toggleSelectionFind = this._register(new Checkbox({
|
||||
icon: findSelectionIcon,
|
||||
icon: ThemeIcon.asCSSIcon(findSelectionIcon),
|
||||
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
|
||||
isChecked: false
|
||||
}));
|
||||
@@ -1066,7 +1077,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
// Close button
|
||||
this._closeBtn = this._register(new SimpleButton({
|
||||
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
|
||||
className: findCloseIcon.classNames,
|
||||
icon: widgetClose,
|
||||
onTrigger: () => {
|
||||
this._state.change({ isRevealed: false, searchScope: null }, false);
|
||||
},
|
||||
@@ -1130,7 +1141,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
// Replace one button
|
||||
this._replaceBtn = this._register(new SimpleButton({
|
||||
label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
|
||||
className: findReplaceIcon.classNames,
|
||||
icon: findReplaceIcon,
|
||||
onTrigger: () => {
|
||||
this._controller.replace();
|
||||
},
|
||||
@@ -1145,7 +1156,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
// Replace all button
|
||||
this._replaceAllBtn = this._register(new SimpleButton({
|
||||
label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),
|
||||
className: findReplaceAllIcon.classNames,
|
||||
icon: findReplaceAllIcon,
|
||||
onTrigger: () => {
|
||||
this._controller.replaceAll();
|
||||
}
|
||||
@@ -1255,11 +1266,35 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
|
||||
const value = this._codeEditor.getOption(EditorOption.accessibilitySupport);
|
||||
this._findInput.setFocusInputOnOptionClick(value !== AccessibilitySupport.Enabled);
|
||||
}
|
||||
|
||||
getViewState() {
|
||||
let widgetViewZoneVisible = false;
|
||||
if (this._viewZone && this._viewZoneId) {
|
||||
widgetViewZoneVisible = this._viewZone.heightInPx > this._codeEditor.getScrollTop();
|
||||
}
|
||||
|
||||
return {
|
||||
widgetViewZoneVisible,
|
||||
scrollTop: this._codeEditor.getScrollTop()
|
||||
};
|
||||
}
|
||||
|
||||
setViewState(state?: { widgetViewZoneVisible: boolean; scrollTop: number }) {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.widgetViewZoneVisible) {
|
||||
// we should add the view zone
|
||||
this._layoutViewZone(state.scrollTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISimpleButtonOpts {
|
||||
readonly label: string;
|
||||
readonly className: string;
|
||||
readonly className?: string;
|
||||
readonly icon?: ThemeIcon;
|
||||
readonly onTrigger: () => void;
|
||||
readonly onKeyDown?: (e: IKeyboardEvent) => void;
|
||||
}
|
||||
@@ -1273,10 +1308,18 @@ export class SimpleButton extends Widget {
|
||||
super();
|
||||
this._opts = opts;
|
||||
|
||||
let className = 'button';
|
||||
if (this._opts.className) {
|
||||
className = className + ' ' + this._opts.className;
|
||||
}
|
||||
if (this._opts.icon) {
|
||||
className = className + ' ' + ThemeIcon.asClassName(this._opts.icon);
|
||||
}
|
||||
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.title = this._opts.label;
|
||||
this._domNode.tabIndex = 0;
|
||||
this._domNode.className = 'button ' + this._opts.className;
|
||||
this._domNode.className = className;
|
||||
this._domNode.setAttribute('role', 'button');
|
||||
this._domNode.setAttribute('aria-label', this._opts.label);
|
||||
|
||||
@@ -1318,11 +1361,11 @@ export class SimpleButton extends Widget {
|
||||
public setExpanded(expanded: boolean): void {
|
||||
this._domNode.setAttribute('aria-expanded', String(!!expanded));
|
||||
if (expanded) {
|
||||
this._domNode.classList.remove(...findCollapsedIcon.classNames.split(' '));
|
||||
this._domNode.classList.add(...findExpandedIcon.classNames.split(' '));
|
||||
this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findCollapsedIcon));
|
||||
this._domNode.classList.add(...ThemeIcon.asClassNameArray(findExpandedIcon));
|
||||
} else {
|
||||
this._domNode.classList.remove(...findExpandedIcon.classNames.split(' '));
|
||||
this._domNode.classList.add(...findCollapsedIcon.classNames.split(' '));
|
||||
this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findExpandedIcon));
|
||||
this._domNode.classList.add(...ThemeIcon.asClassNameArray(findCollapsedIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1345,7 +1388,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
if (widgetShadowColor) {
|
||||
collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||
collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 0 8px 2px ${widgetShadowColor}; }`);
|
||||
}
|
||||
|
||||
const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/findController';
|
||||
import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction, StartFindWithSelectionAction } from 'vs/editor/contrib/find/findController';
|
||||
import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
|
||||
import { withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
@@ -61,14 +61,20 @@ suite('FindController', async () => {
|
||||
let serviceCollection = new ServiceCollection();
|
||||
serviceCollection.set(IStorageService, {
|
||||
_serviceBrand: undefined,
|
||||
onDidChangeStorage: Event.None,
|
||||
onDidChangeTarget: Event.None,
|
||||
onDidChangeValue: Event.None,
|
||||
onWillSaveState: Event.None,
|
||||
get: (key: string) => queryState[key],
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
getNumber: (key: string) => undefined,
|
||||
getNumber: (key: string) => undefined!,
|
||||
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
|
||||
remove: () => undefined
|
||||
} as any);
|
||||
remove: () => undefined,
|
||||
isNew: () => false,
|
||||
flush: () => { return Promise.resolve(); },
|
||||
keys: () => [],
|
||||
logStorage: () => { },
|
||||
migrate: () => { throw new Error(); }
|
||||
} as IStorageService);
|
||||
|
||||
if (platform.isMacintosh) {
|
||||
serviceCollection.set(IClipboardService, <any>{
|
||||
@@ -272,7 +278,7 @@ suite('FindController', async () => {
|
||||
findController.setSearchString(testRegexString);
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.FocusFindInput,
|
||||
shouldAnimate: false,
|
||||
@@ -298,7 +304,7 @@ suite('FindController', async () => {
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
@@ -428,6 +434,59 @@ suite('FindController', async () => {
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #47400, CMD+E supports feeding multiple line of text into the find widget', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let startFindAction = new StartFindAction();
|
||||
|
||||
// change selection
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
|
||||
// cmd+f - open find widget
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 2, 4));
|
||||
let startFindWithSelectionAction = new StartFindWithSelectionAction();
|
||||
await startFindWithSelectionAction.run(null, editor);
|
||||
let findState = findController.getState();
|
||||
|
||||
assert.deepEqual(findState.searchString.split(/\r\n|\r|\n/g), ['ABC', 'ABC']);
|
||||
|
||||
editor.setSelection(new Selection(3, 1, 3, 1));
|
||||
await startFindWithSelectionAction.run(null, editor);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #109756, CMD+E with empty cursor should always work', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
editor.setSelection(new Selection(1, 2, 1, 2));
|
||||
|
||||
let startFindWithSelectionAction = new StartFindWithSelectionAction();
|
||||
startFindWithSelectionAction.run(null, editor);
|
||||
|
||||
let findState = findController.getState();
|
||||
assert.deepEqual(findState.searchString, 'ABC');
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('FindController query options persistence', async () => {
|
||||
@@ -438,14 +497,20 @@ suite('FindController query options persistence', async () => {
|
||||
let serviceCollection = new ServiceCollection();
|
||||
serviceCollection.set(IStorageService, {
|
||||
_serviceBrand: undefined,
|
||||
onDidChangeStorage: Event.None,
|
||||
onDidChangeTarget: Event.None,
|
||||
onDidChangeValue: Event.None,
|
||||
onWillSaveState: Event.None,
|
||||
get: (key: string) => queryState[key],
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
getNumber: (key: string) => undefined,
|
||||
getNumber: (key: string) => undefined!,
|
||||
store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); },
|
||||
remove: () => undefined
|
||||
} as any);
|
||||
remove: () => undefined,
|
||||
isNew: () => false,
|
||||
flush: () => { return Promise.resolve(); },
|
||||
keys: () => [],
|
||||
logStorage: () => { },
|
||||
migrate: () => { throw new Error(); }
|
||||
} as IStorageService);
|
||||
|
||||
test('matchCase', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
@@ -524,9 +589,9 @@ suite('FindController query options persistence', async () => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
const findConfig = {
|
||||
const findConfig: IFindStartOptions = {
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
@@ -558,7 +623,7 @@ suite('FindController query options persistence', async () => {
|
||||
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
@@ -582,7 +647,7 @@ suite('FindController query options persistence', async () => {
|
||||
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
@@ -607,7 +672,7 @@ suite('FindController query options persistence', async () => {
|
||||
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromSelection: 'none',
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
shouldFocus: FindStartFocusAction.NoFocusChange,
|
||||
shouldAnimate: false,
|
||||
|
||||
@@ -32,7 +32,7 @@ import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/f
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, editorSelectionBackground, transparent, iconForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);
|
||||
@@ -916,8 +916,8 @@ registerThemingParticipant((theme, collector) => {
|
||||
const editorFoldColor = theme.getColor(editorFoldForeground);
|
||||
if (editorFoldColor) {
|
||||
collector.addRule(`
|
||||
.monaco-editor .cldr${foldingExpandedIcon.cssSelector},
|
||||
.monaco-editor .cldr${foldingCollapsedIcon.cssSelector} {
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)},
|
||||
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)} {
|
||||
color: ${editorFoldColor} !important;
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -7,18 +7,20 @@ import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationsChangeA
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { IDecorationProvider } from 'vs/editor/contrib/folding/foldingModel';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
|
||||
export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown);
|
||||
export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight);
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.'));
|
||||
export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.'));
|
||||
export class FoldingDecorationProvider implements IDecorationProvider {
|
||||
|
||||
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: foldingCollapsedIcon.classNames
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
|
||||
});
|
||||
|
||||
private static readonly COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
@@ -26,19 +28,19 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
||||
afterContentClassName: 'inline-folded',
|
||||
className: 'folded-background',
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: foldingCollapsedIcon.classNames
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
|
||||
});
|
||||
|
||||
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: foldingExpandedIcon.classNames
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
});
|
||||
|
||||
private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + foldingExpandedIcon.classNames
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
});
|
||||
|
||||
private static readonly HIDDEN_RANGE_DECORATION = ModelDecorationOptions.register({
|
||||
|
||||
@@ -215,13 +215,12 @@ class FormatDocumentAction extends EditorAction {
|
||||
alias: 'Format Document',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.notInCompositeEditor, EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider),
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentFormattingProvider),
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F,
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
when: EditorContextKeys.hasDocumentFormattingProvider,
|
||||
group: '1_modification',
|
||||
order: 1.3
|
||||
}
|
||||
@@ -249,12 +248,12 @@ class FormatSelectionAction extends EditorAction {
|
||||
alias: 'Format Selection',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentSelectionFormattingProvider),
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, EditorContextKeys.hasDocumentSelectionFormattingProvider),
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
when: ContextKeyExpr.and(EditorContextKeys.hasDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection),
|
||||
when: EditorContextKeys.hasNonEmptySelection,
|
||||
group: '1_modification',
|
||||
order: 1.31
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@ import { MarkerNavigationWidget } from './gotoErrorWidget';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
|
||||
import { Codicon, registerIcon } from 'vs/base/common/codicons';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMarkerNavigationService, MarkerList } from 'vs/editor/contrib/gotoError/markerNavigationService';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
export class MarkerController implements IEditorContribution {
|
||||
|
||||
@@ -199,7 +200,7 @@ export class NextMarkerAction extends MarkerNavigationAction {
|
||||
menuOpts: {
|
||||
menuId: MarkerNavigationWidget.TitleMenu,
|
||||
title: NextMarkerAction.LABEL,
|
||||
icon: registerIcon('marker-navigation-next', Codicon.chevronDown),
|
||||
icon: registerIcon('marker-navigation-next', Codicon.chevronDown, nls.localize('nextMarkerIcon', 'Icon for goto next marker.')),
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
}
|
||||
@@ -224,7 +225,7 @@ class PrevMarkerAction extends MarkerNavigationAction {
|
||||
menuOpts: {
|
||||
menuId: MarkerNavigationWidget.TitleMenu,
|
||||
title: NextMarkerAction.LABEL,
|
||||
icon: registerIcon('marker-navigation-previous', Codicon.chevronUp),
|
||||
icon: registerIcon('marker-navigation-previous', Codicon.chevronUp, nls.localize('previousMarkerIcon', 'Icon for goto previous marker.')),
|
||||
group: 'navigation',
|
||||
order: 2
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { splitLines } from 'vs/base/common/strings';
|
||||
|
||||
class MessageWidget {
|
||||
|
||||
@@ -102,7 +103,7 @@ class MessageWidget {
|
||||
}
|
||||
}
|
||||
|
||||
const lines = message.split(/\r\n|\r|\n/g);
|
||||
const lines = splitLines(message);
|
||||
this._lines = lines.length;
|
||||
this._longestLineLength = 0;
|
||||
for (const line of lines) {
|
||||
@@ -296,7 +297,7 @@ export class MarkerNavigationWidget extends PeekViewWidget {
|
||||
protected _fillHead(container: HTMLElement): void {
|
||||
super._fillHead(container);
|
||||
|
||||
this._disposables.add(this._actionbarWidget!.actionRunner.onDidBeforeRun(e => this.editor.focus()));
|
||||
this._disposables.add(this._actionbarWidget!.actionRunner.onBeforeRun(e => this.editor.focus()));
|
||||
|
||||
const actions: IAction[] = [];
|
||||
const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService);
|
||||
|
||||
@@ -162,6 +162,9 @@ abstract class SymbolNavigationAction extends EditorAction {
|
||||
if (!range) {
|
||||
range = reference.range;
|
||||
}
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const targetEditor = await editorService.openCodeEditor({
|
||||
resource: reference.uri,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ReferencesModel, OneReference } from '../referencesModel';
|
||||
@@ -101,7 +101,7 @@ export abstract class ReferencesController implements IEditorContribution {
|
||||
this._disposables.add(this._widget.onDidClose(() => {
|
||||
modelPromise.cancel();
|
||||
if (this._widget) {
|
||||
this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL);
|
||||
this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
this._widget = undefined;
|
||||
}
|
||||
this.closeWidget();
|
||||
@@ -117,17 +117,17 @@ export abstract class ReferencesController implements IEditorContribution {
|
||||
if (event.source !== 'editor' || !this._configurationService.getValue('editor.stablePeek')) {
|
||||
// when stable peek is configured we don't close
|
||||
// the peek window on selecting the editor
|
||||
this.openReference(element, false);
|
||||
this.openReference(element, false, false);
|
||||
}
|
||||
break;
|
||||
case 'side':
|
||||
this.openReference(element, true);
|
||||
this.openReference(element, true, false);
|
||||
break;
|
||||
case 'goto':
|
||||
if (peekMode) {
|
||||
this._gotoReference(element);
|
||||
} else {
|
||||
this.openReference(element, false);
|
||||
this.openReference(element, false, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ export abstract class ReferencesController implements IEditorContribution {
|
||||
});
|
||||
}
|
||||
|
||||
openReference(ref: Location, sideBySide: boolean): void {
|
||||
openReference(ref: Location, sideBySide: boolean, pinned: boolean): void {
|
||||
// clear stage
|
||||
if (!sideBySide) {
|
||||
this.closeWidget();
|
||||
@@ -294,7 +294,7 @@ export abstract class ReferencesController implements IEditorContribution {
|
||||
const { uri, range } = ref;
|
||||
this._editorService.openCodeEditor({
|
||||
resource: uri,
|
||||
options: { selection: range }
|
||||
options: { selection: range, pinned }
|
||||
}, this._editor, sideBySide);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
const listService = accessor.get(IListService);
|
||||
const focus = <any[]>listService.lastFocusedList?.getFocus();
|
||||
if (Array.isArray(focus) && focus[0] instanceof OneReference) {
|
||||
withController(accessor, controller => controller.openReference(focus[0], true));
|
||||
withController(accessor, controller => controller.openReference(focus[0], true, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -413,6 +413,6 @@ CommandsRegistry.registerCommand('openReference', (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const focus = <any[]>listService.lastFocusedList?.getFocus();
|
||||
if (Array.isArray(focus) && focus[0] instanceof OneReference) {
|
||||
withController(accessor, controller => controller.openReference(focus[0], false));
|
||||
withController(accessor, controller => controller.openReference(focus[0], false, true));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,10 +41,20 @@ export class OneReference {
|
||||
}
|
||||
|
||||
get ariaMessage(): string {
|
||||
return localize(
|
||||
'aria.oneReference', "symbol in {0} on line {1} at column {2}",
|
||||
basename(this.uri), this.range.startLineNumber, this.range.startColumn
|
||||
);
|
||||
|
||||
const preview = this.parent.getPreview(this)?.preview(this.range);
|
||||
|
||||
if (!preview) {
|
||||
return localize(
|
||||
'aria.oneReference', "symbol in {0} on line {1} at column {2}",
|
||||
basename(this.uri), this.range.startLineNumber, this.range.startColumn
|
||||
);
|
||||
} else {
|
||||
return localize(
|
||||
'aria.oneReference.preview', "symbol in {0} on line {1} at column {2}, {3}",
|
||||
basename(this.uri), this.range.startLineNumber, this.range.startColumn, preview.value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,15 @@ export class ModesHoverController implements IEditorContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
!this._isHoverSticky && targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID
|
||||
&& this._contentWidget.value?.isColorPickerVisible()
|
||||
) {
|
||||
// though the hover is not sticky, the color picker needs to.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isHoverSticky && targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) {
|
||||
// mouse moved on top of overlay hover widget
|
||||
return;
|
||||
|
||||
@@ -31,12 +31,12 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async';
|
||||
import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { IIdentifiedSingleEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
@@ -251,6 +251,23 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
}));
|
||||
this._register(TokenizationRegistry.onDidChange((e) => {
|
||||
if (this.isVisible && this._lastRange && this._messages.length > 0) {
|
||||
this._messages = this._messages.map(msg => {
|
||||
// If a color hover is visible, we need to update the message that
|
||||
// created it so that the color matches the last chosen color
|
||||
if (msg instanceof ColorHover && !!this._lastRange?.intersectRanges(msg.range) && this._colorPicker?.model.color) {
|
||||
const color = this._colorPicker.model.color;
|
||||
const newColor = {
|
||||
red: color.rgba.r / 255,
|
||||
green: color.rgba.g / 255,
|
||||
blue: color.rgba.b / 255,
|
||||
alpha: color.rgba.a
|
||||
};
|
||||
return new ColorHover(msg.range, newColor, msg.provider);
|
||||
} else {
|
||||
return msg;
|
||||
}
|
||||
});
|
||||
|
||||
this._hover.contentsDomNode.textContent = '';
|
||||
this._renderMessages(this._lastRange, this._messages);
|
||||
}
|
||||
@@ -406,15 +423,17 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
model.presentation.textEdit.range.endLineNumber,
|
||||
model.presentation.textEdit.range.endColumn
|
||||
);
|
||||
newRange = newRange.setEndPosition(newRange.endLineNumber, newRange.startColumn + model.presentation.textEdit.text.length);
|
||||
const trackedRange = this._editor.getModel()!._setTrackedRange(null, newRange, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter);
|
||||
this._editor.pushUndoStop();
|
||||
this._editor.executeEdits('colorpicker', textEdits);
|
||||
newRange = this._editor.getModel()!._getTrackedRange(trackedRange) || newRange;
|
||||
} else {
|
||||
textEdits = [{ identifier: null, range, text: model.presentation.label, forceMoveMarkers: false }];
|
||||
newRange = range.setEndPosition(range.endLineNumber, range.startColumn + model.presentation.label.length);
|
||||
this._editor.pushUndoStop();
|
||||
this._editor.executeEdits('colorpicker', textEdits);
|
||||
}
|
||||
|
||||
this._editor.pushUndoStop();
|
||||
this._editor.executeEdits('colorpicker', textEdits);
|
||||
|
||||
if (model.presentation.additionalTextEdits) {
|
||||
textEdits = [...model.presentation.additionalTextEdits as IIdentifiedSingleEditOperation[]];
|
||||
this._editor.executeEdits('colorpicker', textEdits);
|
||||
@@ -461,7 +480,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
const markdownHoverElement = $('div.hover-row.markdown-hover');
|
||||
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
|
||||
const renderer = markdownDisposeables.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService));
|
||||
markdownDisposeables.add(renderer.onDidRenderCodeBlock(() => {
|
||||
markdownDisposeables.add(renderer.onDidRenderAsync(() => {
|
||||
hoverContentsElement.className = 'hover-contents code-hover-contents';
|
||||
this._hover.onContentsChanged();
|
||||
}));
|
||||
@@ -559,6 +578,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
return hoverElement;
|
||||
}
|
||||
|
||||
private recentMarkerCodeActionsInfo: { marker: IMarker, hasCodeActions: boolean } | undefined = undefined;
|
||||
private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement {
|
||||
const hoverElement = $('div.hover-row.status-bar');
|
||||
const disposables = new DisposableStore();
|
||||
@@ -577,24 +597,28 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
|
||||
if (!this._editor.getOption(EditorOption.readOnly)) {
|
||||
const quickfixPlaceholderElement = dom.append(actionsElement, $('div'));
|
||||
quickfixPlaceholderElement.style.opacity = '0';
|
||||
quickfixPlaceholderElement.style.transition = 'opacity 0.2s';
|
||||
setTimeout(() => quickfixPlaceholderElement.style.opacity = '1', 200);
|
||||
quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes...");
|
||||
disposables.add(toDisposable(() => quickfixPlaceholderElement.remove()));
|
||||
|
||||
if (this.recentMarkerCodeActionsInfo) {
|
||||
if (IMarkerData.makeKey(this.recentMarkerCodeActionsInfo.marker) === IMarkerData.makeKey(markerHover.marker)) {
|
||||
if (!this.recentMarkerCodeActionsInfo.hasCodeActions) {
|
||||
quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available");
|
||||
}
|
||||
} else {
|
||||
this.recentMarkerCodeActionsInfo = undefined;
|
||||
}
|
||||
}
|
||||
const updatePlaceholderDisposable = disposables.add(disposableTimeout(() => quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."), 64));
|
||||
const codeActionsPromise = this.getCodeActions(markerHover.marker);
|
||||
disposables.add(toDisposable(() => codeActionsPromise.cancel()));
|
||||
codeActionsPromise.then(actions => {
|
||||
quickfixPlaceholderElement.style.transition = '';
|
||||
quickfixPlaceholderElement.style.opacity = '1';
|
||||
updatePlaceholderDisposable.dispose();
|
||||
this.recentMarkerCodeActionsInfo = { marker: markerHover.marker, hasCodeActions: actions.validActions.length > 0 };
|
||||
|
||||
if (!actions.validActions.length) {
|
||||
if (!this.recentMarkerCodeActionsInfo.hasCodeActions) {
|
||||
actions.dispose();
|
||||
quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available");
|
||||
return;
|
||||
}
|
||||
quickfixPlaceholderElement.remove();
|
||||
quickfixPlaceholderElement.style.display = 'none';
|
||||
|
||||
let showing = false;
|
||||
disposables.add(toDisposable(() => {
|
||||
|
||||
@@ -84,7 +84,7 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu
|
||||
|
||||
}
|
||||
if (currentLineText !== adjustedLineContent) {
|
||||
indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces)));
|
||||
indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces)));
|
||||
}
|
||||
} else {
|
||||
globalIndent = strings.getLeadingWhitespace(currentLineText);
|
||||
@@ -115,7 +115,7 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu
|
||||
}
|
||||
|
||||
if (oldIndentation !== idealIndentForNextLine) {
|
||||
indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
|
||||
indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
|
||||
}
|
||||
|
||||
// calculate idealIndentForNextLine
|
||||
@@ -472,7 +472,6 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
}
|
||||
const autoIndent = this.editor.getOption(EditorOption.autoIndent);
|
||||
const { tabSize, indentSize, insertSpaces } = model.getOptions();
|
||||
this.editor.pushUndoStop();
|
||||
let textEdits: TextEdit[] = [];
|
||||
|
||||
let indentConverter = {
|
||||
@@ -583,9 +582,12 @@ export class AutoIndentOnPaste implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection()!);
|
||||
this.editor.executeCommand('autoIndentOnPaste', cmd);
|
||||
this.editor.pushUndoStop();
|
||||
if (textEdits.length > 0) {
|
||||
this.editor.pushUndoStop();
|
||||
let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection()!);
|
||||
this.editor.executeCommand('autoIndentOnPaste', cmd);
|
||||
this.editor.pushUndoStop();
|
||||
}
|
||||
}
|
||||
|
||||
private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean {
|
||||
|
||||
@@ -12,15 +12,17 @@ export class CopyLinesCommand implements ICommand {
|
||||
|
||||
private readonly _selection: Selection;
|
||||
private readonly _isCopyingDown: boolean;
|
||||
private readonly _noop: boolean;
|
||||
|
||||
private _selectionDirection: SelectionDirection;
|
||||
private _selectionId: string | null;
|
||||
private _startLineNumberDelta: number;
|
||||
private _endLineNumberDelta: number;
|
||||
|
||||
constructor(selection: Selection, isCopyingDown: boolean) {
|
||||
constructor(selection: Selection, isCopyingDown: boolean, noop?: boolean) {
|
||||
this._selection = selection;
|
||||
this._isCopyingDown = isCopyingDown;
|
||||
this._noop = noop || false;
|
||||
this._selectionDirection = SelectionDirection.LTR;
|
||||
this._selectionId = null;
|
||||
this._startLineNumberDelta = 0;
|
||||
@@ -51,10 +53,14 @@ export class CopyLinesCommand implements ICommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._isCopyingDown) {
|
||||
builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + sourceText);
|
||||
if (this._noop) {
|
||||
builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber + 1, 1), s.endLineNumber === model.getLineCount() ? '' : '\n');
|
||||
} else {
|
||||
builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), sourceText + '\n');
|
||||
if (!this._isCopyingDown) {
|
||||
builder.addEditOperation(new Range(s.endLineNumber, model.getLineMaxColumn(s.endLineNumber), s.endLineNumber, model.getLineMaxColumn(s.endLineNumber)), '\n' + sourceText);
|
||||
} else {
|
||||
builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), sourceText + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
this._selectionId = builder.trackSelection(s);
|
||||
|
||||
@@ -63,10 +63,7 @@ abstract class AbstractCopyLinesAction extends EditorAction {
|
||||
|
||||
const commands: ICommand[] = [];
|
||||
for (const selection of selections) {
|
||||
if (selection.ignore) {
|
||||
continue;
|
||||
}
|
||||
commands.push(new CopyLinesCommand(selection.selection, this.down));
|
||||
commands.push(new CopyLinesCommand(selection.selection, this.down, selection.ignore));
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IndentAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { CompleteEnterAction, IndentAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { IIndentConverter, LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
|
||||
import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils';
|
||||
@@ -135,7 +135,8 @@ export class MoveLinesCommand implements ICommand {
|
||||
// to s.startLineNumber
|
||||
builder.addEditOperation(new Range(s.startLineNumber, 1, s.startLineNumber, 1), insertingText + '\n');
|
||||
|
||||
let ret = this.matchEnterRule(model, indentConverter, tabSize, s.startLineNumber, s.startLineNumber, insertingText);
|
||||
let ret = this.matchEnterRuleMovingDown(model, indentConverter, tabSize, s.startLineNumber, movingLineNumber, insertingText);
|
||||
|
||||
// check if the line being moved before matches onEnter rules, if so let's adjust the indentation by onEnter rules.
|
||||
if (ret !== null) {
|
||||
if (ret !== 0) {
|
||||
@@ -229,31 +230,7 @@ export class MoveLinesCommand implements ICommand {
|
||||
};
|
||||
}
|
||||
|
||||
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) {
|
||||
let validPrecedingLine = oneLineAbove;
|
||||
while (validPrecedingLine >= 1) {
|
||||
// ship empty lines as empty lines just inherit indentation
|
||||
let lineContent;
|
||||
if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) {
|
||||
lineContent = oneLineAboveText;
|
||||
} else {
|
||||
lineContent = model.getLineContent(validPrecedingLine);
|
||||
}
|
||||
|
||||
let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
|
||||
if (nonWhitespaceIdx >= 0) {
|
||||
break;
|
||||
}
|
||||
validPrecedingLine--;
|
||||
}
|
||||
|
||||
if (validPrecedingLine < 1 || line > model.getLineCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let maxColumn = model.getLineMaxColumn(validPrecedingLine);
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
|
||||
|
||||
private parseEnterResult(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, enter: CompleteEnterAction | null) {
|
||||
if (enter) {
|
||||
let enterPrefix = enter.indentation;
|
||||
|
||||
@@ -283,6 +260,72 @@ export class MoveLinesCommand implements ICommand {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param model
|
||||
* @param indentConverter
|
||||
* @param tabSize
|
||||
* @param line the line moving down
|
||||
* @param futureAboveLineNumber the line which will be at the `line` position
|
||||
* @param futureAboveLineText
|
||||
*/
|
||||
private matchEnterRuleMovingDown(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, futureAboveLineNumber: number, futureAboveLineText: string) {
|
||||
if (strings.lastNonWhitespaceIndex(futureAboveLineText) >= 0) {
|
||||
// break
|
||||
let maxColumn = model.getLineMaxColumn(futureAboveLineNumber);
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(futureAboveLineNumber, maxColumn, futureAboveLineNumber, maxColumn));
|
||||
return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
|
||||
} else {
|
||||
// go upwards, starting from `line - 1`
|
||||
let validPrecedingLine = line - 1;
|
||||
while (validPrecedingLine >= 1) {
|
||||
let lineContent = model.getLineContent(validPrecedingLine);
|
||||
let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
|
||||
|
||||
if (nonWhitespaceIdx >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
validPrecedingLine--;
|
||||
}
|
||||
|
||||
if (validPrecedingLine < 1 || line > model.getLineCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let maxColumn = model.getLineMaxColumn(validPrecedingLine);
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
|
||||
return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
|
||||
}
|
||||
}
|
||||
|
||||
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) {
|
||||
let validPrecedingLine = oneLineAbove;
|
||||
while (validPrecedingLine >= 1) {
|
||||
// ship empty lines as empty lines just inherit indentation
|
||||
let lineContent;
|
||||
if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) {
|
||||
lineContent = oneLineAboveText;
|
||||
} else {
|
||||
lineContent = model.getLineContent(validPrecedingLine);
|
||||
}
|
||||
|
||||
let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineContent);
|
||||
if (nonWhitespaceIdx >= 0) {
|
||||
break;
|
||||
}
|
||||
validPrecedingLine--;
|
||||
}
|
||||
|
||||
if (validPrecedingLine < 1 || line > model.getLineCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let maxColumn = model.getLineMaxColumn(validPrecedingLine);
|
||||
let enter = LanguageConfigurationRegistry.getEnterAction(this._autoIndent, model, new Range(validPrecedingLine, maxColumn, validPrecedingLine, maxColumn));
|
||||
return this.parseEnterResult(model, indentConverter, tabSize, line, enter);
|
||||
}
|
||||
|
||||
private trimLeft(str: string) {
|
||||
return str.replace(/^\s+/, '');
|
||||
}
|
||||
|
||||
@@ -329,6 +329,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
|
||||
mode.dispose();
|
||||
});
|
||||
|
||||
|
||||
test('move line should still work as before if there is no indentation rules', () => {
|
||||
testMoveLinesUpWithIndentCommand(
|
||||
null!,
|
||||
@@ -351,3 +352,55 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
class EnterRulesMode extends MockMode {
|
||||
private static readonly _id = new LanguageIdentifier('moveLinesEnterMode', 8);
|
||||
constructor() {
|
||||
super(EnterRulesMode._id);
|
||||
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
|
||||
indentationRules: {
|
||||
decreaseIndentPattern: /^\s*\[$/,
|
||||
increaseIndentPattern: /^\s*\]$/,
|
||||
},
|
||||
brackets: [
|
||||
['{', '}']
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => {
|
||||
|
||||
test('issue #54829. move block across block', () => {
|
||||
let mode = new EnterRulesMode();
|
||||
|
||||
testMoveLinesDownWithIndentCommand(
|
||||
mode.getLanguageIdentifier(),
|
||||
|
||||
[
|
||||
'if (true) {',
|
||||
' if (false) {',
|
||||
' if (1) {',
|
||||
' console.log(\'b\');',
|
||||
' }',
|
||||
' console.log(\'a\');',
|
||||
' }',
|
||||
'}'
|
||||
],
|
||||
new Selection(3, 9, 5, 10),
|
||||
[
|
||||
'if (true) {',
|
||||
' if (false) {',
|
||||
' console.log(\'a\');',
|
||||
' if (1) {',
|
||||
' console.log(\'b\');',
|
||||
' }',
|
||||
' }',
|
||||
'}'
|
||||
],
|
||||
new Selection(4, 9, 6, 10),
|
||||
);
|
||||
|
||||
mode.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/onTypeRename';
|
||||
import * as nls from 'vs/nls';
|
||||
import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
@@ -15,7 +14,7 @@ import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { LinkedEditingRangeProviderRegistry, LinkedEditingRanges } from 'vs/editor/common/modes';
|
||||
import { first, createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -31,19 +30,21 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
|
||||
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('onTypeRenameInputVisible', false);
|
||||
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('LinkedEditingInputVisible', false);
|
||||
|
||||
export class OnTypeRenameContribution extends Disposable implements IEditorContribution {
|
||||
const DECORATION_CLASS_NAME = 'linked-editing-decoration';
|
||||
|
||||
public static readonly ID = 'editor.contrib.onTypeRename';
|
||||
export class LinkedEditingContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.linkedEditing';
|
||||
|
||||
private static readonly DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
className: 'on-type-rename-decoration'
|
||||
className: DECORATION_CLASS_NAME
|
||||
});
|
||||
|
||||
static get(editor: ICodeEditor): OnTypeRenameContribution {
|
||||
return editor.getContribution<OnTypeRenameContribution>(OnTypeRenameContribution.ID);
|
||||
static get(editor: ICodeEditor): LinkedEditingContribution {
|
||||
return editor.getContribution<LinkedEditingContribution>(LinkedEditingContribution.ID);
|
||||
}
|
||||
|
||||
private _debounceDuration = 200;
|
||||
@@ -92,11 +93,11 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
this._register(this._editor.onDidChangeModel(() => this.reinitialize()));
|
||||
|
||||
this._register(this._editor.onDidChangeConfiguration(e => {
|
||||
if (e.hasChanged(EditorOption.renameOnType)) {
|
||||
if (e.hasChanged(EditorOption.linkedEditing) || e.hasChanged(EditorOption.renameOnType)) {
|
||||
this.reinitialize();
|
||||
}
|
||||
}));
|
||||
this._register(OnTypeRenameProviderRegistry.onDidChange(() => this.reinitialize()));
|
||||
this._register(LinkedEditingRangeProviderRegistry.onDidChange(() => this.reinitialize()));
|
||||
this._register(this._editor.onDidChangeModelLanguage(() => this.reinitialize()));
|
||||
|
||||
this.reinitialize();
|
||||
@@ -104,7 +105,7 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
|
||||
private reinitialize() {
|
||||
const model = this._editor.getModel();
|
||||
const isEnabled = model !== null && this._editor.getOption(EditorOption.renameOnType) && OnTypeRenameProviderRegistry.has(model);
|
||||
const isEnabled = model !== null && (this._editor.getOption(EditorOption.linkedEditing) || this._editor.getOption(EditorOption.renameOnType)) && LinkedEditingRangeProviderRegistry.has(model);
|
||||
if (isEnabled === this._enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -219,9 +220,10 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
try {
|
||||
this._editor.popUndoStop();
|
||||
this._ignoreChangeEvent = true;
|
||||
const prevEditOperationType = this._editor._getViewModel().getPrevEditOperationType();
|
||||
this._editor.executeEdits('onTypeRename', edits);
|
||||
this._editor.executeEdits('linkedEditing', edits);
|
||||
this._editor._getViewModel().setPrevEditOperationType(prevEditOperationType);
|
||||
} finally {
|
||||
this._ignoreChangeEvent = false;
|
||||
@@ -282,7 +284,7 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
this._currentRequestModelVersion = modelVersionId;
|
||||
const request = createCancelablePromise(async token => {
|
||||
try {
|
||||
const response = await getOnTypeRenameRanges(model, position, token);
|
||||
const response = await getLinkedEditingRanges(model, position, token);
|
||||
if (request !== this._currentRequest) {
|
||||
return;
|
||||
}
|
||||
@@ -312,12 +314,12 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
}
|
||||
|
||||
if (!foundReferenceRange) {
|
||||
// Cannot do on type rename if the ranges are not where the cursor is...
|
||||
// Cannot do linked editing if the ranges are not where the cursor is...
|
||||
this.clearRanges();
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION }));
|
||||
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: LinkedEditingContribution.DECORATION }));
|
||||
this._visibleContextKey.set(true);
|
||||
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
|
||||
} catch (err) {
|
||||
@@ -361,12 +363,12 @@ export class OnTypeRenameContribution extends Disposable implements IEditorContr
|
||||
// }
|
||||
}
|
||||
|
||||
export class OnTypeRenameAction extends EditorAction {
|
||||
export class LinkedEditingAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.onTypeRename',
|
||||
label: nls.localize('onTypeRename.label', "On Type Rename Symbol"),
|
||||
alias: 'On Type Rename Symbol',
|
||||
id: 'editor.action.linkedEditing',
|
||||
label: nls.localize('linkedEditing.label', "Start Linked Editing"),
|
||||
alias: 'Start Linked Editing',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
@@ -397,7 +399,7 @@ export class OnTypeRenameAction extends EditorAction {
|
||||
}
|
||||
|
||||
run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const controller = OnTypeRenameContribution.get(editor);
|
||||
const controller = LinkedEditingContribution.get(editor);
|
||||
if (controller) {
|
||||
return Promise.resolve(controller.updateRanges(true));
|
||||
}
|
||||
@@ -405,9 +407,9 @@ export class OnTypeRenameAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
const OnTypeRenameCommand = EditorCommand.bindToContribution<OnTypeRenameContribution>(OnTypeRenameContribution.get);
|
||||
registerEditorCommand(new OnTypeRenameCommand({
|
||||
id: 'cancelOnTypeRenameInput',
|
||||
const LinkedEditingCommand = EditorCommand.bindToContribution<LinkedEditingContribution>(LinkedEditingContribution.get);
|
||||
registerEditorCommand(new LinkedEditingCommand({
|
||||
id: 'cancelLinkedEditingInput',
|
||||
precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE,
|
||||
handler: x => x.clearRanges(),
|
||||
kbOpts: {
|
||||
@@ -419,45 +421,31 @@ registerEditorCommand(new OnTypeRenameCommand({
|
||||
}));
|
||||
|
||||
|
||||
export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{
|
||||
ranges: IRange[],
|
||||
wordPattern?: RegExp
|
||||
} | undefined | null> {
|
||||
const orderedByScore = OnTypeRenameProviderRegistry.ordered(model);
|
||||
function getLinkedEditingRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<LinkedEditingRanges | undefined | null> {
|
||||
const orderedByScore = LinkedEditingRangeProviderRegistry.ordered(model);
|
||||
|
||||
// in order of score ask the occurrences provider
|
||||
// in order of score ask the linked editing range provider
|
||||
// until someone response with a good result
|
||||
// (good = none empty array)
|
||||
return first<{
|
||||
ranges: IRange[],
|
||||
wordPattern?: RegExp
|
||||
} | undefined>(orderedByScore.map(provider => () => {
|
||||
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((res) => {
|
||||
if (!res) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
ranges: res.ranges,
|
||||
wordPattern: res.wordPattern || provider.wordPattern
|
||||
};
|
||||
}, (err) => {
|
||||
onUnexpectedExternalError(err);
|
||||
// (good = not null)
|
||||
return first<LinkedEditingRanges | undefined | null>(orderedByScore.map(provider => async () => {
|
||||
try {
|
||||
return await provider.provideLinkedEditingRanges(model, position, token);
|
||||
} catch (e) {
|
||||
onUnexpectedExternalError(e);
|
||||
return undefined;
|
||||
});
|
||||
|
||||
}
|
||||
}), result => !!result && arrays.isNonEmptyArray(result?.ranges));
|
||||
}
|
||||
|
||||
export const editorOnTypeRenameBackground = registerColor('editor.onTypeRenameBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorOnTypeRenameBackground', 'Background color when the editor auto renames on type.'));
|
||||
export const editorLinkedEditingBackground = registerColor('editor.linkedEditingBackground', { dark: Color.fromHex('#f00').transparent(0.3), light: Color.fromHex('#f00').transparent(0.3), hc: Color.fromHex('#f00').transparent(0.3) }, nls.localize('editorLinkedEditingBackground', 'Background color when the editor auto renames on type.'));
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const editorOnTypeRenameBackgroundColor = theme.getColor(editorOnTypeRenameBackground);
|
||||
if (editorOnTypeRenameBackgroundColor) {
|
||||
collector.addRule(`.monaco-editor .on-type-rename-decoration { background: ${editorOnTypeRenameBackgroundColor}; border-left-color: ${editorOnTypeRenameBackgroundColor}; }`);
|
||||
const editorLinkedEditingBackgroundColor = theme.getColor(editorLinkedEditingBackground);
|
||||
if (editorLinkedEditingBackgroundColor) {
|
||||
collector.addRule(`.monaco-editor .${DECORATION_CLASS_NAME} { background: ${editorLinkedEditingBackgroundColor}; border-left-color: ${editorLinkedEditingBackgroundColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeLinkedEditingProvider', (model, position) => getLinkedEditingRanges(model, position, CancellationToken.None));
|
||||
|
||||
registerEditorContribution(OnTypeRenameContribution.ID, OnTypeRenameContribution);
|
||||
registerEditorAction(OnTypeRenameAction);
|
||||
registerEditorContribution(LinkedEditingContribution.ID, LinkedEditingContribution);
|
||||
registerEditorAction(LinkedEditingAction);
|
||||
@@ -10,12 +10,13 @@ import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename';
|
||||
import { LinkedEditingContribution } from 'vs/editor/contrib/linkedEditing/linkedEditing';
|
||||
import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
|
||||
const mockFile = URI.parse('test:somefile.ttt');
|
||||
const mockFileSelector = { scheme: 'test' };
|
||||
@@ -29,7 +30,12 @@ interface TestEditor {
|
||||
redo(): void;
|
||||
}
|
||||
|
||||
suite('On type rename', () => {
|
||||
const languageIdentifier = new modes.LanguageIdentifier('linkedEditingTestLangage', 74);
|
||||
LanguageConfigurationRegistry.register(languageIdentifier, {
|
||||
wordPattern: /[a-zA-Z]+/
|
||||
});
|
||||
|
||||
suite('linked editing', () => {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
setup(() => {
|
||||
@@ -42,8 +48,8 @@ suite('On type rename', () => {
|
||||
|
||||
function createMockEditor(text: string | string[]): ITestCodeEditor {
|
||||
const model = typeof text === 'string'
|
||||
? createTextModel(text, undefined, undefined, mockFile)
|
||||
: createTextModel(text.join('\n'), undefined, undefined, mockFile);
|
||||
? createTextModel(text, undefined, languageIdentifier, mockFile)
|
||||
: createTextModel(text.join('\n'), undefined, languageIdentifier, mockFile);
|
||||
|
||||
const editor = createTestCodeEditor({ model });
|
||||
disposables.add(model);
|
||||
@@ -55,18 +61,16 @@ suite('On type rename', () => {
|
||||
|
||||
function testCase(
|
||||
name: string,
|
||||
initialState: { text: string | string[], responseWordPattern?: RegExp, providerWordPattern?: RegExp },
|
||||
initialState: { text: string | string[], responseWordPattern?: RegExp },
|
||||
operations: (editor: TestEditor) => Promise<void>,
|
||||
expectedEndText: string | string[]
|
||||
) {
|
||||
test(name, async () => {
|
||||
disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, {
|
||||
wordPattern: initialState.providerWordPattern,
|
||||
provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) {
|
||||
disposables.add(modes.LinkedEditingRangeProviderRegistry.register(mockFileSelector, {
|
||||
provideLinkedEditingRanges(model: ITextModel, pos: IPosition) {
|
||||
const wordAtPos = model.getWordAtPosition(pos);
|
||||
if (wordAtPos) {
|
||||
const matches = model.findMatches(wordAtPos.word, false, false, true, USUAL_WORD_SEPARATORS, false);
|
||||
assert.ok(matches.length > 0);
|
||||
return { ranges: matches.map(m => m.range), wordPattern: initialState.responseWordPattern };
|
||||
}
|
||||
return { ranges: [], wordPattern: initialState.responseWordPattern };
|
||||
@@ -74,25 +78,25 @@ suite('On type rename', () => {
|
||||
}));
|
||||
|
||||
const editor = createMockEditor(initialState.text);
|
||||
editor.updateOptions({ renameOnType: true });
|
||||
const ontypeRenameContribution = editor.registerAndInstantiateContribution(
|
||||
OnTypeRenameContribution.ID,
|
||||
OnTypeRenameContribution
|
||||
editor.updateOptions({ linkedEditing: true });
|
||||
const linkedEditingContribution = editor.registerAndInstantiateContribution(
|
||||
LinkedEditingContribution.ID,
|
||||
LinkedEditingContribution
|
||||
);
|
||||
ontypeRenameContribution.setDebounceDuration(0);
|
||||
linkedEditingContribution.setDebounceDuration(0);
|
||||
|
||||
const testEditor: TestEditor = {
|
||||
setPosition(pos: Position) {
|
||||
editor.setPosition(pos);
|
||||
return ontypeRenameContribution.currentUpdateTriggerPromise;
|
||||
return linkedEditingContribution.currentUpdateTriggerPromise;
|
||||
},
|
||||
setSelection(sel: IRange) {
|
||||
editor.setSelection(sel);
|
||||
return ontypeRenameContribution.currentUpdateTriggerPromise;
|
||||
return linkedEditingContribution.currentUpdateTriggerPromise;
|
||||
},
|
||||
trigger(source: string | null | undefined, handlerId: string, payload: any) {
|
||||
editor.trigger(source, handlerId, payload);
|
||||
return ontypeRenameContribution.currentSyncTriggerPromise;
|
||||
return linkedEditingContribution.currentSyncTriggerPromise;
|
||||
},
|
||||
undo() {
|
||||
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||
@@ -247,7 +251,7 @@ suite('On type rename', () => {
|
||||
// testCase('Selection insert - across two boundary', state, async (editor) => {
|
||||
// const pos = new Position(1, 2);
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await linkedEditingContribution.updateLinkedUI(pos);
|
||||
// await editor.setSelection(new Range(1, 4, 1, 9));
|
||||
// await editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||
// }, '<ooioo>');
|
||||
@@ -299,7 +303,7 @@ suite('On type rename', () => {
|
||||
|
||||
const state3 = {
|
||||
...state,
|
||||
providerWordPattern: /[a-yA-Y]+/
|
||||
responseWordPattern: /[a-yA-Y]+/
|
||||
};
|
||||
|
||||
testCase('Breakout with stop pattern - insert', state3, async (editor) => {
|
||||
@@ -334,7 +338,6 @@ suite('On type rename', () => {
|
||||
|
||||
const state4 = {
|
||||
...state,
|
||||
providerWordPattern: /[a-yA-Y]+/,
|
||||
responseWordPattern: /[a-eA-E]+/
|
||||
};
|
||||
|
||||
@@ -380,7 +383,7 @@ suite('On type rename', () => {
|
||||
// testCase('Delete - left all', state, async (editor) => {
|
||||
// const pos = new Position(1, 3);
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await linkedEditingContribution.updateLinkedUI(pos);
|
||||
// await editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// }, '></>');
|
||||
|
||||
@@ -390,7 +393,7 @@ suite('On type rename', () => {
|
||||
// testCase('Delete - left all then undo', state, async (editor) => {
|
||||
// const pos = new Position(1, 5);
|
||||
// await editor.setPosition(pos);
|
||||
// await ontypeRenameContribution.updateLinkedUI(pos);
|
||||
// await linkedEditingContribution.updateLinkedUI(pos);
|
||||
// await editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||
// editor.undo();
|
||||
// }, '></ooo>');
|
||||
@@ -47,7 +47,17 @@ function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString {
|
||||
: nls.localize('links.navigate.kb.alt', "alt + click");
|
||||
|
||||
if (link.url) {
|
||||
const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}) (${kb})`);
|
||||
let nativeLabel = '';
|
||||
if (/^command:/i.test(link.url.toString())) {
|
||||
// Don't show complete command arguments in the native tooltip
|
||||
const match = link.url.toString().match(/^command:([^?#]+)/);
|
||||
if (match) {
|
||||
const commandId = match[1];
|
||||
const nativeLabelText = nls.localize('tooltip.explanation', "Execute command {0}", commandId);
|
||||
nativeLabel = ` "${nativeLabelText}"`;
|
||||
}
|
||||
}
|
||||
const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}${nativeLabel}) (${kb})`);
|
||||
return hoverMessage;
|
||||
} else {
|
||||
return new MarkdownString().appendText(`${label} (${kb})`);
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-editor-overlaymessage.below {
|
||||
padding-bottom: 0;
|
||||
padding-top: 8px;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
@@ -37,3 +43,13 @@
|
||||
border-width: 8px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-editor-overlaymessage:not(.below) .anchor.top,
|
||||
.monaco-editor .monaco-editor-overlaymessage.below .anchor.below {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-editor .monaco-editor-overlaymessage.below .anchor.top {
|
||||
display: inherit;
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user