Merge remote-tracking branch 'origin/main' into alex/ghost-text

This commit is contained in:
Alex Dima 2021-05-28 18:33:32 +02:00
commit ea02f214c7
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
24 changed files with 421 additions and 381 deletions

View file

@ -40,54 +40,40 @@ export interface IEmptyContentData {
horizontalDistanceToText?: number;
}
interface IETextRange {
boundingHeight: number;
boundingLeft: number;
boundingTop: number;
boundingWidth: number;
htmlText: string;
offsetLeft: number;
offsetTop: number;
text: string;
collapse(start?: boolean): void;
compareEndPoints(how: string, sourceRange: IETextRange): number;
duplicate(): IETextRange;
execCommand(cmdID: string, showUI?: boolean, value?: any): boolean;
execCommandShowHelp(cmdID: string): boolean;
expand(Unit: string): boolean;
findText(string: string, count?: number, flags?: number): boolean;
getBookmark(): string;
getBoundingClientRect(): ClientRect;
getClientRects(): ClientRectList;
inRange(range: IETextRange): boolean;
isEqual(range: IETextRange): boolean;
move(unit: string, count?: number): number;
moveEnd(unit: string, count?: number): number;
moveStart(unit: string, count?: number): number;
moveToBookmark(bookmark: string): boolean;
moveToElementText(element: Element): void;
moveToPoint(x: number, y: number): void;
parentElement(): Element;
pasteHTML(html: string): void;
queryCommandEnabled(cmdID: string): boolean;
queryCommandIndeterm(cmdID: string): boolean;
queryCommandState(cmdID: string): boolean;
queryCommandSupported(cmdID: string): boolean;
queryCommandText(cmdID: string): string;
queryCommandValue(cmdID: string): any;
scrollIntoView(fStart?: boolean): void;
select(): void;
setEndPoint(how: string, SourceRange: IETextRange): void;
export interface ITextContentData {
mightBeForeignElement: boolean;
}
declare const IETextRange: {
prototype: IETextRange;
new(): IETextRange;
};
const enum HitTestResultType {
Unknown = 0,
Content = 1,
}
interface IHitTestResult {
position: Position | null;
hitTarget: Element | null;
class UnknownHitTestResult {
readonly type = HitTestResultType.Unknown;
constructor(
readonly hitTarget: Element | null = null
) { }
}
class ContentHitTestResult {
readonly type = HitTestResultType.Content;
constructor(
readonly position: Position,
readonly spanNode: HTMLElement
) { }
}
type HitTestResult = UnknownHitTestResult | ContentHitTestResult;
namespace HitTestResult {
export function createFromDOMInfo(ctx: HitTestContext, spanNode: HTMLElement, offset: number): HitTestResult {
const position = ctx.getPositionFromDOMInfo(spanNode, offset);
if (position) {
return new ContentHitTestResult(position, spanNode);
}
return new UnknownHitTestResult(spanNode);
}
}
export class PointerHandlerLastRenderData {
@ -426,6 +412,17 @@ class HitTestRequest extends BareHitTestRequest {
return `pos(${this.pos.x},${this.pos.y}), editorPos(${this.editorPos.x},${this.editorPos.y}), mouseVerticalOffset: ${this.mouseVerticalOffset}, mouseContentHorizontalOffset: ${this.mouseContentHorizontalOffset}\n\ttarget: ${this.target ? (<HTMLElement>this.target).outerHTML : null}`;
}
public fulfill(type: MouseTargetType.UNKNOWN, position?: Position | null, range?: EditorRange | null): MouseTarget;
public fulfill(type: MouseTargetType.TEXTAREA, position: Position | null): MouseTarget;
public fulfill(type: MouseTargetType.GUTTER_GLYPH_MARGIN | MouseTargetType.GUTTER_LINE_NUMBERS | MouseTargetType.GUTTER_LINE_DECORATIONS, position: Position, range: EditorRange, detail: IMarginData): MouseTarget;
public fulfill(type: MouseTargetType.GUTTER_VIEW_ZONE | MouseTargetType.CONTENT_VIEW_ZONE, position: Position, range: null, detail: IViewZoneData): MouseTarget;
public fulfill(type: MouseTargetType.CONTENT_TEXT, position: Position | null, range: EditorRange | null, detail: ITextContentData): MouseTarget;
public fulfill(type: MouseTargetType.CONTENT_EMPTY, position: Position | null, range: EditorRange | null, detail: IEmptyContentData): MouseTarget;
public fulfill(type: MouseTargetType.CONTENT_WIDGET, position: null, range: null, detail: string): MouseTarget;
public fulfill(type: MouseTargetType.SCROLLBAR, position: Position): MouseTarget;
public fulfill(type: MouseTargetType.OVERLAY_WIDGET, position: null, range: null, detail: string): MouseTarget;
// public fulfill(type: MouseTargetType.OVERVIEW_RULER, position?: Position | null, range?: EditorRange | null, detail?: any): MouseTarget;
// public fulfill(type: MouseTargetType.OUTSIDE_EDITOR, position?: Position | null, range?: EditorRange | null, detail?: any): MouseTarget;
public fulfill(type: MouseTargetType, position: Position | null = null, range: EditorRange | null = null, detail: any = null): MouseTarget {
let mouseColumn = this.mouseColumn;
if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) {
@ -506,8 +503,8 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.position) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.position.lineNumber, hitTestResult.position.column);
if (hitTestResult.type === HitTestResultType.Content) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@ -567,7 +564,7 @@ export class MouseTargetFactory {
for (const d of lastViewCursorsRenderData) {
if (request.target === d.domNode) {
return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position);
return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false });
}
}
}
@ -599,7 +596,7 @@ export class MouseTargetFactory {
cursorVerticalOffset <= mouseVerticalOffset
&& mouseVerticalOffset <= cursorVerticalOffset + d.height
) {
return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position);
return request.fulfill(MouseTargetType.CONTENT_TEXT, d.position, null, { mightBeForeignElement: false });
}
}
}
@ -621,7 +618,7 @@ export class MouseTargetFactory {
// Is it the textarea?
if (ElementPath.isTextArea(request.targetPath)) {
if (ctx.lastRenderData.lastTextareaPosition) {
return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition);
return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition, null, { mightBeForeignElement: false });
}
return request.fulfill(MouseTargetType.TEXTAREA, ctx.lastRenderData.lastTextareaPosition);
}
@ -667,7 +664,7 @@ export class MouseTargetFactory {
}
if (ctx.isInTopPadding(request.mouseVerticalOffset)) {
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(1, 1), undefined, EMPTY_CONTENT_AFTER_LINES);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(1, 1), null, EMPTY_CONTENT_AFTER_LINES);
}
// Check if it is below any lines and any view zones
@ -675,7 +672,7 @@ export class MouseTargetFactory {
// This most likely indicates it happened after the last view-line
const lineCount = ctx.model.getLineCount();
const maxLineColumn = ctx.model.getLineMaxColumn(lineCount);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineCount, maxLineColumn), undefined, EMPTY_CONTENT_AFTER_LINES);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineCount, maxLineColumn), null, EMPTY_CONTENT_AFTER_LINES);
}
if (domHitTestExecuted) {
@ -686,14 +683,14 @@ export class MouseTargetFactory {
if (ctx.model.getLineLength(lineNumber) === 0) {
const lineWidth = ctx.getLineWidth(lineNumber);
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, 1), undefined, detail);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, new Position(lineNumber, 1), null, detail);
}
const lineWidth = ctx.getLineWidth(lineNumber);
if (request.mouseContentHorizontalOffset >= lineWidth) {
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
const pos = new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber));
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, null, detail);
}
}
@ -703,8 +700,8 @@ export class MouseTargetFactory {
const hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
if (hitTestResult.position) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.position.lineNumber, hitTestResult.position.column);
if (hitTestResult.type === HitTestResultType.Content) {
return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.spanNode, hitTestResult.position);
}
return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
@ -760,14 +757,15 @@ export class MouseTargetFactory {
return (chars + 1);
}
private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, lineNumber: number, column: number): MouseTarget {
const pos = new Position(lineNumber, column);
private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, spanNode: HTMLElement, pos: Position): MouseTarget {
const lineNumber = pos.lineNumber;
const column = pos.column;
const lineWidth = ctx.getLineWidth(lineNumber);
if (request.mouseContentHorizontalOffset > lineWidth) {
const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, undefined, detail);
return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, null, detail);
}
const visibleRange = ctx.visibleRangeForPosition(lineNumber, column);
@ -779,7 +777,7 @@ export class MouseTargetFactory {
const columnHorizontalOffset = visibleRange.left;
if (request.mouseContentHorizontalOffset === columnHorizontalOffset) {
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos);
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: false });
}
// Let's define a, b, c and check if the offset is in between them...
@ -803,21 +801,25 @@ export class MouseTargetFactory {
points.sort((a, b) => a.offset - b.offset);
const mouseCoordinates = request.pos.toClientCoordinates();
const spanNodeClientRect = spanNode.getBoundingClientRect();
const mouseIsOverSpanNode = (spanNodeClientRect.left <= mouseCoordinates.clientX && mouseCoordinates.clientX <= spanNodeClientRect.right);
for (let i = 1; i < points.length; i++) {
const prev = points[i - 1];
const curr = points[i];
if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) {
const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column);
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng);
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode });
}
}
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos);
return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, null, { mightBeForeignElement: !mouseIsOverSpanNode });
}
/**
* Most probably WebKit browsers and Edge
*/
private static _doHitTestWithCaretRangeFromPoint(ctx: HitTestContext, request: BareHitTestRequest): IHitTestResult {
private static _doHitTestWithCaretRangeFromPoint(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult {
// In Chrome, especially on Linux it is possible to click between lines,
// so try to adjust the `hity` below so that it lands in the center of a line
@ -836,7 +838,7 @@ export class MouseTargetFactory {
const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY);
const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates());
if (r.position) {
if (r.type === HitTestResultType.Content) {
return r;
}
@ -844,7 +846,7 @@ export class MouseTargetFactory {
return this._actualDoHitTestWithCaretRangeFromPoint(ctx, request.pos.toClientCoordinates());
}
private static _actualDoHitTestWithCaretRangeFromPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult {
private static _actualDoHitTestWithCaretRangeFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult {
const shadowRoot = dom.getShadowRoot(ctx.viewDomNode);
let range: Range;
if (shadowRoot) {
@ -858,15 +860,11 @@ export class MouseTargetFactory {
}
if (!range || !range.startContainer) {
return {
position: null,
hitTarget: null
};
return new UnknownHitTestResult();
}
// Chrome always hits a TEXT_NODE, while Edge sometimes hits a token span
const startContainer = range.startContainer;
let hitTarget: HTMLElement | null = null;
if (startContainer.nodeType === startContainer.TEXT_NODE) {
// startContainer is expected to be the token text
@ -876,13 +874,9 @@ export class MouseTargetFactory {
const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (<HTMLElement>parent3).className : null;
if (parent3ClassName === ViewLine.CLASS_NAME) {
const p = ctx.getPositionFromDOMInfo(<HTMLElement>parent1, range.startOffset);
return {
position: p,
hitTarget: null
};
return HitTestResult.createFromDOMInfo(ctx, <HTMLElement>parent1, range.startOffset);
} else {
hitTarget = <HTMLElement>startContainer.parentNode;
return new UnknownHitTestResult(<HTMLElement>startContainer.parentNode);
}
} else if (startContainer.nodeType === startContainer.ELEMENT_NODE) {
// startContainer is expected to be the token span
@ -891,26 +885,19 @@ export class MouseTargetFactory {
const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (<HTMLElement>parent2).className : null;
if (parent2ClassName === ViewLine.CLASS_NAME) {
const p = ctx.getPositionFromDOMInfo(<HTMLElement>startContainer, (<HTMLElement>startContainer).textContent!.length);
return {
position: p,
hitTarget: null
};
return HitTestResult.createFromDOMInfo(ctx, <HTMLElement>startContainer, (<HTMLElement>startContainer).textContent!.length);
} else {
hitTarget = <HTMLElement>startContainer;
return new UnknownHitTestResult(<HTMLElement>startContainer);
}
}
return {
position: null,
hitTarget: hitTarget
};
return new UnknownHitTestResult();
}
/**
* Most probably Gecko
*/
private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult {
private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult {
const hitResult: { offsetNode: Node; offset: number; } = (<any>document).caretPositionFromPoint(coords.clientX, coords.clientY);
if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) {
@ -921,16 +908,9 @@ export class MouseTargetFactory {
const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (<HTMLElement>parent3).className : null;
if (parent3ClassName === ViewLine.CLASS_NAME) {
const p = ctx.getPositionFromDOMInfo(<HTMLElement>hitResult.offsetNode.parentNode, hitResult.offset);
return {
position: p,
hitTarget: null
};
return HitTestResult.createFromDOMInfo(ctx, <HTMLElement>hitResult.offsetNode.parentNode, hitResult.offset);
} else {
return {
position: null,
hitTarget: <HTMLElement>hitResult.offsetNode.parentNode
};
return new UnknownHitTestResult(<HTMLElement>hitResult.offsetNode.parentNode);
}
}
@ -946,26 +926,15 @@ export class MouseTargetFactory {
// it returned the `<span>` of the line and the offset is the `<span>` with the inline decoration
const tokenSpan = hitResult.offsetNode.childNodes[Math.min(hitResult.offset, hitResult.offsetNode.childNodes.length - 1)];
if (tokenSpan) {
const p = ctx.getPositionFromDOMInfo(<HTMLElement>tokenSpan, 0);
return {
position: p,
hitTarget: null
};
return HitTestResult.createFromDOMInfo(ctx, <HTMLElement>tokenSpan, 0);
}
} else if (parent2ClassName === ViewLine.CLASS_NAME) {
// it returned the `<span>` with the inline decoration
const p = ctx.getPositionFromDOMInfo(<HTMLElement>hitResult.offsetNode, 0);
return {
position: p,
hitTarget: null
};
return HitTestResult.createFromDOMInfo(ctx, <HTMLElement>hitResult.offsetNode, 0);
}
}
return {
position: null,
hitTarget: <HTMLElement>hitResult.offsetNode
};
return new UnknownHitTestResult(<HTMLElement>hitResult.offsetNode);
}
private static _snapToSoftTabBoundary(position: Position, viewModel: IViewModel): Position {
@ -978,22 +947,17 @@ export class MouseTargetFactory {
return position;
}
private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): IHitTestResult {
private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult {
let result: IHitTestResult;
let result: HitTestResult = new UnknownHitTestResult();
if (typeof document.caretRangeFromPoint === 'function') {
result = this._doHitTestWithCaretRangeFromPoint(ctx, request);
} else if ((<any>document).caretPositionFromPoint) {
result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
} else {
result = {
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);
if (result.type === HitTestResultType.Content && ctx.stickyTabStops) {
result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.model), result.spanNode);
}
return result;
}

View file

@ -23,6 +23,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { isIOS } from 'vs/base/common/platform';
export class ContextMenuController implements IEditorContribution {
@ -209,7 +210,7 @@ export class ContextMenuController implements IEditorContribution {
anchor = { x: posx, y: posy };
}
const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM);
const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM) && !isIOS; // Do not use shadow dom on IOS #122035
// Show menu
this._contextMenuIsBeingShownCount++;

View file

@ -8,7 +8,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { DocumentColorProvider, IColorInformation } from 'vs/editor/common/modes';
import { IIdentifiedSingleEditOperation, IModelDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
import { EditorHoverStatusBar, IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover';
import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
@ -33,10 +33,11 @@ export class ColorHover implements IHoverPart {
public readonly provider: DocumentColorProvider
) { }
public isValidForHoverRange(hoverRange: Range): boolean {
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
return (
this.range.startColumn <= hoverRange.startColumn
&& this.range.endColumn >= hoverRange.endColumn
anchor.type === HoverAnchorType.Range
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn
);
}
}
@ -49,11 +50,11 @@ export class ColorHoverParticipant implements IEditorHoverParticipant<ColorHover
@IThemeService private readonly _themeService: IThemeService,
) { }
public computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): ColorHover[] {
public computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): ColorHover[] {
return [];
}
public async computeAsync(range: Range, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<ColorHover[]> {
public async computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<ColorHover[]> {
if (!this._editor.hasModel()) {
return [];
}
@ -82,7 +83,7 @@ export class ColorHoverParticipant implements IEditorHoverParticipant<ColorHover
return new ColorHover(this, Range.lift(colorInfo.range), model, provider);
}
public renderHoverParts(hoverParts: ColorHover[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
public renderHoverParts(hoverParts: ColorHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
if (hoverParts.length === 0 || !this._editor.hasModel()) {
return Disposable.None;
}

View file

@ -216,7 +216,7 @@ export class ModesHoverController implements IEditorContribution {
}
public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void {
this._getOrCreateContentWidget().startShowingAt(range, mode, focus);
this._getOrCreateContentWidget().startShowingAtRange(range, mode, focus);
}
public dispose(): void {

View file

@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget';
import { IModelDecoration } from 'vs/editor/common/model';
export interface IHoverPart {
/**
* The creator of this hover part.
*/
readonly owner: IEditorHoverParticipant;
/**
* The range where this hover part applies.
*/
readonly range: Range;
/**
* Force the hover to always be rendered at this specific range,
* even in the case of multiple hover parts.
*/
readonly forceShowAtRange?: boolean;
isValidForHoverAnchor(anchor: HoverAnchor): boolean;
}
export interface IEditorHover {
hide(): void;
onContentsChanged(): void;
setColorPicker(widget: ColorPickerWidget): void;
}
export const enum HoverAnchorType {
Range = 1,
ForeignElement = 2
}
export class HoverRangeAnchor {
public readonly type = HoverAnchorType.Range;
constructor(
public readonly priority: number,
public readonly range: Range
) {
}
public equals(other: HoverAnchor) {
return (other.type === HoverAnchorType.Range && this.range.equalsRange(other.range));
}
public canAdoptVisibleHover(lastAnchor: HoverAnchor, showAtPosition: Position): boolean {
return (lastAnchor.type === HoverAnchorType.Range && showAtPosition.lineNumber === this.range.startLineNumber);
}
}
export class HoverForeignElementAnchor {
public readonly type = HoverAnchorType.ForeignElement;
constructor(
public readonly priority: number,
public readonly owner: IEditorHoverParticipant,
public readonly range: Range
) {
}
public equals(other: HoverAnchor) {
return (other.type === HoverAnchorType.ForeignElement && this.owner === other.owner);
}
public canAdoptVisibleHover(lastAnchor: HoverAnchor, showAtPosition: Position): boolean {
return (lastAnchor.type === HoverAnchorType.ForeignElement && this.owner === lastAnchor.owner);
}
}
export type HoverAnchor = HoverRangeAnchor | HoverForeignElementAnchor;
export interface IEditorHoverStatusBar {
addAction(actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): void;
append(element: HTMLElement): HTMLElement;
}
export interface IEditorHoverParticipant<T extends IHoverPart = IHoverPart> {
suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null;
computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): T[];
computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<T[]>;
createLoadingMessage?(anchor: HoverAnchor): T | null;
renderHoverParts(hoverParts: T[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable;
}

View file

@ -14,7 +14,7 @@ import { asArray } from 'vs/base/common/arrays';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelDecoration } from 'vs/editor/common/model';
import { EditorHoverStatusBar, IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover';
import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
import { HoverProviderRegistry } from 'vs/editor/common/modes';
import { getHover } from 'vs/editor/contrib/hover/getHover';
import { Position } from 'vs/editor/common/core/position';
@ -30,10 +30,11 @@ export class MarkdownHover implements IHoverPart {
public readonly contents: IMarkdownString[]
) { }
public isValidForHoverRange(hoverRange: Range): boolean {
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
return (
this.range.startColumn <= hoverRange.startColumn
&& this.range.endColumn >= hoverRange.endColumn
anchor.type === HoverAnchorType.Range
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn
);
}
}
@ -47,17 +48,20 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant<Markdow
@IOpenerService private readonly _openerService: IOpenerService,
) { }
public createLoadingMessage(range: Range): MarkdownHover {
return new MarkdownHover(this, range, [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]);
public createLoadingMessage(anchor: HoverAnchor): MarkdownHover | null {
if (anchor.type !== HoverAnchorType.Range) {
return null;
}
return new MarkdownHover(this, anchor.range, [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]);
}
public computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): MarkdownHover[] {
if (!this._editor.hasModel()) {
public computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] {
if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
return [];
}
const model = this._editor.getModel();
const lineNumber = hoverRange.startLineNumber;
const lineNumber = anchor.range.startLineNumber;
const maxColumn = model.getLineMaxColumn(lineNumber);
const result: MarkdownHover[] = [];
for (const d of lineDecorations) {
@ -69,15 +73,15 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant<Markdow
continue;
}
const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn);
const range = new Range(anchor.range.startLineNumber, startColumn, anchor.range.startLineNumber, endColumn);
result.push(new MarkdownHover(this, range, asArray(hoverMessage)));
}
return result;
}
public async computeAsync(range: Range, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<MarkdownHover[]> {
if (!this._editor.hasModel() || !range) {
public async computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<MarkdownHover[]> {
if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
return Promise.resolve([]);
}
@ -88,8 +92,8 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant<Markdow
}
const hovers = await getHover(model, new Position(
range.startLineNumber,
range.startColumn
anchor.range.startLineNumber,
anchor.range.startColumn
), token);
const result: MarkdownHover[] = [];
@ -97,13 +101,13 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant<Markdow
if (isEmptyMarkdownString(hover.contents)) {
continue;
}
const rng = hover.range ? Range.lift(hover.range) : range;
const rng = hover.range ? Range.lift(hover.range) : anchor.range;
result.push(new MarkdownHover(this, rng, hover.contents));
}
return result;
}
public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
const disposables = new DisposableStore();
for (const hoverPart of hoverParts) {
for (const contents of hoverPart.contents) {

View file

@ -24,7 +24,7 @@ import { IModelDecoration } from 'vs/editor/common/model';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Progress } from 'vs/platform/progress/common/progress';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { EditorHoverStatusBar, IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover';
import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
const $ = dom.$;
@ -36,10 +36,11 @@ export class MarkerHover implements IHoverPart {
public readonly marker: IMarker,
) { }
public isValidForHoverRange(hoverRange: Range): boolean {
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
return (
this.range.startColumn <= hoverRange.startColumn
&& this.range.endColumn >= hoverRange.endColumn
anchor.type === HoverAnchorType.Range
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn
);
}
}
@ -60,13 +61,13 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant<MarkerHov
@IOpenerService private readonly _openerService: IOpenerService,
) { }
public computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): MarkerHover[] {
if (!this._editor.hasModel()) {
public computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkerHover[] {
if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
return [];
}
const model = this._editor.getModel();
const lineNumber = hoverRange.startLineNumber;
const lineNumber = anchor.range.startLineNumber;
const maxColumn = model.getLineMaxColumn(lineNumber);
const result: MarkerHover[] = [];
for (const d of lineDecorations) {
@ -78,14 +79,14 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant<MarkerHov
continue;
}
const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn);
const range = new Range(anchor.range.startLineNumber, startColumn, anchor.range.startLineNumber, endColumn);
result.push(new MarkerHover(this, range, marker));
}
return result;
}
public renderHoverParts(hoverParts: MarkerHover[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
public renderHoverParts(hoverParts: MarkerHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
if (!hoverParts.length) {
return Disposable.None;
}
@ -163,7 +164,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant<MarkerHov
return hoverElement;
}
private renderMarkerStatusbar(markerHover: MarkerHover, statusBar: EditorHoverStatusBar, disposables: DisposableStore): void {
private renderMarkerStatusbar(markerHover: MarkerHover, statusBar: IEditorHoverStatusBar, disposables: DisposableStore): void {
if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) {
statusBar.addAction({
label: nls.localize('view problem', "View Problem"),

View file

@ -31,34 +31,11 @@ import { InlineCompletionsHoverParticipant } from 'vs/editor/contrib/inlineCompl
import { ColorHoverParticipant } from 'vs/editor/contrib/hover/colorHoverParticipant';
import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorHoverStatusBar, IHoverPart, HoverAnchor, IEditorHoverParticipant, HoverAnchorType, IEditorHover, HoverRangeAnchor } from 'vs/editor/contrib/hover/hoverTypes';
const $ = dom.$;
export interface IHoverPart {
/**
* The creator of this hover part.
*/
readonly owner: IEditorHoverParticipant;
/**
* The range where this hover part applies.
*/
readonly range: Range;
/**
* Force the hover to always be rendered at this specific range,
* even in the case of multiple hover parts.
*/
readonly forceShowAtRange?: boolean;
isValidForHoverRange(hoverRange: Range): boolean;
}
export interface IEditorHover {
hide(): void;
onContentsChanged(): void;
setColorPicker(widget: ColorPickerWidget): void;
}
export class EditorHoverStatusBar extends Disposable {
class EditorHoverStatusBar extends Disposable implements IEditorHoverStatusBar {
public readonly hoverElement: HTMLElement;
private readonly actionsElement: HTMLElement;
@ -90,18 +67,11 @@ export class EditorHoverStatusBar extends Disposable {
}
}
export interface IEditorHoverParticipant<T extends IHoverPart = IHoverPart> {
computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): T[];
computeAsync?(range: Range, lineDecorations: IModelDecoration[], token: CancellationToken): Promise<T[]>;
createLoadingMessage?(range: Range): T;
renderHoverParts(hoverParts: T[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable;
}
class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
private readonly _editor: ICodeEditor;
private _result: IHoverPart[];
private _range: Range | null;
private _anchor: HoverAnchor | null;
constructor(
editor: ICodeEditor,
@ -109,11 +79,11 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
) {
this._editor = editor;
this._result = [];
this._range = null;
this._anchor = null;
}
public setRange(range: Range): void {
this._range = range;
public setAnchor(anchor: HoverAnchor): void {
this._anchor = anchor;
this._result = [];
}
@ -121,9 +91,13 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
this._result = [];
}
private static _getLineDecorations(editor: IActiveCodeEditor, hoverRange: Range): IModelDecoration[] {
private static _getLineDecorations(editor: IActiveCodeEditor, anchor: HoverAnchor): IModelDecoration[] {
if (anchor.type !== HoverAnchorType.Range) {
return [];
}
const model = editor.getModel();
const lineNumber = hoverRange.startLineNumber;
const lineNumber = anchor.range.startLineNumber;
const maxColumn = model.getLineMaxColumn(lineNumber);
return editor.getLineDecorations(lineNumber).filter((d) => {
if (d.options.isWholeLine) {
@ -132,7 +106,7 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
if (startColumn > hoverRange.startColumn || hoverRange.endColumn > endColumn) {
if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) {
return false;
}
return true;
@ -140,40 +114,35 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
}
public async computeAsync(token: CancellationToken): Promise<IHoverPart[]> {
const range = this._range;
const anchor = this._anchor;
if (!this._editor.hasModel() || !range) {
if (!this._editor.hasModel() || !anchor) {
return Promise.resolve([]);
}
const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, range);
const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, anchor);
const allResults = await Promise.all(this._participants.map(p => this._computeAsync(p, lineDecorations, range, token)));
const allResults = await Promise.all(this._participants.map(p => this._computeAsync(p, lineDecorations, anchor, token)));
return flatten(allResults);
}
private async _computeAsync(participant: IEditorHoverParticipant, lineDecorations: IModelDecoration[], range: Range, token: CancellationToken): Promise<IHoverPart[]> {
private async _computeAsync(participant: IEditorHoverParticipant, lineDecorations: IModelDecoration[], anchor: HoverAnchor, token: CancellationToken): Promise<IHoverPart[]> {
if (!participant.computeAsync) {
return [];
}
return participant.computeAsync(range, lineDecorations, token);
return participant.computeAsync(anchor, lineDecorations, token);
}
public computeSync(): IHoverPart[] {
if (!this._editor.hasModel() || !this._range) {
if (!this._editor.hasModel() || !this._anchor) {
return [];
}
if (this._range.startLineNumber > this._editor.getModel().getLineCount()) {
// Illegal line number => no results
return [];
}
const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, this._range);
const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, this._anchor);
let result: IHoverPart[] = [];
for (const participant of this._participants) {
result = result.concat(participant.computeSync(this._range, lineDecorations));
result = result.concat(participant.computeSync(this._anchor, lineDecorations));
}
return coalesce(result);
@ -193,11 +162,13 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
}
public getResultWithLoadingMessage(): IHoverPart[] {
if (this._range) {
if (this._anchor) {
for (const participant of this._participants) {
if (participant.createLoadingMessage) {
const loadingMessage = participant.createLoadingMessage(this._range);
return this._result.slice(0).concat([loadingMessage]);
const loadingMessage = participant.createLoadingMessage(this._anchor);
if (loadingMessage) {
return this._result.slice(0).concat([loadingMessage]);
}
}
}
}
@ -209,6 +180,8 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
static readonly ID = 'editor.contrib.modesContentHoverWidget';
private readonly _participants: IEditorHoverParticipant[];
private readonly _hover: HoverWidget;
private readonly _id: string;
private readonly _editor: ICodeEditor;
@ -221,7 +194,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
public readonly allowEditorOverflow = true;
private _messages: IHoverPart[];
private _lastRange: Range | null;
private _lastAnchor: HoverAnchor | null;
private readonly _computer: ModesContentComputer;
private readonly _hoverOperation: HoverOperation<IHoverPart[]>;
private _highlightDecorations: string[];
@ -238,7 +211,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
) {
super();
const participants = [
this._participants = [
instantiationService.createInstance(ColorHoverParticipant, editor, this),
instantiationService.createInstance(MarkdownHoverParticipant, editor, this),
instantiationService.createInstance(InlineCompletionsHoverParticipant, editor, this),
@ -273,8 +246,8 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
this._stoleFocus = false;
this._messages = [];
this._lastRange = null;
this._computer = new ModesContentComputer(this._editor, participants);
this._lastAnchor = null;
this._computer = new ModesContentComputer(this._editor, this._participants);
this._highlightDecorations = [];
this._isChangingDecorations = false;
this._shouldFocus = false;
@ -300,9 +273,9 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay);
}));
this._register(TokenizationRegistry.onDidChange(() => {
if (this._isVisible && this._lastRange && this._messages.length > 0) {
if (this._isVisible && this._lastAnchor && this._messages.length > 0) {
this._hover.contentsDomNode.textContent = '';
this._renderMessages(this._lastRange, this._messages);
this._renderMessages(this._lastAnchor, this._messages);
}
}));
}
@ -340,25 +313,37 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
}
public maybeShowAt(mouseEvent: IEditorMouseEvent): boolean {
if (!this._shouldShowAt(mouseEvent)) {
const anchorCandidates: HoverAnchor[] = [];
for (const participant of this._participants) {
if (typeof participant.suggestHoverAnchor === 'function') {
const anchor = participant.suggestHoverAnchor(mouseEvent);
if (anchor) {
anchorCandidates.push(anchor);
}
}
}
if (this._shouldShowAt(mouseEvent) && mouseEvent.target.range) {
// TODO@rebornix. This should be removed if we move Color Picker out of Hover component.
// Check if mouse is hovering on color decorator
const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox'))
&& mouseEvent.target.range.endColumn - mouseEvent.target.range.startColumn === 1;
const showAtRange = (
hoverOnColorDecorator // shift the mouse focus by one as color decorator is a `before` decoration of next character.
? new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1)
: mouseEvent.target.range
);
anchorCandidates.push(new HoverRangeAnchor(0, showAtRange));
}
if (anchorCandidates.length === 0) {
return false;
}
if (!mouseEvent.target.range) {
return false;
}
anchorCandidates.sort((a, b) => b.priority - a.priority);
this._startShowingAt(anchorCandidates[0], HoverStartMode.Delayed, false);
// TODO@rebornix. This should be removed if we move Color Picker out of Hover component.
// Check if mouse is hovering on color decorator
const hoverOnColorDecorator = [...mouseEvent.target.element?.classList.values() || []].find(className => className.startsWith('ced-colorBox'))
&& mouseEvent.target.range.endColumn - mouseEvent.target.range.startColumn === 1;
const showAtRange = (
hoverOnColorDecorator // shift the mouse focus by one as color decorator is a `before` decoration of next character.
? new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1)
: mouseEvent.target.range
);
this.startShowingAt(showAtRange, HoverStartMode.Delayed, false);
return true;
}
@ -434,8 +419,12 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
}
}
public startShowingAt(range: Range, mode: HoverStartMode, focus: boolean): void {
if (this._lastRange && this._lastRange.equalsRange(range)) {
public startShowingAtRange(range: Range, mode: HoverStartMode, focus: boolean): void {
this._startShowingAt(new HoverRangeAnchor(0, range), mode, focus);
}
private _startShowingAt(anchor: HoverAnchor, mode: HoverStartMode, focus: boolean): void {
if (this._lastAnchor && this._lastAnchor.equals(anchor)) {
// We have to show the widget at the exact same range as before, so no work is needed
return;
}
@ -446,29 +435,29 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
// The range might have changed, but the hover is visible
// Instead of hiding it completely, filter out messages that are still in the new range and
// kick off a new computation
if (!this._showAtPosition || this._showAtPosition.lineNumber !== range.startLineNumber) {
if (!this._showAtPosition || !this._lastAnchor || !anchor.canAdoptVisibleHover(this._lastAnchor, this._showAtPosition)) {
this.hide();
} else {
const filteredMessages = this._messages.filter((m) => m.isValidForHoverRange(range));
const filteredMessages = this._messages.filter((m) => m.isValidForHoverAnchor(anchor));
if (filteredMessages.length === 0) {
this.hide();
} else if (filteredMessages.length === this._messages.length) {
// no change
return;
} else {
this._renderMessages(range, filteredMessages);
this._renderMessages(anchor, filteredMessages);
}
}
}
this._lastRange = range;
this._computer.setRange(range);
this._lastAnchor = anchor;
this._computer.setAnchor(anchor);
this._shouldFocus = focus;
this._hoverOperation.start(mode);
}
public hide(): void {
this._lastRange = null;
this._lastAnchor = null;
this._hoverOperation.cancel();
if (this._isVisible) {
@ -512,14 +501,14 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
private _withResult(result: IHoverPart[], complete: boolean): void {
this._messages = result;
if (this._lastRange && this._messages.length > 0) {
this._renderMessages(this._lastRange, this._messages);
if (this._lastAnchor && this._messages.length > 0) {
this._renderMessages(this._lastAnchor, this._messages);
} else if (complete) {
this.hide();
}
}
private _renderMessages(renderRange: Range, messages: IHoverPart[]): void {
private _renderMessages(anchor: HoverAnchor, messages: IHoverPart[]): void {
if (this._renderDisposable) {
this._renderDisposable.dispose();
this._renderDisposable = null;
@ -567,7 +556,7 @@ export class ModesContentHoverWidget extends Widget implements IContentWidget, I
if (forceShowAtRange) {
this._showAt(forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus);
} else {
this._showAt(new Position(renderRange.startLineNumber, renderColumn), highlightRange, this._shouldFocus);
this._showAt(new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus);
}
this._updateContents(fragment);
}

View file

@ -75,6 +75,10 @@ export class GhostTextController extends Disposable {
return this.activeController.value?.shouldShowHoverAt(hoverRange) || false;
}
public shouldShowHoverAtViewZone(viewZoneId: string): boolean {
return this.widget.shouldShowHoverAtViewZone(viewZoneId);
}
public trigger(): void {
this.triggeredExplicitly = true;
if (!this.activeController.value) {

View file

@ -111,6 +111,10 @@ export class GhostTextWidget extends Disposable {
return this.modelRef.value?.object;
}
public shouldShowHoverAtViewZone(viewZoneId: string): boolean {
return (this.viewZoneId === viewZoneId);
}
public setModel(model: GhostTextWidgetModel | undefined): void {
if (model === this.model) { return; }
this.modelRef.value = model

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { EditorHoverStatusBar, IEditorHover, IEditorHoverParticipant, IHoverPart } from 'vs/editor/contrib/hover/modesContentHover';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { IModelDecoration } from 'vs/editor/common/model';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
@ -13,6 +13,7 @@ import { GhostTextController, ShowNextInlineCompletionAction, ShowPreviousInline
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
export class InlineCompletionsHover implements IHoverPart {
constructor(
@ -20,10 +21,11 @@ export class InlineCompletionsHover implements IHoverPart {
public readonly range: Range
) { }
public isValidForHoverRange(hoverRange: Range): boolean {
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
return (
this.range.startColumn <= hoverRange.startColumn
&& this.range.endColumn >= hoverRange.endColumn
anchor.type === HoverAnchorType.Range
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn
);
}
}
@ -37,15 +39,40 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
) { }
computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): InlineCompletionsHover[] {
suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {
const controller = GhostTextController.get(this._editor);
if (controller.shouldShowHoverAt(hoverRange)) {
return [new InlineCompletionsHover(this, hoverRange)];
if (!controller) {
return null;
}
if (mouseEvent.target.type === MouseTargetType.CONTENT_VIEW_ZONE) {
// handle the case where the mouse is over the view zone
const viewZoneData = <IViewZoneData>mouseEvent.target.detail;
if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) {
return new HoverForeignElementAnchor(1000, this, Range.fromPositions(viewZoneData.positionBefore || viewZoneData.position, viewZoneData.positionBefore || viewZoneData.position));
}
}
if (mouseEvent.target.type === MouseTargetType.CONTENT_EMPTY) {
// handle the case where the mouse is over the empty portion of a line following ghost text
if (mouseEvent.target.range && controller.shouldShowHoverAt(mouseEvent.target.range)) {
return new HoverForeignElementAnchor(1000, this, mouseEvent.target.range);
}
}
// let mightBeForeignElement = false;
// if (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT && mouseEvent.target.detail) {
// mightBeForeignElement = (<ITextContentData>mouseEvent.target.detail).mightBeForeignElement;
// }
return null;
}
computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): InlineCompletionsHover[] {
const controller = GhostTextController.get(this._editor);
if (controller && controller.shouldShowHoverAt(anchor.range)) {
return [new InlineCompletionsHover(this, anchor.range)];
}
return [];
}
renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
const menu = this._menuService.createMenu(
MenuId.InlineCompletionsActions,
this._contextKeyService

View file

@ -8,6 +8,7 @@ import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { findFreePortFaster } from 'vs/base/node/ports';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
@ -15,8 +16,8 @@ import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/t
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
return tunnel.waitForReady();
}
@ -38,7 +39,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
private readonly _socketsDispose: Map<string, () => void> = new Map();
constructor(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
constructor(options: IConnectionOptions, private readonly defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
super();
this._options = options;
this._server = net.createServer();
@ -76,14 +77,14 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
// if that fails, the method above returns 0, which works out fine below...
let address: string | net.AddressInfo | null = null;
this._server.listen(localPort, '127.0.0.1');
this._server.listen(localPort, this.defaultTunnelHost);
await this._barrier.wait();
address = <net.AddressInfo>this._server.address();
// It is possible for findFreePortFaster to return a port that there is already a server listening on. This causes the previous listen call to error out.
if (!address) {
localPort = 0;
this._server.listen(localPort, '127.0.0.1');
this._server.listen(localPort, this.defaultTunnelHost);
await this._barrier.wait();
address = <net.AddressInfo>this._server.address();
}
@ -137,11 +138,16 @@ export class BaseTunnelService extends AbstractTunnelService {
private readonly socketFactory: ISocketFactory,
@ILogService logService: ILogService,
@ISignService private readonly signService: ISignService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(logService);
}
private get defaultTunnelHost(): string {
return (this.configurationService.getValue('remote.localPortHost') === 'localhost') ? '127.0.0.1' : '0.0.0.0';
}
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
@ -162,7 +168,7 @@ export class BaseTunnelService extends AbstractTunnelService {
ipcLogger: null
};
const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort);
const tunnel = createRemoteTunnel(options, this.defaultTunnelHost, remoteHost, remotePort, localPort);
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
this.addTunnelToMap(remoteHost, remotePort, tunnel);
return tunnel;
@ -174,8 +180,9 @@ export class TunnelService extends BaseTunnelService {
public constructor(
@ILogService logService: ILogService,
@ISignService signService: ISignService,
@IProductService productService: IProductService
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService
) {
super(nodeSocketFactory, logService, signService, productService);
super(nodeSocketFactory, logService, signService, productService, configurationService);
}
}

View file

@ -85,7 +85,6 @@ export const enum TerminalSettingId {
LocalEchoExcludePrograms = 'terminal.integrated.localEchoExcludePrograms',
LocalEchoStyle = 'terminal.integrated.localEchoStyle',
EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions',
AllowWorkspaceConfiguration = 'terminal.integrated.allowWorkspaceConfiguration',
InheritEnv = 'terminal.integrated.inheritEnv'
}
@ -583,8 +582,6 @@ export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimension
forceExactSize?: boolean;
}
export type SafeConfigProvider = <T>(key: string) => T | undefined;
export const enum ProfileSource {
GitBash = 'Git Bash',
Pwsh = 'PowerShell'

View file

@ -296,12 +296,6 @@ const terminalPlatformConfiguration: IConfigurationNode = {
type: 'boolean',
default: true
},
[TerminalSettingId.AllowWorkspaceConfiguration]: {
scope: ConfigurationScope.APPLICATION,
description: localize('terminal.integrated.allowWorkspaceConfiguration', "Allows shell and profile settings to be pick up from a workspace."),
type: 'boolean',
default: false
},
[TerminalSettingId.InheritEnv]: {
scope: ConfigurationScope.APPLICATION,
description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."),

View file

@ -5,7 +5,7 @@
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType, ITerminalProfile, IRequestResolveVariablesEvent, SafeConfigProvider, TerminalSettingId, TitleEventSource, TerminalIcon, IReconnectConstants } from 'vs/platform/terminal/common/terminal';
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType, ITerminalProfile, IRequestResolveVariablesEvent, TitleEventSource, TerminalIcon, IReconnectConstants } from 'vs/platform/terminal/common/terminal';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { FileAccess } from 'vs/base/common/network';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
@ -223,7 +223,7 @@ export class PtyHostService extends Disposable implements IPtyService {
return this._proxy.getDefaultSystemShell(osOverride);
}
async getProfiles(includeDetectedProfiles: boolean = false): Promise<ITerminalProfile[]> {
return detectAvailableProfiles(includeDetectedProfiles, this._buildSafeConfigProvider(), undefined, this._logService, this._resolveVariables.bind(this));
return detectAvailableProfiles(includeDetectedProfiles, this._configurationService, undefined, this._logService, this._resolveVariables.bind(this));
}
getEnvironment(): Promise<IProcessEnvironment> {
return this._proxy.getEnvironment();
@ -326,21 +326,4 @@ export class PtyHostService extends Disposable implements IPtyService {
this._logService.warn(`Resolved variables received without matching request ${id}`);
}
}
private _buildSafeConfigProvider(): SafeConfigProvider {
return (key: string) => {
const isWorkspaceConfigAllowed = this._configurationService.getValue(TerminalSettingId.AllowWorkspaceConfiguration);
if (isWorkspaceConfigAllowed) {
return this._configurationService.getValue(key) as any;
}
const inspected = this._configurationService.inspect(key);
if (!inspected) {
return undefined;
}
if (inspected.userValue && typeof inspected.userValue === 'object' && inspected.defaultValue && typeof inspected.defaultValue === 'object') {
return { ...inspected.defaultValue, ...inspected.userValue };
}
return inspected?.userValue || inspected?.defaultValue;
};
}
}

View file

@ -9,17 +9,18 @@ import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node
import * as cp from 'child_process';
import { ILogService } from 'vs/platform/log/common/log';
import * as pfs from 'vs/base/node/pfs';
import { ITerminalEnvironment, ITerminalProfile, ITerminalProfileObject, ProfileSource, SafeConfigProvider, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ITerminalEnvironment, ITerminalProfile, ITerminalProfileObject, ProfileSource, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { Codicon } from 'vs/base/common/codicons';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
let profileSources: Map<string, IPotentialTerminalProfile> | undefined;
export function detectAvailableProfiles(
includeDetectedProfiles: boolean,
safeConfigProvider: SafeConfigProvider,
configurationService: IConfigurationService,
fsProvider?: IFsProvider,
logService?: ILogService,
variableResolver?: (text: string[]) => Promise<string[]>,
@ -34,9 +35,9 @@ export function detectAvailableProfiles(
includeDetectedProfiles,
fsProvider,
logService,
safeConfigProvider<boolean>(TerminalSettingId.UseWslProfiles) !== false,
safeConfigProvider(TerminalSettingId.ProfilesWindows),
safeConfigProvider(TerminalSettingId.DefaultProfileWindows),
configurationService.getValue<boolean>(TerminalSettingId.UseWslProfiles) !== false,
configurationService.getValue(TerminalSettingId.ProfilesWindows),
configurationService.getValue(TerminalSettingId.DefaultProfileWindows),
testPaths,
variableResolver
);
@ -45,8 +46,8 @@ export function detectAvailableProfiles(
fsProvider,
logService,
includeDetectedProfiles,
safeConfigProvider(isMacintosh ? TerminalSettingId.ProfilesMacOs : TerminalSettingId.ProfilesLinux),
safeConfigProvider(isMacintosh ? TerminalSettingId.DefaultProfileMacOs : TerminalSettingId.DefaultProfileLinux),
configurationService.getValue(isMacintosh ? TerminalSettingId.ProfilesMacOs : TerminalSettingId.ProfilesLinux),
configurationService.getValue(isMacintosh ? TerminalSettingId.DefaultProfileMacOs : TerminalSettingId.DefaultProfileLinux),
testPaths,
variableResolver
);

View file

@ -241,6 +241,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
defaultSnippets: [{ body: { onAutoForward: 'ignore' } }],
markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"),
additionalProperties: false
},
'remote.localPortHost': {
type: 'string',
enum: ['localhost', 'allInterfaces'],
default: 'localhost',
description: localize('remote.localPortHost', "Specifies the local host name that will be used for port forwarding.")
}
}
});

View file

@ -240,7 +240,8 @@
}
.monaco-workbench .pane-body.integrated-terminal .tabs-container.has-text .tabs-list .terminal-tabs-entry .monaco-icon-label::after {
padding-right: 0
padding-right: 0;
padding-left: 0;
}
.monaco-workbench .pane-body.integrated-terminal .tabs-container:not(.has-text) .terminal-tabs-entry .codicon {

View file

@ -359,7 +359,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
async showProfileMigrationNotification(): Promise<void> {
const platform = this._getPlatformKey();
const shouldMigrateToProfile = (!!this._configurationService.getValue(TerminalSettingPrefix.Shell + platform) ||
!!this._configurationService.getValue(TerminalSettingPrefix.ShellArgs + platform)) &&
!!this._configurationService.inspect(TerminalSettingPrefix.ShellArgs + platform).userValue) &&
!!this._configurationService.getValue(TerminalSettingPrefix.DefaultProfile + platform);
if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true)) {
this._notificationService.prompt(
@ -374,8 +374,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const profile = await this._terminalProfileResolverService.createProfileFromShellAndShellArgs(shell, shellArgs);
if (profile) {
this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile.profileName);
this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, null);
this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, null);
this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, undefined);
this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, undefined);
this._logService.trace(`migrated from shell/shellArgs, ${shell} ${shellArgs} to profile ${JSON.stringify(profile)}`);
} else {
this._logService.trace('migration from shell/shellArgs to profile did not occur bc created profile was an exact match for existing one', shell, shellArgs);

View file

@ -26,7 +26,7 @@ import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminal
import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder';
import { localize } from 'vs/nls';
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { IProcessEnvironment, OperatingSystem, OS } from 'vs/base/common/platform';
import { IProcessEnvironment, isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICompleteTerminalConfiguration } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
@ -240,19 +240,19 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
});
const terminalConfig = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
const configuration: ICompleteTerminalConfiguration = {
'terminal.integrated.automationShell.windows': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.AutomationShellWindows) as string,
'terminal.integrated.automationShell.osx': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.AutomationShellMacOs) as string,
'terminal.integrated.automationShell.linux': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.AutomationShellLinux) as string,
'terminal.integrated.shell.windows': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellWindows) as string,
'terminal.integrated.shell.osx': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellMacOs) as string,
'terminal.integrated.shell.linux': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellLinux) as string,
'terminal.integrated.shellArgs.windows': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellArgsWindows) as string | string[],
'terminal.integrated.shellArgs.osx': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellArgsMacOs) as string | string[],
'terminal.integrated.shellArgs.linux': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.ShellArgsLinux) as string | string[],
'terminal.integrated.env.windows': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.EnvWindows) as ITerminalEnvironment,
'terminal.integrated.env.osx': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.EnvMacOs) as ITerminalEnvironment,
'terminal.integrated.env.linux': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.EnvLinux) as ITerminalEnvironment,
'terminal.integrated.cwd': this._terminalProfileResolverService.getSafeConfigValueFullKey(TerminalSettingId.Cwd) as string,
'terminal.integrated.automationShell.windows': this._configurationService.getValue(TerminalSettingId.AutomationShellWindows) as string,
'terminal.integrated.automationShell.osx': this._configurationService.getValue(TerminalSettingId.AutomationShellMacOs) as string,
'terminal.integrated.automationShell.linux': this._configurationService.getValue(TerminalSettingId.AutomationShellLinux) as string,
'terminal.integrated.shell.windows': this._configurationService.getValue(TerminalSettingId.ShellWindows) as string,
'terminal.integrated.shell.osx': this._configurationService.getValue(TerminalSettingId.ShellMacOs) as string,
'terminal.integrated.shell.linux': this._configurationService.getValue(TerminalSettingId.ShellLinux) as string,
'terminal.integrated.shellArgs.windows': this._configurationService.getValue(TerminalSettingId.ShellArgsWindows) as string | string[],
'terminal.integrated.shellArgs.osx': this._configurationService.getValue(TerminalSettingId.ShellArgsMacOs) as string | string[],
'terminal.integrated.shellArgs.linux': this._configurationService.getValue(TerminalSettingId.ShellArgsLinux) as string | string[],
'terminal.integrated.env.windows': this._configurationService.getValue(TerminalSettingId.EnvWindows) as ITerminalEnvironment,
'terminal.integrated.env.osx': this._configurationService.getValue(TerminalSettingId.EnvMacOs) as ITerminalEnvironment,
'terminal.integrated.env.linux': this._configurationService.getValue(TerminalSettingId.EnvLinux) as ITerminalEnvironment,
'terminal.integrated.cwd': this._configurationService.getValue(TerminalSettingId.Cwd) as string,
'terminal.integrated.detectLocale': terminalConfig.detectLocale
};
newProcess = await this._remoteTerminalService.createProcess(shellLaunchConfig, configuration, activeWorkspaceRootUri, cols, rows, shouldPersist, this._configHelper);
@ -354,9 +354,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
// Fetch any extension environment additions and apply them
private async _setupEnvVariableInfo(variableResolver: terminalEnvironment.VariableResolver | undefined, shellLaunchConfig: IShellLaunchConfig): Promise<IProcessEnvironment> {
// const platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
// this._configurationService.getValue<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const envFromConfigValue = this._terminalProfileResolverService.getSafeConfigValue('env', OS) as ITerminalEnvironment | undefined;
const platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
const envFromConfigValue = this._configurationService.getValue<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
this._configHelper.showRecommendations(shellLaunchConfig);
let baseEnv: IProcessEnvironment;

View file

@ -205,7 +205,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
private _getUnresolvedRealDefaultProfile(os: OperatingSystem): ITerminalProfile | undefined {
const defaultProfileName = this.getSafeConfigValue('defaultProfile', os);
const defaultProfileName = this._configurationService.getValue(`terminal.integrated.defaultProfile.${this._getOsKey(os)}`);
if (defaultProfileName && typeof defaultProfileName === 'string') {
return this._terminalService.availableProfiles.find(e => e.profileName === defaultProfileName);
}
@ -213,9 +213,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
private async _getUnresolvedShellSettingDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile | undefined> {
let executable = this.getSafeConfigValue('shell', options.os) as string | null;
if (!this._isValidShell(executable) && !this.getSafeConfigValue('shellArgs', options.os, false)) {
return undefined;
let executable = this._configurationService.getValue<string>(`terminal.integrated.shell.${this._getOsKey(options.os)}`);
if (!this._isValidShell(executable)) {
const shellArgs = this._configurationService.inspect(`terminal.integrated.shellArgs.${this._getOsKey(options.os)}`);
// && !this.getSafeConfigValue('shellArgs', options.os, false)) {
if (!shellArgs.userValue && !shellArgs.workspaceValue) {
return undefined;
}
}
if (!executable || !this._isValidShell(executable)) {
@ -223,7 +227,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
let args: string | string[] | undefined;
const shellArgsSetting = this.getSafeConfigValue('shellArgs', options.os);
const shellArgsSetting = this._configurationService.getValue(`terminal.integrated.shellArgs.${this._getOsKey(options.os)}`);
if (this._isValidShellArgs(shellArgsSetting, options.os)) {
args = shellArgsSetting;
}
@ -279,7 +283,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
private _getUnresolvedAutomationShellProfile(options: IShellLaunchConfigResolveOptions): ITerminalProfile | undefined {
const automationShell = this.getSafeConfigValue('automationShell', options.os);
const automationShell = this._configurationService.getValue(`terminal.integrated.automationShell.${this._getOsKey(options.os)}`);
if (!automationShell || typeof automationShell !== 'string') {
return undefined;
}
@ -385,32 +389,6 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
return false;
}
// TODO: Remove when workspace trust is enabled
getSafeConfigValue(key: string, os: OperatingSystem, useDefaultValue: boolean = true): unknown | undefined {
return this.getSafeConfigValueFullKey(`terminal.integrated.${key}.${this._getOsKey(os)}`, useDefaultValue);
}
getSafeConfigValueFullKey(key: string, useDefaultValue: boolean = true): unknown | undefined {
const isWorkspaceConfigAllowed = this._configurationService.getValue(TerminalSettingId.AllowWorkspaceConfiguration);
const config = this._configurationService.inspect(key);
let value: unknown | undefined;
if (isWorkspaceConfigAllowed) {
value = config.user?.value || config.workspace?.value;
} else {
value = config.user?.value;
}
if (value === undefined && useDefaultValue) {
value = config.default?.value;
}
// Clone if needed to allow extensibility
if (Array.isArray(value)) {
return value.slice();
}
if (value !== null && typeof value === 'object') {
return { ...value };
}
return value;
}
async createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined> {
const detectedProfile = this._terminalService.availableProfiles?.find(p => p.path === shell);
const fallbackProfile = (await this.getDefaultProfile({

View file

@ -110,10 +110,6 @@ export interface ITerminalProfileResolverService {
getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string>;
getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]>;
getEnvironment(remoteAuthority: string | undefined): Promise<IProcessEnvironment>;
// TODO: Remove when workspace trust is enabled
getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined;
getSafeConfigValueFullKey(key: string): unknown | undefined;
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined>;
}
@ -209,7 +205,6 @@ export interface ITerminalConfiguration {
focusMode: 'singleClick' | 'doubleClick';
},
bellDuration: number;
allowWorkspaceConfiguration: boolean;
}
export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray<string> = ['vim', 'vi', 'nano', 'tmux'];

View file

@ -5,9 +5,10 @@
import { deepStrictEqual, fail, ok, strictEqual } from 'assert';
import { isWindows } from 'vs/base/common/platform';
import { ITerminalProfile, ProfileSource, SafeConfigProvider } from 'vs/platform/terminal/common/terminal';
import { ITerminalProfile, ProfileSource } from 'vs/platform/terminal/common/terminal';
import { ITerminalConfiguration, ITerminalProfiles } from 'vs/workbench/contrib/terminal/common/terminal';
import { detectAvailableProfiles, IFsProvider } from 'vs/platform/terminal/node/terminalProfiles';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
/**
* Assets that two profiles objects are equal, this will treat explicit undefined and unset
@ -26,21 +27,6 @@ function profilesEqual(actualProfiles: ITerminalProfile[], expectedProfiles: ITe
}
}
function buildTestSafeConfigProvider(config: ITestTerminalConfig): SafeConfigProvider {
return (key: string) => {
switch (key) {
case 'terminal.integrated.profiles.linux': return config.profiles.linux as any;
case 'terminal.integrated.profiles.osx': return config.profiles.osx as any;
case 'terminal.integrated.profiles.windows': return config.profiles.windows as any;
case 'terminal.integrated.defaultProfile.linux': return Object.keys(config.profiles.linux)?.[0];
case 'terminal.integrated.defaultProfile.osx': return Object.keys(config.profiles.osx)?.[0];
case 'terminal.integrated.defaultProfile.windows': return Object.keys(config.profiles.windows)?.[0];
case 'terminal.integrated.useWslProfiles': return config.useWslProfiles;
default: throw new Error('Unexpected config key');
}
};
}
suite('Workbench - TerminalProfiles', () => {
suite('detectAvailableProfiles', () => {
if (isWindows) {
@ -58,7 +44,8 @@ suite('Workbench - TerminalProfiles', () => {
},
useWslProfiles: false
};
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(config), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: config } });
const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined);
const expected = [
{ profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: ['--login'], isDefault: true }
];
@ -78,7 +65,8 @@ suite('Workbench - TerminalProfiles', () => {
},
useWslProfiles: false
};
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(config), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: config } });
const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined);
const expected = [
{ profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, args: ['-NoProfile'], isDefault: true }
];
@ -98,7 +86,8 @@ suite('Workbench - TerminalProfiles', () => {
},
useWslProfiles: false
};
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(config), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: config } });
const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined);
const expected = [{ profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: [], isAutoDetected: undefined, overrideName: undefined, isDefault: true }];
profilesEqual(profiles, expected);
});
@ -120,7 +109,8 @@ suite('Workbench - TerminalProfiles', () => {
'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe',
'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
];
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths);
const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } });
const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths);
const expected = [
{ profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true }
];
@ -133,7 +123,8 @@ suite('Workbench - TerminalProfiles', () => {
'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe',
'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
];
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths);
const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } });
const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths);
const expected = [
{ profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true }
];
@ -144,7 +135,8 @@ suite('Workbench - TerminalProfiles', () => {
'C:\\Windows\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe',
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
];
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths);
const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } });
const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths);
strictEqual(profiles.length, 1);
strictEqual(profiles[0].profileName, 'PowerShell');
});
@ -188,7 +180,8 @@ suite('Workbench - TerminalProfiles', () => {
'/bin/fakeshell1',
'/bin/fakeshell3'
]);
const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(absoluteConfig), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: absoluteConfig } });
const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined);
const expected: ITerminalProfile[] = [
{ profileName: 'fakeshell1', path: '/bin/fakeshell1', isDefault: true },
{ profileName: 'fakeshell3', path: '/bin/fakeshell3', isDefault: true }
@ -200,7 +193,8 @@ suite('Workbench - TerminalProfiles', () => {
'/bin/fakeshell1',
'/bin/fakeshell3'
], '/bin/fakeshell1\n/bin/fakeshell3');
const profiles = await detectAvailableProfiles(true, buildTestSafeConfigProvider(onPathConfig), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } });
const profiles = await detectAvailableProfiles(true, configurationService, fsProvider, undefined, undefined, undefined);
const expected: ITerminalProfile[] = [
{ profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true },
{ profileName: 'fakeshell3', path: 'fakeshell3', isDefault: true }
@ -212,7 +206,8 @@ suite('Workbench - TerminalProfiles', () => {
const fsProvider = createFsProvider([
'/bin/fakeshell1'
], '/bin/fakeshell1\n/bin/fakeshell3');
const profiles = await detectAvailableProfiles(true, buildTestSafeConfigProvider(onPathConfig), fsProvider, undefined, undefined, undefined);
const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } });
const profiles = await detectAvailableProfiles(true, configurationService, fsProvider, undefined, undefined, undefined);
const expected: ITerminalProfile[] = [
{ profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true }
];

View file

@ -13,6 +13,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { URI } from 'vs/base/common/uri';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class TunnelService extends BaseTunnelService {
public constructor(
@ -20,9 +21,10 @@ export class TunnelService extends BaseTunnelService {
@ISignService signService: ISignService,
@IProductService productService: IProductService,
@IRemoteAgentService _remoteAgentService: IRemoteAgentService,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@IConfigurationService configurationService: IConfigurationService
) {
super(nodeSocketFactory, logService, signService, productService);
super(nodeSocketFactory, logService, signService, productService, configurationService);
}
override canTunnel(uri: URI): boolean {