Merge remote-tracking branch 'origin/main' into alex/ghost-text
This commit is contained in:
commit
ea02f214c7
24 changed files with 421 additions and 381 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
87
src/vs/editor/contrib/hover/hoverTypes.ts
Normal file
87
src/vs/editor/contrib/hover/hoverTypes.ts
Normal 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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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 }
|
||||
];
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue