Update to VS Code 1.52.1

This commit is contained in:
Asher
2021-02-09 16:08:37 +00:00
1351 changed files with 56560 additions and 38990 deletions

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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(() => {

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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);
});
}
}

View File

@@ -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) {

View File

@@ -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>>();

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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';

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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)
);
}
}

View File

@@ -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()));

View File

@@ -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(),

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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."),
]
}
}
};

View File

@@ -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',

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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() };
}

View File

@@ -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> {

View File

@@ -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.
}

View File

@@ -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();

View File

@@ -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
}
/**

View File

@@ -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

View File

@@ -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 [];

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -90,6 +90,7 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr
commandArgs,
undefined,
false,
null);
null,
false);
}

View File

@@ -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);
}
});
}

View File

@@ -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)));

View File

@@ -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++;
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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(() => {

View File

@@ -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()));

View File

@@ -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
));
}

View File

@@ -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) {

View File

@@ -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`);
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}
`);

View File

@@ -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({

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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));
}
});

View File

@@ -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
);
}
}
}

View File

@@ -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;

View File

@@ -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(() => {

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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();

View File

@@ -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+/, '');
}

View File

@@ -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();
});
});

View File

@@ -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);

View File

@@ -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>');

View File

@@ -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})`);

View File

@@ -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