Merge remote-tracking branch 'origin/main' into tyriar/terminal_layout

This commit is contained in:
Daniel Imms 2021-11-05 07:03:24 -07:00
commit ae66110c90
77 changed files with 813 additions and 440 deletions

View file

@ -6,6 +6,9 @@ trigger:
pr: none
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
inputs:
@ -17,7 +20,7 @@ steps:
- bash: |
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
CHANNEL="G1C14HJ2F"
CHANNEL="C1C14HJ2F"
if [ "$TAG_VERSION" == "1.999.0" ]; then
MESSAGE="<!here>. Someone pushed 1.999.0 tag. Please delete it ASAP from remote and local."
@ -59,11 +62,11 @@ steps:
- bash: |
TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
CHANNEL="G1C14HJ2F"
CHANNEL="C1C14HJ2F"
MESSAGE="DefinitelyTyped/DefinitelyTyped#vscode-types-$TAG_VERSION created. Endgame champion, please open this link, examine changes and create a PR:"
LINK="https://github.com/DefinitelyTyped/DefinitelyTyped/compare/vscode-types-$TAG_VERSION?quick_pull=1&body=Updating%20VS%20Code%20Extension%20API.%20See%20https%3A%2F%2Fgithub.com%2Fmicrosoft%2Fvscode%2Fissues%2F70175%20for%20details."
MESSAGE2="[@eamodio, @jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
MESSAGE2="[@jrieken, @kmaetzel, @egamma]. Please review and merge PR to publish @types/vscode."
curl -X POST -H "Authorization: Bearer $(SLACK_TOKEN)" \
-H 'Content-type: application/json; charset=utf-8' \

View file

@ -1927,8 +1927,9 @@ export class Repository implements Disposable {
if (HEAD !== undefined) {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const showActionButton = config.get<string>('showUnpublishedCommitsButton', 'whenEmpty');
const postCommitCommand = config.get<string>('postCommitCommand');
if (showActionButton === 'always' || (showActionButton === 'whenEmpty' && workingTree.length === 0 && index.length === 0 && untracked.length === 0 && merge.length === 0)) {
if (showActionButton === 'always' || (showActionButton === 'whenEmpty' && workingTree.length === 0 && index.length === 0 && untracked.length === 0 && merge.length === 0 && postCommitCommand !== 'sync' && postCommitCommand !== 'push')) {
if (HEAD.name && HEAD.commit) {
if (HEAD.upstream) {
if (HEAD.ahead) {

View file

@ -179,7 +179,7 @@ class FolderDetector {
for (const filename of await fs.promises.readdir(root)) {
const ext = path.extname(filename);
if (ext !== '.js' && ext !== '.mjs' && ext !== '.cjs') {
if (ext !== '.js' && ext !== '.mjs' && ext !== '.cjs' && ext !== '.ts') {
continue;
}

View file

@ -331,7 +331,7 @@
"customEditors": [
{
"viewType": "vscode.markdown.preview.editor",
"displayName": "Markdown Preview (Experimental)",
"displayName": "Markdown Preview",
"priority": "option",
"selector": [
{

View file

@ -120,7 +120,6 @@ window.addEventListener('message', async event => {
case 'updateView':
if (event.data.source === documentResource) {
console.log('updateView', event.data.line);
onUpdateView(event.data.line);
}
return;
@ -155,7 +154,6 @@ window.addEventListener('message', async event => {
// Move styles to head
// This prevents an ugly flash of unstyled content
const styles = newRoot.querySelectorAll('link');
for (const style of styles) {
style.remove();
}
@ -165,7 +163,22 @@ window.addEventListener('message', async event => {
childrenOnly: true,
onBeforeElUpdated: (fromEl, toEl) => {
if (areEqual(fromEl, toEl)) {
fromEl.setAttribute('data-line', toEl.getAttribute('data-line')!);
// areEqual doesn't look at `data-line` so copy those over
const fromLines = fromEl.querySelectorAll('[data-line]');
const toLines = fromEl.querySelectorAll('[data-line]');
if (fromLines.length !== toLines.length) {
console.log('unexpected line number change');
}
for (let i = 0; i < fromLines.length; ++i) {
const fromChild = fromLines[i];
const toChild = toLines[i];
if (toChild) {
fromChild.setAttribute('data-line', toChild.getAttribute('data-line')!);
}
}
return false;
}
@ -241,7 +254,7 @@ document.addEventListener('click', event => {
window.addEventListener('scroll', throttle(() => {
updateScrollProgress();
console.log('scroll');
if (scrollDisabledCount > 0) {
scrollDisabledCount -= 1;
} else {

View file

@ -58,7 +58,7 @@ export function activate(
new TypeScriptVersion(
TypeScriptVersionSource.Bundled,
vscode.Uri.joinPath(context.extensionUri, 'dist/browser/typescript/tsserver.web.js').toString(),
API.fromSimpleString('4.4.1')));
API.fromSimpleString('4.5.0')));
const lazyClientHost = createLazyClientHost(context, false, {
pluginManager,

View file

@ -1,6 +1,6 @@
{
"name": "code-oss-dev",
"version": "1.62.0",
"version": "1.63.0",
"distro": "68b7698bb44bb140dfd35df9825ea6825edf87af",
"author": {
"name": "Microsoft Corporation"
@ -205,7 +205,7 @@
"vinyl-fs": "^3.0.0",
"vscode-debugprotocol": "1.50.0",
"vscode-nls-dev": "^3.3.1",
"vscode-telemetry-extractor": "^1.8.0",
"vscode-telemetry-extractor": "^1.9.3",
"webpack": "^5.42.0",
"webpack-cli": "^4.7.2",
"webpack-stream": "^6.1.2",

View file

@ -1182,7 +1182,7 @@ export function computeScreenAwareSize(cssPx: number): number {
/**
* Open safely a new window. This is the best way to do so, but you cannot tell
* if the window was opened or if it was blocked by the browser's popup blocker.
* If you want to tell if the browser blocked the new window, use `windowOpenNoOpenerWithSuccess`.
* If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
*
* See https://github.com/microsoft/monaco-editor/issues/601
* To protect against malicious code in the linked site, particularly phishing attempts,
@ -1201,19 +1201,49 @@ export function windowOpenNoOpener(url: string): void {
}
/**
* Open safely a new window. This technique is not appropriate in certain contexts,
* like for example when the JS context is executing inside a sandboxed iframe.
* If it is not necessary to know if the browser blocked the new window, use
* `windowOpenNoOpener`.
* Open a new window in a popup. This is the best way to do so, but you cannot tell
* if the window was opened or if it was blocked by the browser's popup blocker.
* If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
*
* Note: this does not set {@link window.opener} to null. This is to allow the opened popup to
* be able to use {@link window.close} to close itself. Because of this, you should only use
* this function on urls that you trust.
*
* In otherwords, you should almost always use {@link windowOpenNoOpener} instead of this function.
*/
const popupWidth = 780, popupHeight = 640;
export function windowOpenPopup(url: string): void {
const left = Math.floor(window.screenLeft + window.innerWidth / 2 - popupWidth / 2);
const top = Math.floor(window.screenTop + window.innerHeight / 2 - popupHeight / 2);
window.open(
url,
'_blank',
`width=${popupWidth},height=${popupHeight},top=${top},left=${left}`
);
}
/**
* Attempts to open a window and returns whether it succeeded. This technique is
* not appropriate in certain contexts, like for example when the JS context is
* executing inside a sandboxed iframe. If it is not necessary to know if the
* browser blocked the new window, use {@link windowOpenNoOpener}.
*
* See https://github.com/microsoft/monaco-editor/issues/601
* See https://github.com/microsoft/monaco-editor/issues/2474
* See https://mathiasbynens.github.io/rel-noopener/
*
* @param url the url to open
* @param noOpener whether or not to set the {@link window.opener} to null. You should leave the default
* (true) unless you trust the url that is being opened.
* @returns boolean indicating if the {@link window.open} call succeeded
*/
export function windowOpenNoOpenerWithSuccess(url: string): boolean {
export function windowOpenWithSuccess(url: string, noOpener = true): boolean {
const newTab = window.open();
if (newTab) {
(newTab as any).opener = null;
if (noOpener) {
// see `windowOpenNoOpener` for details on why this is important
(newTab as any).opener = null;
}
newTab.location.href = url;
return true;
}

View file

@ -86,6 +86,7 @@ export namespace CSSIcon {
export const iconNameSegment = '[A-Za-z0-9]+';
export const iconNameExpression = '[A-Za-z0-9\\-]+';
export const iconModifierExpression = '~[A-Za-z]+';
export const iconNameCharacter = '[A-Za-z0-9\\-~]';
const cssIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`);

View file

@ -10,6 +10,7 @@ import { ltrim } from 'vs/base/common/strings';
export const iconStartMarker = '$(';
const iconsRegex = new RegExp(`\\$\\(${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
const iconNameCharacterRegexp = new RegExp(CSSIcon.iconNameCharacter);
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
export function escapeIcons(text: string): string {
@ -103,7 +104,7 @@ function doParseLabelWithIcons(text: string, firstIconIndex: number): IParsedLab
// within icon
else if (currentIconStart !== -1) {
// Make sure this is a real icon name
if (/^[a-z0-9\-]$/i.test(char)) {
if (iconNameCharacterRegexp.test(char)) {
currentIconValue += char;
} else {
// This is not a real icon, treat it as text

View file

@ -63,6 +63,11 @@ suite('Icon Labels', () => {
filterOk(matchesFuzzyIconAware, 'unt', parseLabelWithIcons('$(primitive-dot) $(file-text) Untitled-1'), [
{ start: 30, end: 33 },
]);
// Testing #136172
filterOk(matchesFuzzyIconAware, 's', parseLabelWithIcons('$(loading~spin) start'), [
{ start: 16, end: 17 },
]);
});
test('stripIcons', () => {

View file

@ -268,7 +268,7 @@ class SharedProcessMain extends Disposable {
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
appenders.push(collectorAppender);
}
const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, insiders ? false : testCollector);
const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey, insiders ? false : testCollector, testCollector && insiders);
this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
appenders.push(appInsightsAppender);
}

View file

@ -72,8 +72,11 @@ export class BracketPairs extends Disposable implements IBracketPairs {
this.onDidChangeEmitter.fire();
}
} else {
this.bracketPairsTree.clear();
this.onDidChangeEmitter.fire();
if (this.bracketPairsTree.value) {
this.bracketPairsTree.clear();
// Important: Don't call fire if there was no change!
this.onDidChangeEmitter.fire();
}
}
}

View file

@ -71,6 +71,16 @@ export interface IFindStartOptions {
loop: boolean;
}
export interface IFindStartArguments {
searchString?: string;
replaceString?: string;
isRegex?: boolean;
matchWholeWord?: boolean;
isCaseSensitive?: boolean;
preserveCase?: boolean;
findInSelection?: boolean;
}
export class CommonFindController extends Disposable implements IEditorContribution {
public static readonly ID = 'editor.contrib.findController';
@ -273,7 +283,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
// overwritten in subclass
}
protected async _start(opts: IFindStartOptions): Promise<void> {
protected async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {
this.disposeModel();
if (!this._editor.hasModel()) {
@ -282,6 +292,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
}
let stateChanges: INewFindReplaceState = {
...newState,
isRevealed: true
};
@ -315,7 +326,7 @@ export class CommonFindController extends Disposable implements IEditorContribut
}
// Overwrite isReplaceRevealed
if (opts.forceRevealReplace) {
if (opts.forceRevealReplace || stateChanges.isReplaceRevealed) {
stateChanges.isReplaceRevealed = true;
} else if (!this._findWidgetVisible.get()) {
stateChanges.isReplaceRevealed = false;
@ -337,8 +348,8 @@ export class CommonFindController extends Disposable implements IEditorContribut
}
}
public start(opts: IFindStartOptions): Promise<void> {
return this._start(opts);
public start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {
return this._start(opts, newState);
}
public moveToNextMatch(): boolean {
@ -423,7 +434,7 @@ export class FindController extends CommonFindController implements IFindControl
this._findOptionsWidget = null;
}
protected override async _start(opts: IFindStartOptions): Promise<void> {
protected override async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {
if (!this._widget) {
this._createFindWidget();
}
@ -447,9 +458,9 @@ export class FindController extends CommonFindController implements IFindControl
break;
}
opts.updateSearchScope = updateSearchScope;
opts.updateSearchScope = opts.updateSearchScope || updateSearchScope;
await super._start(opts);
await super._start(opts, newState);
if (this._widget) {
if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {
@ -520,6 +531,90 @@ StartFindAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeE
});
});
const findArgDescription = {
description: 'Open a new In-Editor Find Widget.',
args: [{
name: 'Open a new In-Editor Find Widget args',
schema: {
properties: {
searchString: { type: 'string' },
replaceString: { type: 'string' },
regex: { type: 'boolean' },
regexOverride: {
type: 'number',
description: nls.localize('actions.find.isRegexOverride', 'Overrides "Use Regular Expression" flag.\nThe flag will not be saved for the future.\n0: Do Nothing\n1: True\n2: False')
},
wholeWord: { type: 'boolean' },
wholeWordOverride: {
type: 'number',
description: nls.localize('actions.find.wholeWordOverride', 'Overrides "Match Whole Word" flag.\nThe flag will not be saved for the future.\n0: Do Nothing\n1: True\n2: False')
},
matchCase: { type: 'boolean' },
matchCaseOverride: {
type: 'number',
description: nls.localize('actions.find.matchCaseOverride', 'Overrides "Math Case" flag.\nThe flag will not be saved for the future.\n0: Do Nothing\n1: True\n2: False')
},
preserveCase: { type: 'boolean' },
preserveCaseOverride: {
type: 'number',
description: nls.localize('actions.find.preserveCaseOverride', 'Overrides "Preserve Case" flag.\nThe flag will not be saved for the future.\n0: Do Nothing\n1: True\n2: False')
},
findInSelection: { type: 'boolean' },
}
}
}]
} as const;
export class StartFindWithArgsAction extends EditorAction {
constructor() {
super({
id: FIND_IDS.StartFindWithArgs,
label: nls.localize('startFindWithArgsAction', "Find With Arguments"),
alias: 'Find With Arguments',
precondition: undefined,
kbOpts: {
kbExpr: null,
primary: 0,
weight: KeybindingWeight.EditorContrib
},
description: findArgDescription
});
}
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor, args?: IFindStartArguments): Promise<void> {
let controller = CommonFindController.get(editor);
if (controller) {
const newState: INewFindReplaceState = args ? {
searchString: args.searchString,
replaceString: args.replaceString,
isReplaceRevealed: args.replaceString !== undefined,
isRegex: args.isRegex,
// isRegexOverride: args.regexOverride,
wholeWord: args.matchWholeWord,
// wholeWordOverride: args.wholeWordOverride,
matchCase: args.isCaseSensitive,
// matchCaseOverride: args.matchCaseOverride,
preserveCase: args.preserveCase,
// preserveCaseOverride: args.preserveCaseOverride,
} : {};
await controller.start({
forceRevealReplace: false,
seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',
seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',
seedSearchStringFromGlobalClipboard: true,
shouldFocus: FindStartFocusAction.FocusFindInput,
shouldAnimate: true,
updateSearchScope: args?.findInSelection || false,
loop: editor.getOption(EditorOption.find).loop
}, newState);
controller.setGlobalBufferTerm(controller.getState().searchString);
}
}
}
export class StartFindWithSelectionAction extends EditorAction {
constructor() {
@ -772,6 +867,7 @@ StartFindReplaceAction.addImplementation(0, (accessor: ServicesAccessor, editor:
registerEditorContribution(CommonFindController.ID, FindController);
registerEditorAction(StartFindWithArgsAction);
registerEditorAction(StartFindWithSelectionAction);
registerEditorAction(NextMatchFindAction);
registerEditorAction(PreviousMatchFindAction);

View file

@ -55,6 +55,7 @@ export const TogglePreserveCaseKeybinding: IKeybindings = {
export const FIND_IDS = {
StartFindAction: 'actions.find',
StartFindWithSelection: 'actions.findWithSelection',
StartFindWithArgs: 'editor.actions.findWithArgs',
NextMatchFindAction: 'editor.action.nextMatchFindAction',
PreviousMatchFindAction: 'editor.action.previousMatchFindAction',
NextSelectionMatchFindAction: 'editor.action.nextSelectionMatchFindAction',

View file

@ -176,7 +176,7 @@ export class InlayHintsController implements IEditorContribution {
// update inline hints when content or scroll position changes
this._sessionDisposables.add(this._editor.onDidChangeModelContent(() => scheduler.schedule()));
this._disposables.add(this._editor.onDidScrollChange(() => scheduler.schedule()));
this._sessionDisposables.add(this._editor.onDidScrollChange(() => scheduler.schedule()));
scheduler.schedule();
// update inline hints when any any provider fires an event

View file

@ -264,7 +264,7 @@
/** Do NOT display ReadMore when docs is side/below **/
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore, .monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
display: none !important;
}
@ -280,11 +280,6 @@
display: inline-block;
}
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore,
.monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
display: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover>.contents>.main>.right>.readMore {
visibility: visible;
}
@ -394,7 +389,6 @@
word-break: break-all;
}
.monaco-editor .suggest-details>.monaco-scrollable-element>.body>.docs {
margin: 0;
padding: 4px 5px;

View file

@ -825,7 +825,7 @@ export class SuggestWidget implements IDisposable {
private _positionDetails(): void {
if (this._isDetailsVisible()) {
this._details.placeAtAnchor(this.element.domNode);
this._details.placeAtAnchor(this.element.domNode, this._contentWidget.getPosition()?.preference[0] === ContentWidgetPositionPreference.BELOW);
}
}

View file

@ -262,6 +262,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
private _added: boolean = false;
private _anchorBox?: dom.IDomNodePagePosition;
private _preferAlignAtTop: boolean = true;
private _userSize?: dom.Dimension;
private _topLeft?: TopLeftPosition;
@ -315,7 +316,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
this._disposables.add(this.widget.onDidChangeContents(() => {
if (this._anchorBox) {
this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size);
this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, this._preferAlignAtTop);
}
}));
}
@ -361,13 +362,14 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
}
}
placeAtAnchor(anchor: HTMLElement) {
placeAtAnchor(anchor: HTMLElement, preferAlignAtTop: boolean) {
const anchorBox = dom.getDomNodePagePosition(anchor);
this._anchorBox = anchorBox;
this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size);
this._preferAlignAtTop = preferAlignAtTop;
this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop);
}
_placeAtAnchor(anchorBox: dom.IDomNodePagePosition, size: dom.Dimension) {
_placeAtAnchor(anchorBox: dom.IDomNodePagePosition, size: dom.Dimension, preferAlignAtTop: boolean) {
const bodyBox = dom.getClientArea(document.body);
const info = this.widget.getLayoutInfo();
@ -416,12 +418,22 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
height = maxHeight;
}
let maxSize: dom.Dimension;
if (height <= placement.maxSizeTop.height) {
alignAtTop = true;
maxSize = placement.maxSizeTop;
if (preferAlignAtTop) {
if (height <= placement.maxSizeTop.height) {
alignAtTop = true;
maxSize = placement.maxSizeTop;
} else {
alignAtTop = false;
maxSize = placement.maxSizeBottom;
}
} else {
alignAtTop = false;
maxSize = placement.maxSizeBottom;
if (height <= placement.maxSizeBottom.height) {
alignAtTop = false;
maxSize = placement.maxSizeBottom;
} else {
alignAtTop = true;
maxSize = placement.maxSizeTop;
}
}
this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height });

View file

@ -62,7 +62,6 @@ export interface IConstructorSignature8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
export interface ServicesAccessor {
get<T>(id: ServiceIdentifier<T>): T;
get<T>(id: ServiceIdentifier<T>, isOptional: typeof optional): T | undefined;
}
export const IInstantiationService = createDecorator<IInstantiationService>('instantiationService');
@ -158,17 +157,3 @@ export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
export function refineServiceDecorator<T1, T extends T1>(serviceIdentifier: ServiceIdentifier<T1>): ServiceIdentifier<T> {
return <ServiceIdentifier<T>>serviceIdentifier;
}
/**
* Mark a service dependency as optional.
* @deprecated Avoid, see https://github.com/microsoft/vscode/issues/119440
*/
export function optional<T>(serviceIdentifier: ServiceIdentifier<T>) {
return function (target: Function, key: string, index: number) {
if (arguments.length !== 3) {
throw new Error('@optional-decorator can only be used to decorate a parameter');
}
storeServiceDependency(serviceIdentifier, target, index, true);
};
}

View file

@ -7,7 +7,7 @@ import { IdleValue } from 'vs/base/common/async';
import { illegalState } from 'vs/base/common/errors';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Graph } from 'vs/platform/instantiation/common/graph';
import { IInstantiationService, optional, ServiceIdentifier, ServicesAccessor, _util } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
// TRACING
@ -45,14 +45,14 @@ export class InstantiationService implements IInstantiationService {
let _done = false;
try {
const accessor: ServicesAccessor = {
get: <T>(id: ServiceIdentifier<T>, isOptional?: typeof optional) => {
get: <T>(id: ServiceIdentifier<T>) => {
if (_done) {
throw illegalState('service accessor is only valid during the invocation of its target method');
}
const result = this._getOrCreateServiceInstance(id, _trace);
if (!result && isOptional !== optional) {
if (!result) {
throw new Error(`[invokeFunction] unknown service '${id}'`);
}
return result;

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { createDecorator, IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@ -85,18 +85,7 @@ class TargetWithStaticParam {
}
}
class TargetNotOptional {
constructor(@IService1 service1: IService1, @IService2 service2: IService2) {
}
}
class TargetOptional {
constructor(@IService1 service1: IService1, @optional(IService2) service2: IService2) {
assert.ok(service1);
assert.strictEqual(service1.c, 1);
assert.ok(service2 === undefined);
}
}
class DependentServiceTarget {
constructor(@IDependentService d: IDependentService) {
@ -181,13 +170,6 @@ suite('Instantiation Service', () => {
let service = new InstantiationService(collection);
service.createInstance(Service1Consumer);
// no IService2
assert.throws(() => service.createInstance(Target2Dep));
service.invokeFunction(function (a) {
assert.ok(a.get(IService1));
assert.ok(!a.get(IService2, optional));
});
collection.set(IService2, new Service2());
service.createInstance(Target2Dep);
@ -197,18 +179,6 @@ suite('Instantiation Service', () => {
});
});
test('@Param - optional', function () {
let collection = new ServiceCollection([IService1, new Service1()]);
let service = new InstantiationService(collection, true);
service.createInstance(TargetOptional);
assert.throws(() => service.createInstance(TargetNotOptional));
service = new InstantiationService(collection, false);
service.createInstance(TargetOptional);
service.createInstance(TargetNotOptional);
});
// we made this a warning
// test('@Param - too many args', function () {
// let service = instantiationService.create(Object.create(null));
@ -320,7 +290,6 @@ suite('Instantiation Service', () => {
function test(accessor: ServicesAccessor) {
assert.ok(accessor.get(IService1) instanceof Service1);
assert.throws(() => accessor.get(IService2));
assert.strictEqual(accessor.get(IService2, optional), undefined);
return true;
}
assert.strictEqual(service.invokeFunction(test), true);

View file

@ -29,7 +29,7 @@ async function getClient(aiKey: string, testCollector: boolean): Promise<Telemet
}
if (aiKey.indexOf('AIF-') === 0) {
client.config.endpointUrl = testCollector ? 'https://mobile.events.data.microsoft.com/OneCollector/1.0' : 'https://vortex.data.microsoft.com/collect/v1';
client.config.endpointUrl = testCollector ? 'https://mobile.events.data.microsoft.com/collect/v1' : 'https://vortex.data.microsoft.com/collect/v1';
}
return client;
}

View file

@ -142,6 +142,9 @@ export class PtyService extends Disposable implements IPtyService {
{
...state.shellLaunchConfig,
cwd: state.processDetails.cwd,
color: state.processDetails.color,
icon: state.processDetails.icon,
name: state.processDetails.title,
initialText: state.replayEvent.events[0].data + '\x1b[0m\n\n\r\x1b[1;48;5;252;38;5;234m ' + restoreMessage + ' \x1b[K\x1b[0m\n\r'
},
state.processDetails.cwd,
@ -190,7 +193,7 @@ export class PtyService extends Disposable implements IPtyService {
executableEnv,
windowsEnableConpty
};
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.fixedDimensions);
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
process.onProcessExit(event => {
persistentProcess.dispose();
@ -461,9 +464,13 @@ export class PersistentTerminalProcess extends Disposable {
reviveBuffer: string | undefined,
private _icon?: TerminalIcon,
private _color?: string,
name?: string,
fixedDimensions?: IFixedTerminalDimensions
) {
super();
if (name) {
this.setTitle(name, TitleEventSource.Api);
}
this._logService.trace('persistentTerminalProcess#ctor', _persistentProcessId, arguments);
this._wasRevived = reviveBuffer !== undefined;
this._serializer = new XtermSerializer(

View file

@ -9,6 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { FileType } from 'vs/platform/files/common/files';
import { LogLevel } from 'vs/platform/log/common/log';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
@ -187,13 +188,19 @@ export interface IPathData {
// a hint that the file exists. if true, the
// file exists, if false it does not. with
// undefined the state is unknown.
// `undefined` the state is unknown.
readonly exists?: boolean;
// Specifies if the file should be only be opened if it exists
// a hint about the file type of this path.
// with `undefined` the type is unknown.
readonly type?: FileType;
// Specifies if the file should be only be opened
// if it exists
readonly openOnlyIfExists?: boolean;
// Specifies an optional id to override the editor used to edit the resource, e.g. custom editor.
// Specifies an optional id to override the editor
// used to edit the resource, e.g. custom editor.
readonly editorOverrideId?: string;
}

View file

@ -29,7 +29,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { IFileService } from 'vs/platform/files/common/files';
import { FileType, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
@ -988,14 +988,21 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return undefined;
}
return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority, exists: true, transient: workspace.transient };
return {
workspace: { id: workspace.id, configPath: workspace.configPath },
type: FileType.File,
exists: true,
remoteAuthority: workspace.remoteAuthority,
transient: workspace.transient
};
}
}
return {
fileUri: URI.file(path),
selection: lineNumber ? { startLineNumber: lineNumber, startColumn: columnNumber || 1 } : undefined,
exists: true
type: FileType.File,
exists: true,
selection: lineNumber ? { startLineNumber: lineNumber, startColumn: columnNumber || 1 } : undefined
};
}
@ -1003,6 +1010,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
else if (pathStat.isDirectory()) {
return {
workspace: getSingleFolderWorkspaceIdentifier(URI.file(path), pathStat),
type: FileType.Directory,
exists: true
};
}
@ -1014,6 +1022,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
else if (!isWindows && path === '/dev/null') {
return {
fileUri: URI.file(path),
type: FileType.File,
exists: true
};
}
@ -1027,6 +1036,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (options.ignoreFileNotFound) {
return {
fileUri,
type: FileType.File,
exists: false
};
}

12
src/vs/vscode.d.ts vendored
View file

@ -541,7 +541,7 @@ declare module 'vscode' {
* The {@link TextEditorSelectionChangeKind change kind} which has triggered this
* event. Can be `undefined`.
*/
readonly kind: TextEditorSelectionChangeKind | undefined
readonly kind: TextEditorSelectionChangeKind | undefined;
}
/**
@ -8137,14 +8137,14 @@ declare module 'vscode' {
* If this is provided, your extension should restore the editor from the backup instead of reading the file
* from the user's workspace.
*/
readonly backupId: string | undefined
readonly backupId: string | undefined;
/**
* If the URI is an untitled file, this will be populated with the byte data of that file
*
* If this is provided, your extension should utilize this byte data rather than executing fs APIs on the URI passed in
*/
readonly untitledDocumentData: Uint8Array | undefined
readonly untitledDocumentData: Uint8Array | undefined;
}
/**
@ -12926,7 +12926,7 @@ declare module 'vscode' {
/**
* Event specific information.
*/
readonly body: any | undefined;
readonly body: any;
}
/**
@ -13917,12 +13917,12 @@ declare module 'vscode' {
/**
* The {@link AuthenticationSession AuthenticationSessions} of the {@link AuthenticationProvider} that have been added.
*/
readonly added: readonly AuthenticationSession[] | undefined
readonly added: readonly AuthenticationSession[] | undefined;
/**
* The {@link AuthenticationSession AuthenticationSessions} of the {@link AuthenticationProvider} that have been removed.
*/
readonly removed: readonly AuthenticationSession[] | undefined
readonly removed: readonly AuthenticationSession[] | undefined;
/**
* The {@link AuthenticationSession AuthenticationSessions} of the {@link AuthenticationProvider} that have been changed.

View file

@ -11,7 +11,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { reviveWebviewContentOptions } from 'vs/workbench/api/browser/mainThreadWebviews';
import { ExtHostContext, ExtHostEditorInsetsShape, IExtHostContext, IWebviewOptions, MainContext, MainThreadEditorInsetsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { extHostNamedCustomer } from '../common/extHostCustomers';
// todo@jrieken move these things back into something like contrib/insets
@ -34,7 +34,7 @@ class EditorWebviewZone implements IViewZone {
readonly editor: IActiveCodeEditor,
readonly line: number,
readonly height: number,
readonly webview: WebviewElement,
readonly webview: IWebviewElement,
) {
this.domNode = document.createElement('div');
this.domNode.style.zIndex = '10'; // without this, the webview is not interactive

View file

@ -37,12 +37,14 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
private _forwardMarkers(resources: readonly URI[]): void {
const data: [UriComponents, IMarkerData[]][] = [];
for (const resource of resources) {
data.push([
resource,
this._markerService.read({ resource }).filter(marker => !this._activeOwners.has(marker.owner))
]);
const markerData = this._markerService.read({ resource }).filter(marker => !this._activeOwners.has(marker.owner));
if (markerData.length > 0) {
data.push([resource, markerData]);
}
}
if (data.length > 0) {
this._proxy.$acceptMarkersChange(data);
}
this._proxy.$acceptMarkersChange(data);
}
$changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void {

View file

@ -15,7 +15,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { serializeWebviewMessage, deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview, WebviewContentOptions, WebviewExtensionDescription, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape {
@ -29,7 +29,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
private readonly _proxy: extHostProtocol.ExtHostWebviewsShape;
private readonly _webviews = new Map<string, Webview>();
private readonly _webviews = new Map<string, IWebview>();
constructor(
context: extHostProtocol.IExtHostContext,
@ -41,7 +41,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews);
}
public addWebview(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }): void {
public addWebview(handle: extHostProtocol.WebviewHandle, webview: IOverlayWebview, options: { serializeBuffersForPostMessage: boolean }): void {
if (this._webviews.has(handle)) {
throw new Error('Webview already registered');
}
@ -67,7 +67,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return true;
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: WebviewOverlay, options: { serializeBuffersForPostMessage: boolean }) {
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewHandle, webview: IOverlayWebview, options: { serializeBuffersForPostMessage: boolean }) {
const disposables = new DisposableStore();
disposables.add(webview.onDidClickLink((uri) => this.onDidClickLink(handle, uri)));
@ -92,7 +92,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
}
private isSupportedLink(webview: Webview, link: URI): boolean {
private isSupportedLink(webview: IWebview, link: URI): boolean {
if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) {
return true;
}
@ -102,7 +102,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return !!webview.contentOptions.enableCommandUris && link.scheme === Schemas.command;
}
private getWebview(handle: extHostProtocol.WebviewHandle): Webview {
private getWebview(handle: extHostProtocol.WebviewHandle): IWebview {
const webview = this._webviews.get(handle);
if (!webview) {
throw new Error(`Unknown webview handle:${handle}`);

View file

@ -207,7 +207,7 @@ class ToggleScreencastModeAction extends Action2 {
const event = new StandardKeyboardEvent(e);
const shortcut = keybindingService.softDispatch(event, event.target);
if (shortcut || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) {
if (shortcut?.commandId || !configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) {
if (
event.ctrlKey || event.altKey || event.metaKey || event.shiftKey
|| length > 20
@ -243,7 +243,7 @@ class ToggleScreencastModeAction extends Action2 {
append(keyboardMarker, $('span.title', {}, `${titleLabel} `));
}
if (format === 'keys' || format === 'commandAndKeys' || format === 'commandWithGroupAndKeys') {
if (!configurationService.getValue('screencastMode.onlyKeyboardShortcuts') || !titleLabel || shortcut?.commandId && (format === 'keys' || format === 'commandAndKeys' || format === 'commandWithGroupAndKeys')) {
append(keyboardMarker, $('span.key', {}, keyLabel || ''));
}

View file

@ -586,9 +586,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
return;
}
const selection = widget.getSelection();
const focus = widget.getFocus()[0];
const fakeKeyboardEvent = new KeyboardEvent('keydown');
widget.setSelection([], fakeKeyboardEvent);
widget.setFocus([], fakeKeyboardEvent);
if (selection.length > 1) {
if (focus) {
widget.setSelection([focus], fakeKeyboardEvent);
} else {
widget.setSelection(selection[0], fakeKeyboardEvent);
}
} else if (selection.length === 1 && selection[0] !== focus) {
widget.setSelection([focus], fakeKeyboardEvent);
} else {
widget.setSelection([], fakeKeyboardEvent);
widget.setFocus([], fakeKeyboardEvent);
}
widget.setAnchor(undefined);
}
});

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { isSafari, setFullscreen } from 'vs/base/browser/browser';
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpenerWithSuccess, windowOpenNoOpener } from 'vs/base/browser/dom';
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { timeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
@ -141,10 +141,20 @@ export class BrowserWindow extends Disposable {
}
}
let isAllowedOpener = false;
if (this.environmentService.options?.openerAllowedExternalUrlPrefixes) {
for (const trustedPopupPrefix of this.environmentService.options.openerAllowedExternalUrlPrefixes) {
if (href.startsWith(trustedPopupPrefix)) {
isAllowedOpener = true;
break;
}
}
}
// HTTP(s): open in new window and deal with potential popup blockers
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
if (isSafari) {
const opened = windowOpenNoOpenerWithSuccess(href);
const opened = windowOpenWithSuccess(href, !isAllowedOpener);
if (!opened) {
const showResult = await this.dialogService.show(
Severity.Warning,
@ -161,7 +171,9 @@ export class BrowserWindow extends Disposable {
);
if (showResult.choice === 0) {
windowOpenNoOpener(href);
isAllowedOpener
? windowOpenPopup(href)
: windowOpenNoOpener(href);
}
if (showResult.choice === 1) {

View file

@ -17,7 +17,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IEncodingSupport, IModeSupport } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ICompositeControl, IComposite } from 'vs/workbench/common/composite';
import { IFileService } from 'vs/platform/files/common/files';
import { FileType, IFileService } from 'vs/platform/files/common/files';
import { IPathData } from 'vs/platform/windows/common/windows';
import { coalesce } from 'vs/base/common/arrays';
import { IExtUri } from 'vs/base/common/resources';
@ -1138,11 +1138,25 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService
return;
}
const exists = (typeof path.exists === 'boolean') ? path.exists : await fileService.exists(resource);
let exists = path.exists;
let type = path.type;
if (typeof exists !== 'boolean' || typeof type !== 'number') {
try {
type = (await fileService.resolve(resource)).isFile ? FileType.File : FileType.Unknown;
exists = true;
} catch {
exists = false;
}
}
if (!exists && path.openOnlyIfExists) {
return;
}
if (type !== FileType.File) {
return;
}
const options: ITextEditorOptions = {
selection: exists ? path.selection : undefined,
pinned: true,

View file

@ -20,7 +20,7 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, GroupIdentifier, IRevertOptions, ISaveOptions, isEditorInputWithOptionsAndGroup, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
@ -66,7 +66,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
resource: URI,
viewType: string,
id: string,
webview: WebviewOverlay,
webview: IOverlayWebview,
options: { startsDirty?: boolean, backupId?: string, untitledDocumentData?: VSBuffer, readonly oldResource?: URI },
@IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService,
@IInstantiationService private readonly instantiationService: IInstantiationService,

View file

@ -22,13 +22,14 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { CONTEXT_DEBUGGERS_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { breakpointsExtPoint, debuggersExtPoint, launchSchema, presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
@ -38,6 +39,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
private debugAdapterFactories = new Map<string, IDebugAdapterFactory>();
private debuggersAvailable: IContextKey<boolean>;
private debugExtensionsAvailable: IContextKey<boolean>;
private readonly _onDidRegisterDebugger = new Emitter<void>();
private readonly _onDidDebuggersExtPointRead = new Emitter<void>();
private breakpointModeIdsSet = new Set<string>();
@ -53,6 +55,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IModeService private readonly modeService: IModeService,
@IDialogService private readonly dialogService: IDialogService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
) {
super();
this.adapterDescriptorFactories = [];
@ -64,6 +67,13 @@ export class AdapterManager extends Disposable implements IAdapterManager {
this.debuggersAvailable.set(this.hasEnabledDebuggers());
}
}));
this.debugExtensionsAvailable = CONTEXT_DEBUG_EXTENSION_AVAILABLE.bindTo(contextKeyService);
this.debugExtensionsAvailable.set(true); // Avoid a flash of the default message before extensions load.
this._register(this.onDidDebuggersExtPointRead(() => {
this.debugExtensionsAvailable.set(this.debuggers.length > 0);
}));
this.lifecycleService.when(LifecyclePhase.Eventually)
.then(() => this.debugExtensionsAvailable.set(this.debuggers.length > 0)); // If no extensions with a debugger contribution are loaded
}
private registerListeners(): void {

View file

@ -10,7 +10,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
import { IDebugService, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -142,3 +142,9 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, WorkbenchStateContext.isEqualTo('empty')),
group: ViewContentGroups.Debug
});
viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
content: localize('allDebuggersDisabled', "All debug extensions are disabled. Enable a debug extension or install a new one from the Marketplace."),
when: CONTEXT_DEBUG_EXTENSION_AVAILABLE.toNegated(),
group: ViewContentGroups.Debug
});

View file

@ -71,6 +71,7 @@ export const CONTEXT_JUMP_TO_CURSOR_SUPPORTED = new RawContextKey<boolean>('jump
export const CONTEXT_STEP_INTO_TARGETS_SUPPORTED = new RawContextKey<boolean>('stepIntoTargetsSupported', false, { type: 'boolean', description: nls.localize('stepIntoTargetsSupported', "True when the focused session supports 'stepIntoTargets' request.") });
export const CONTEXT_BREAKPOINTS_EXIST = new RawContextKey<boolean>('breakpointsExist', false, { type: 'boolean', description: nls.localize('breakpointsExist', "True when at least one breakpoint exists.") });
export const CONTEXT_DEBUGGERS_AVAILABLE = new RawContextKey<boolean>('debuggersAvailable', false, { type: 'boolean', description: nls.localize('debuggersAvailable', "True when there is at least one debug extensions active.") });
export const CONTEXT_DEBUG_EXTENSION_AVAILABLE = new RawContextKey<boolean>('debugExtensionAvailable', false, { type: 'boolean', description: nls.localize('debugExtensionsAvailable', "True when there is at least one debug extension installed and enabled.") });
export const CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT = new RawContextKey<string>('debugProtocolVariableMenuContext', undefined, { type: 'string', description: nls.localize('debugProtocolVariableMenuContext', "Represents the context the debug adapter sets on the focused variable in the VARIABLES view.") });
export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey<boolean>('debugSetVariableSupported', false, { type: 'boolean', description: nls.localize('debugSetVariableSupported', "True when the focused session supports 'setVariable' request.") });
export const CONTEXT_SET_EXPRESSION_SUPPORTED = new RawContextKey<boolean>('debugSetExpressionSupported', false, { type: 'boolean', description: nls.localize('debugSetExpressionSupported', "True when the focused session supports 'setExpression' request.") });

View file

@ -50,7 +50,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry';
import { isUndefined } from 'vs/base/common/types';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWebviewService, Webview, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, IWebview, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { generateUuid } from 'vs/base/common/uuid';
import { platform } from 'vs/base/common/process';
@ -554,11 +554,11 @@ export class ExtensionEditor extends EditorPane {
this.activeWebview?.runFindAction(previous);
}
public get activeWebview(): Webview | undefined {
if (!this.activeElement || !(this.activeElement as Webview).runFindAction) {
public get activeWebview(): IWebview | undefined {
if (!this.activeElement || !(this.activeElement as IWebview).runFindAction) {
return undefined;
}
return this.activeElement as Webview;
return this.activeElement as IWebview;
}
private onNavbarChange(extension: IExtension, { id, focus }: { id: string | null, focus: boolean }, template: IExtensionEditorTemplate): void {

View file

@ -44,7 +44,7 @@ import { CONTEXT_SYNC_ENABLEMENT } from 'vs/workbench/services/userDataSync/comm
import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { MultiCommand } from 'vs/editor/browser/editorExtensions';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { CATEGORIES } from 'vs/workbench/common/actions';
@ -366,7 +366,7 @@ CommandsRegistry.registerCommand({
}
});
function overrideActionForActiveExtensionEditorWebview(command: MultiCommand | undefined, f: (webview: Webview) => void) {
function overrideActionForActiveExtensionEditorWebview(command: MultiCommand | undefined, f: (webview: IWebview) => void) {
command?.addImplementation(105, 'extensions-editor', (accessor) => {
const editorService = accessor.get(IEditorService);
const editor = editorService.activeEditorPane;

View file

@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
@ -52,6 +52,9 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
@ -218,8 +221,35 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
}
}
class InteractiveInputContentProvider implements ITextModelContentProvider {
private readonly _registration: IDisposable;
constructor(
@ITextModelService textModelService: ITextModelService,
@IModelService private readonly _modelService: IModelService,
) {
this._registration = textModelService.registerTextModelContentProvider(Schemas.vscodeInteractiveInput, this);
}
dispose(): void {
this._registration.dispose();
}
async provideTextContent(resource: URI): Promise<ITextModel | null> {
const existing = this._modelService.getModel(resource);
if (existing) {
return existing;
}
let result: ITextModel | null = this._modelService.createModel('', null, resource, false);
return result;
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocumentContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Starting);
export class InteractiveEditorSerializer implements IEditorSerializer {
canSerialize(): boolean {

View file

@ -330,7 +330,7 @@ export class InteractiveEditor extends EditorPane {
}
}));
const editorModel = input.resolveInput(this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? 'plaintext');
const editorModel = await input.resolveInput(this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? 'plaintext');
this.#codeEditorWidget.setModel(editorModel);
this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidFocusEditorWidget(() => this.#onDidFocusWidget.fire()));
this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidContentSizeChange(e => {

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IReference } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@ -54,16 +55,12 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
private _inputResolver: Promise<IResolvedNotebookEditorModel | null> | null;
private _editorModelReference: IResolvedNotebookEditorModel | null;
private _inputModel: ITextModel | null;
get inputModel() {
return this._inputModel;
}
private _inputModelRef: IReference<IResolvedTextEditorModel> | null;
get primary(): EditorInput {
return this._notebookEditorInput;
}
private _modelService: IModelService;
private _textModelService: ITextModelService;
private _interactiveDocumentService: IInteractiveDocumentService;
@ -73,6 +70,8 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
title: string | undefined,
@IInstantiationService instantiationService: IInstantiationService,
@IModelService modelService: IModelService,
@ITextModelService textModelService: ITextModelService,
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService
) {
const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {});
@ -83,8 +82,8 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._inputResource = inputResource;
this._inputResolver = null;
this._editorModelReference = null;
this._inputModel = null;
this._modelService = modelService;
this._inputModelRef = null;
this._textModelService = textModelService;
this._interactiveDocumentService = interactiveDocumentService;
this._registerListeners();
@ -131,14 +130,15 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return this._inputResolver;
}
resolveInput(language: string) {
if (this._inputModel) {
return this._inputModel;
async resolveInput(language: string) {
if (this._inputModelRef) {
return this._inputModelRef.object.textEditorModel;
}
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language);
this._inputModel = this._modelService.createModel('', null, this.inputResource, false);
return this._inputModel;
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
return this._inputModelRef.object.textEditorModel;
}
override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {
@ -170,8 +170,8 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._editorModelReference?.dispose();
this._editorModelReference = null;
this._interactiveDocumentService.willRemoveInteractiveDocument(this.resource!, this.inputResource);
this._inputModel?.dispose();
this._inputModel = null;
this._inputModelRef?.dispose();
this._inputModelRef = null;
super.dispose();
}
}

View file

@ -24,7 +24,7 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { rendererLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants';
@ -41,7 +41,7 @@ function _log(loggerService: ILogService, str: string) {
}
}
function getFocusedWebviewDelegate(accessor: ServicesAccessor): Webview | undefined {
function getFocusedWebviewDelegate(accessor: ServicesAccessor): IWebview | undefined {
const loggerService = accessor.get(ILogService);
const editorService = accessor.get(IEditorService);
const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
@ -65,7 +65,7 @@ function getFocusedWebviewDelegate(accessor: ServicesAccessor): Webview | undefi
return webview;
}
function withWebview(accessor: ServicesAccessor, f: (webviewe: Webview) => void) {
function withWebview(accessor: ServicesAccessor, f: (webviewe: IWebview) => void) {
const webview = getFocusedWebviewDelegate(accessor);
if (webview) {
f(webview);

View file

@ -16,7 +16,7 @@ import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewStat
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { CellKind, NotebookCellMetadata, IOrderedMimeType, INotebookRendererInfo, ICellOutput, INotebookCellStatusBarItem, NotebookCellInternalMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange, cellRangesToIndexes, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IEditorPane } from 'vs/workbench/common/editor';
@ -430,7 +430,7 @@ export interface INotebookEditor {
hasModel(): this is IActiveNotebookEditor;
dispose(): void;
getDomNode(): HTMLElement;
getInnerWebview(): Webview | undefined;
getInnerWebview(): IWebview | undefined;
getSelectionViewModels(): ICellViewModel[];
/**

View file

@ -58,7 +58,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { readFontInfo } from 'vs/editor/browser/config/configuration';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
@ -1055,7 +1055,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
return this._overflowContainer;
}
getInnerWebview(): Webview | undefined {
getInnerWebview(): IWebview | undefined {
return this._webview?.webview;
}

View file

@ -30,7 +30,7 @@ import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/no
import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions';
@ -314,11 +314,12 @@ export class NotebookOutputRendererInfoStore {
const enum ReuseOrder {
PreviouslySelected = 1 << 8,
SameExtensionAsNotebook = 2 << 8,
OtherRenderer = 3 << 8,
BuiltIn = 4 << 8,
BuiltIn = 3 << 8,
OtherRenderer = 4 << 8,
}
const preferred = notebookProviderInfo && this.preferredMimetype.getValue()[notebookProviderInfo.id]?.[mimeType];
const notebookExtId = notebookProviderInfo?.extension?.value;
const renderers: { ordered: IOrderedMimeType, score: number }[] = Array.from(this.contributedRenderers.values())
.map(renderer => {
const ownScore = kernelProvides === undefined
@ -329,9 +330,10 @@ export class NotebookOutputRendererInfoStore {
return undefined;
}
const rendererExtId = renderer.extensionId.value;
const reuseScore = preferred === renderer.id
? ReuseOrder.PreviouslySelected
: renderer.extensionId.value === notebookProviderInfo?.extension?.value
: rendererExtId === notebookExtId || RENDERER_EQUIVALENT_EXTENSIONS.get(rendererExtId)?.has(notebookExtId!)
? ReuseOrder.SameExtensionAsNotebook
: renderer.isBuiltin ? ReuseOrder.BuiltIn : ReuseOrder.OtherRenderer;
return {

View file

@ -36,7 +36,7 @@ import { INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contr
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, WebviewContentPurpose, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
@ -60,7 +60,7 @@ export interface INotebookWebviewMessage {
}
export interface IResolvedBackLayerWebview {
webview: WebviewElement;
webview: IWebviewElement;
}
/**
@ -87,7 +87,7 @@ export interface INotebookDelegateForWebview {
export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
element: HTMLElement;
webview: WebviewElement | undefined = undefined;
webview: IWebviewElement | undefined = undefined;
insetMapping: Map<IDisplayOutputViewModel, ICachedInset<T>> = new Map();
readonly markupPreviewMapping = new Map<string, IMarkupCellInitialization>();
private hiddenInsetMapping: Set<IDisplayOutputViewModel> = new Set();

View file

@ -56,6 +56,17 @@ export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER: readonly string[] = [
'image/jpeg',
];
/**
* A mapping of extension IDs who contain renderers, to extensions who they
* should be treated as the same in the renderer selection logic. This is used
* to prefer the 1st party Jupyter renderers even though they're in a separate
* extension, for instance. See #136247.
*/
export const RENDERER_EQUIVALENT_EXTENSIONS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
['ms-toolsai.jupyter', new Set(['vscode.ipynb'])],
['ms-toolsai.jupyter-renderers', new Set(['vscode.ipynb'])],
]);
export const BUILTIN_RENDERER_ID = '_builtin';
export const RENDERER_NOT_AVAILABLE = '_notAvailable';

View file

@ -16,9 +16,10 @@ import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/s
import { ITextModel } from 'vs/editor/common/model';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IOutputChannelModel, IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel';
import { IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputChannelModel';
import { IViewsService } from 'vs/workbench/common/views';
import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView';
import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService';
const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel';

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as resources from 'vs/base/common/resources';
import { ITextModel } from 'vs/editor/common/model';
import { Emitter, Event } from 'vs/base/common/event';
@ -28,39 +28,6 @@ export interface IOutputChannelModel extends IDisposable {
clear(till?: number): void;
}
export const IOutputChannelModelService = createDecorator<IOutputChannelModelService>('outputChannelModelService');
export interface IOutputChannelModelService {
readonly _serviceBrand: undefined;
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel;
}
export abstract class AbstractOutputChannelModelService {
declare readonly _serviceBrand: undefined;
constructor(
private readonly outputLocation: URI,
@IFileService protected readonly fileService: IFileService,
@IInstantiationService protected readonly instantiationService: IInstantiationService
) { }
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel {
return file ? this.instantiationService.createInstance(FileOutputChannelModel, modelUri, mimeType, file) : this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir);
}
private _outputDir: Promise<URI> | null = null;
private get outputDir(): Promise<URI> {
if (!this._outputDir) {
this._outputDir = this.fileService.createFolder(this.outputLocation).then(() => this.outputLocation);
}
return this._outputDir;
}
}
export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel {
protected readonly _onDidAppendedContent = this._register(new Emitter<void>());
@ -117,7 +84,7 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen
return this.model;
}
appendToModel(content: string): void {
protected appendToModel(content: string): void {
if (this.model && content) {
const lastLine = this.model.getLineCount();
const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine);
@ -197,7 +164,7 @@ class OutputFileListener extends Disposable {
/**
* An output channel driven by a file and does not support appending messages.
*/
class FileOutputChannelModel extends AbstractFileOutputChannelModel implements IOutputChannelModel {
export class FileOutputChannelModel extends AbstractFileOutputChannelModel implements IOutputChannelModel {
private readonly fileHandler: OutputFileListener;
@ -359,44 +326,38 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement
this.appendedMessage = '';
}
loadModel(): Promise<ITextModel> {
async loadModel(): Promise<ITextModel> {
this.loadingFromFileInProgress = true;
if (this.modelUpdater.isScheduled()) {
this.modelUpdater.cancel();
}
this.appendedMessage = '';
return this.loadFile()
.then(content => {
if (this.endOffset !== this.startOffset + VSBuffer.fromString(content).byteLength) {
// Queue content is not written into the file
// Flush it and load file again
this.flush();
return this.loadFile();
}
return content;
})
.then(content => {
if (this.appendedMessage) {
this.write(this.appendedMessage);
this.appendedMessage = '';
}
this.loadingFromFileInProgress = false;
return this.createModel(content);
});
let content = await this.loadFile();
if (this.endOffset !== this.startOffset + VSBuffer.fromString(content).byteLength) {
// Queue content is not written into the file
// Flush it and load file again
this.flush();
content = await this.loadFile();
}
if (this.appendedMessage) {
this.write(this.appendedMessage);
this.appendedMessage = '';
}
this.loadingFromFileInProgress = false;
return this.createModel(content);
}
private resetModel(): Promise<void> {
private async resetModel(): Promise<void> {
this.startOffset = 0;
this.endOffset = 0;
if (this.model) {
return this.loadModel().then(() => undefined);
await this.loadModel();
}
return Promise.resolve(undefined);
}
private loadFile(): Promise<string> {
return this.fileService.readFile(this.file, { position: this.startOffset })
.then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value.toString());
private async loadFile(): Promise<string> {
const content = await this.fileService.readFile(this.file, { position: this.startOffset });
return this.appendedMessage ? content.value + this.appendedMessage : content.value.toString();
}
protected override updateModel(): void {
@ -415,7 +376,7 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement
}
}
class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel {
export class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel {
private readonly _onDidAppendedContent: Emitter<void> = this._register(new Emitter<void>());
readonly onDidAppendedContent: Event<void> = this._onDidAppendedContent.event;

View file

@ -3,13 +3,47 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IOutputChannelModelService, AbstractOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFileService } from 'vs/platform/files/common/files';
import { toLocalISOString } from 'vs/base/common/date';
import { dirname, joinPath } from 'vs/base/common/resources';
import { DelegatedOutputChannelModel, FileOutputChannelModel, IOutputChannelModel } from 'vs/workbench/contrib/output/common/outputChannelModel';
import { URI } from 'vs/base/common/uri';
export const IOutputChannelModelService = createDecorator<IOutputChannelModelService>('outputChannelModelService');
export interface IOutputChannelModelService {
readonly _serviceBrand: undefined;
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel;
}
export abstract class AbstractOutputChannelModelService {
declare readonly _serviceBrand: undefined;
constructor(
private readonly outputLocation: URI,
@IFileService protected readonly fileService: IFileService,
@IInstantiationService protected readonly instantiationService: IInstantiationService
) { }
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel {
return file ? this.instantiationService.createInstance(FileOutputChannelModel, modelUri, mimeType, file) : this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir);
}
private _outputDir: Promise<URI> | null = null;
private get outputDir(): Promise<URI> {
if (!this._outputDir) {
this._outputDir = this.fileService.createFolder(this.outputLocation).then(() => this.outputLocation);
}
return this._outputDir;
}
}
export class OutputChannelModelService extends AbstractOutputChannelModelService implements IOutputChannelModelService {

View file

@ -7,11 +7,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { join } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { IOutputChannelModelService, AbstractOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModel';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { toLocalISOString } from 'vs/base/common/date';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { AbstractOutputChannelModelService, IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService';
export class OutputChannelModelService extends AbstractOutputChannelModelService implements IOutputChannelModelService {

View file

@ -137,6 +137,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _containerReadyBarrier: AutoOpenBarrier;
private _attachBarrier: AutoOpenBarrier;
private _icon: TerminalIcon | undefined;
private _messageTitleDisposable: IDisposable | undefined;
private _widgetManager: TerminalWidgetManager = this._instantiationService.createInstance(TerminalWidgetManager);
@ -316,6 +318,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._fixedRows = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.rows;
this._fixedCols = _shellLaunchConfig.attachPersistentProcess?.fixedDimensions?.cols;
this._icon = _shellLaunchConfig.attachPersistentProcess?.icon || _shellLaunchConfig.icon;
// the resource is already set when it's been moved from another window
this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title);
@ -411,11 +414,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
private _getIcon(): TerminalIcon | undefined {
const icon = this._shellLaunchConfig.icon || this._shellLaunchConfig.attachPersistentProcess?.icon;
if (!icon) {
return this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
if (!this._icon) {
this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
}
return icon;
return this._icon;
}
private _getColor(): string | undefined {
@ -1850,7 +1852,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
matchOnDescription: true
});
if (result && result.description) {
this.shellLaunchConfig.icon = iconRegistry.get(result.description);
this._icon = iconRegistry.get(result.description);
this._onIconChanged.fire(this);
}
}

View file

@ -886,6 +886,7 @@ class ScrollableMarkdownMessage extends Disposable {
const rendered = this._register(markdown.render(message, {}));
rendered.element.style.height = '100%';
rendered.element.style.userSelect = 'text';
container.appendChild(rendered.element);
this.scrollable = this._register(new DomScrollableElement(rendered.element, {

View file

@ -11,18 +11,18 @@ import { URI } from 'vs/base/common/uri';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, IWebview, WebviewContentOptions, IWebviewElement, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
/**
* Webview editor overlay that creates and destroys the underlying webview as needed.
* Webview that is absolutely positioned over another element and that can creates and destroys an underlying webview as needed.
*/
export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOverlay {
export class OverlayWebview extends Disposable implements IOverlayWebview {
private readonly _onDidWheel = this._register(new Emitter<IMouseWheelEvent>());
public readonly onDidWheel = this._onDidWheel.event;
private readonly _pendingMessages = new Set<{ readonly message: any, readonly transfer?: readonly ArrayBuffer[] }>();
private readonly _webview = this._register(new MutableDisposable<WebviewElement>());
private readonly _webview = this._register(new MutableDisposable<IWebviewElement>());
private readonly _webviewEvents = this._register(new DisposableStore());
private _html: string = '';
@ -299,7 +299,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv
runFindAction(previous: boolean): void { this._webview.value?.runFindAction(previous); }
private withWebview(f: (webview: Webview) => void): void {
private withWebview(f: (webview: IWebview) => void): void {
if (this._webview.value) {
f(this._webview.value);
}

View file

@ -980,6 +980,36 @@ onDomReady(() => {
assertIsDefined(target.contentDocument).execCommand(data);
});
hostMessaging.onMessage('find', (_event, data) => {
const target = getActiveFrame();
if (!target) {
return;
}
const didFind = (/** @type {any} */ (target.contentWindow)).find(
data.value,
false,
/* backwards*/ data.previous,
/* wrapAround*/ true,
false,
/*aSearchInFrames*/ true,
false);
hostMessaging.postMessage('did-find', didFind);
});
hostMessaging.onMessage('find-stop', (_event, data) => {
const target = getActiveFrame();
if (!target) {
return;
}
if (!data.clearSelection) {
const selection = target.contentWindow.getSelection();
for (let i = 0; i < selection.rangeCount; i++) {
selection.removeRange(selection.getRangeAt(i));
}
}
});
trackFocus({
onFocus: () => hostMessaging.postMessage('did-focus'),
onBlur: () => hostMessaging.postMessage('did-blur')

View file

@ -7,14 +7,14 @@ import { MultiCommand, RedoCommand, SelectAllCommand, UndoCommand } from 'vs/edi
import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard';
import * as nls from 'vs/nls';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
const PRIORITY = 100;
function overrideCommandForWebview(command: MultiCommand | undefined, f: (webview: Webview) => void) {
function overrideCommandForWebview(command: MultiCommand | undefined, f: (webview: IWebview) => void) {
command?.addImplementation(PRIORITY, 'webview', accessor => {
const webviewService = accessor.get(IWebviewService);
const webview = webviewService.activeWebview;

View file

@ -30,17 +30,17 @@ export interface IWebviewService {
/**
* The currently focused webview.
*/
readonly activeWebview: Webview | undefined;
readonly activeWebview: IWebview | undefined;
/**
* All webviews.
*/
readonly webviews: Iterable<Webview>;
readonly webviews: Iterable<IWebview>;
/**
* Fired when the currently focused webview changes.
*/
readonly onDidChangeActiveWebview: Event<Webview | undefined>;
readonly onDidChangeActiveWebview: Event<IWebview | undefined>;
/**
* Create a basic webview dom element.
@ -50,7 +50,7 @@ export interface IWebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions,
extension: WebviewExtensionDescription | undefined,
): WebviewElement;
): IWebviewElement;
/**
* Create a lazily created webview element that is overlaid on top of another element.
@ -63,7 +63,7 @@ export interface IWebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions,
extension: WebviewExtensionDescription | undefined,
): WebviewOverlay;
): IOverlayWebview;
}
export const enum WebviewContentPurpose {
@ -119,7 +119,7 @@ export interface WebviewMessageReceivedEvent {
readonly transfer?: readonly ArrayBuffer[];
}
export interface Webview extends IDisposable {
export interface IWebview extends IDisposable {
readonly id: string;
@ -169,12 +169,12 @@ export interface Webview extends IDisposable {
/**
* Basic webview rendered directly in the dom
*/
export interface WebviewElement extends Webview {
export interface IWebviewElement extends IWebview {
/**
* Append the webview to a HTML element.
*
* Note that the webview content will be destroyed if any part of the parent hierarchy
* changes. You can avoid this by using a {@link WebviewOverlay} instead.
* changes. You can avoid this by using a {@link IOverlayWebview} instead.
*
* @param parent Element to append the webview to.
*/
@ -182,15 +182,15 @@ export interface WebviewElement extends Webview {
}
/**
* Lazily created {@link Webview} that is absolutely positioned over another element.
* Lazily created {@link IWebview} that is absolutely positioned over another element.
*
* Absolute positioning lets us avoid having the webview be re-parented, which would destroy the
* webview's content.
*
* Note that the underlying webview owned by a `WebviewOverlay` can be dynamically created
* and destroyed depending on who has {@link WebviewOverlay.claim claimed} or {@link WebviewOverlay.release released} it.
* and destroyed depending on who has {@link IOverlayWebview.claim claimed} or {@link IOverlayWebview.release released} it.
*/
export interface WebviewOverlay extends Webview {
export interface IOverlayWebview extends IWebview {
/**
* The HTML element that holds the webview.
*/
@ -204,7 +204,7 @@ export interface WebviewOverlay extends Webview {
* This will create the underlying webview element.
*
* @param claimant Identifier for the object claiming the webview.
* This must match the `claimant` passed to {@link WebviewOverlay.release}.
* This must match the `claimant` passed to {@link IOverlayWebview.release}.
*/
claim(claimant: any, scopedContextKeyService: IContextKeyService | undefined): void;
@ -215,7 +215,7 @@ export interface WebviewOverlay extends Webview {
* cause the underlying webview element to be destoryed.
*
* @param claimant Identifier for the object releasing its claim on the webview.
* This must match the `claimant` passed to {@link WebviewOverlay.claim}.
* This must match the `claimant` passed to {@link IOverlayWebview.claim}.
*/
release(claimant: any): void;

View file

@ -10,7 +10,7 @@ import { IAction } from 'vs/base/common/actions';
import { ThrottledDelayer } from 'vs/base/common/async';
import { streamToBuffer } from 'vs/base/common/buffer';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
@ -22,6 +22,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
@ -31,7 +32,8 @@ import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPor
import { asWebviewUri, decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/api/common/shared/webview';
import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
import { areWebviewContentOptionsEqual, Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { areWebviewContentOptionsEqual, IWebview, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const enum WebviewMessageChannels {
@ -41,6 +43,7 @@ export const enum WebviewMessageChannels {
didFocus = 'did-focus',
didBlur = 'did-blur',
didLoad = 'did-load',
didFind = 'did-find',
doUpdateState = 'do-update-state',
doReload = 'do-reload',
setConfirmBeforeClose = 'set-confirm-before-close',
@ -88,7 +91,7 @@ namespace WebviewState {
export type State = typeof Ready | Initializing;
}
export class IFrameWebview extends Disposable implements Webview {
export class WebviewElement extends Disposable implements IWebview, WebviewFindDelegate {
protected get platform(): string { return 'browser'; }
@ -129,6 +132,9 @@ export class IFrameWebview extends Disposable implements Webview {
private readonly _messageHandlers = new Map<string, Set<(data: any) => void>>();
protected readonly _webviewFindWidget: WebviewFindWidget | undefined;
public readonly checkImeCompletionState = true;
constructor(
public readonly id: string,
private readonly options: WebviewOptions,
@ -145,6 +151,7 @@ export class IFrameWebview extends Disposable implements Webview {
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ITunnelService private readonly _tunnelService: ITunnelService,
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
@ -215,6 +222,10 @@ export class IFrameWebview extends Disposable implements Webview {
this.handleFocusChange(false);
}));
this._register(this.on(WebviewMessageChannels.didFind, (didFind: boolean) => {
this._hasFindResult.fire(didFind);
}));
this._register(this.on<{ message: string }>(WebviewMessageChannels.fatalError, (e) => {
notificationService.error(localize('fatalErrorMessage', "Error loading webview: {0}", e.message));
}));
@ -312,13 +323,16 @@ export class IFrameWebview extends Disposable implements Webview {
}
}));
if (options.enableFindWidget) {
this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this));
this.styledFindWidget();
}
this.initElement(extension, options);
}
override dispose(): void {
if (this.element) {
this.element.remove();
}
this.element?.remove();
this._element = undefined;
this._onDidDispose.fire();
@ -418,9 +432,14 @@ export class IFrameWebview extends Disposable implements Webview {
}
public mountTo(parent: HTMLElement) {
if (this.element) {
parent.appendChild(this.element);
if (!this.element) {
return;
}
if (this._webviewFindWidget) {
parent.appendChild(this._webviewFindWidget.getDomNode()!);
}
parent.appendChild(this.element);
}
protected get webviewContentEndpoint(): string {
@ -467,7 +486,7 @@ export class IFrameWebview extends Disposable implements Webview {
}
this._hasAlertedAboutMissingCsp = true;
if (this.extension && this.extension.id) {
if (this.extension?.id) {
if (this._environmentService.isExtensionDevelopment) {
this._onMissingCsp.fire(this.extension.id);
}
@ -586,6 +605,12 @@ export class IFrameWebview extends Disposable implements Webview {
}
this._send('styles', { styles, activeTheme, themeName: themeLabel });
this.styledFindWidget();
}
private styledFindWidget() {
this._webviewFindWidget?.updateTheme(this.webviewThemeDataProvider.getTheme());
}
private handleFocusChange(isFocused: boolean): void {
@ -757,15 +782,51 @@ export class IFrameWebview extends Disposable implements Webview {
});
}
public showFind(): void {
// noop
protected readonly _hasFindResult = this._register(new Emitter<boolean>());
public readonly hasFindResult: Event<boolean> = this._hasFindResult.event;
protected readonly _onDidStopFind = this._register(new Emitter<void>());
public readonly onDidStopFind: Event<void> = this._onDidStopFind.event;
/**
* Webviews expose a stateful find API.
* Successive calls to find will move forward or backward through onFindResults
* depending on the supplied options.
*
* @param value The string to search for. Empty strings are ignored.
*/
public find(value: string, previous: boolean): void {
if (!this.element) {
return;
}
this._send('find', { value, previous });
}
public hideFind(): void {
// noop
public startFind(value: string) {
if (!value || !this.element) {
return;
}
this._send('find', { value });
}
public runFindAction(previous: boolean): void {
// noop
public stopFind(keepSelection?: boolean): void {
if (!this.element) {
return;
}
this._send('find-stop', { keepSelection });
this._onDidStopFind.fire();
}
public showFind() {
this._webviewFindWidget?.reveal();
}
public hideFind() {
this._webviewFindWidget?.hide();
}
public runFindAction(previous: boolean) {
this._webviewFindWidget?.find(previous);
}
}

View file

@ -7,9 +7,9 @@ import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
import { IWebviewService, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay';
import { IWebviewService, IWebview, WebviewContentOptions, IWebviewElement, WebviewExtensionDescription, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewElement } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { OverlayWebview } from './overlayWebview';
export class WebviewService extends Disposable implements IWebviewService {
declare readonly _serviceBrand: undefined;
@ -23,24 +23,24 @@ export class WebviewService extends Disposable implements IWebviewService {
this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider);
}
private _activeWebview?: Webview;
private _activeWebview?: IWebview;
public get activeWebview() { return this._activeWebview; }
private updateActiveWebview(value: Webview | undefined) {
private updateActiveWebview(value: IWebview | undefined) {
if (value !== this._activeWebview) {
this._activeWebview = value;
this._onDidChangeActiveWebview.fire(value);
}
}
private _webviews = new Set<Webview>();
private _webviews = new Set<IWebview>();
public get webviews(): Iterable<Webview> {
public get webviews(): Iterable<IWebview> {
return this._webviews.values();
}
private readonly _onDidChangeActiveWebview = this._register(new Emitter<Webview | undefined>());
private readonly _onDidChangeActiveWebview = this._register(new Emitter<IWebview | undefined>());
public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event;
createWebviewElement(
@ -48,8 +48,8 @@ export class WebviewService extends Disposable implements IWebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions,
extension: WebviewExtensionDescription | undefined,
): WebviewElement {
const webview = this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider);
): IWebviewElement {
const webview = this._instantiationService.createInstance(WebviewElement, id, options, contentOptions, extension, this._webviewThemeDataProvider);
this.registerNewWebview(webview);
return webview;
}
@ -59,13 +59,13 @@ export class WebviewService extends Disposable implements IWebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions,
extension: WebviewExtensionDescription | undefined,
): WebviewOverlay {
const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension);
): IOverlayWebview {
const webview = this._instantiationService.createInstance(OverlayWebview, id, options, contentOptions, extension);
this.registerNewWebview(webview);
return webview;
}
protected registerNewWebview(webview: Webview) {
protected registerNewWebview(webview: IWebview) {
this._webviews.add(webview);
webview.onDidFocus(() => {

View file

@ -5,7 +5,7 @@
import * as DOM from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
/**
* Allows webviews to monitor when an element in the VS Code editor is being dragged/dropped.
@ -14,7 +14,7 @@ import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
* event so it can handle editor element drag drop.
*/
export class WebviewWindowDragMonitor extends Disposable {
constructor(getWebview: () => Webview | undefined) {
constructor(getWebview: () => IWebview | undefined) {
super();
this._register(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => {

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { Delayer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { IMenuService } from 'vs/platform/actions/common/actions';
@ -22,27 +21,23 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { FindInFrameOptions, IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { IFrameWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget';
import { WebviewElement, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { WindowIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
/**
* Webview backed by an iframe but that uses Electron APIs to power the webview.
*/
export class ElectronIframeWebview extends IFrameWebview implements WebviewFindDelegate {
export class ElectronWebviewElement extends WebviewElement {
private readonly _webviewKeyboardHandler: WindowIgnoreMenuShortcutsManager;
private _webviewFindWidget: WebviewFindWidget | undefined;
private _findStarted: boolean = false;
private _cachedHtmlContent: string | undefined;
private readonly _webviewMainService: IWebviewManagerService;
private readonly _iframeDelayer = this._register(new Delayer<void>(200));
public readonly checkImeCompletionState = true;
protected override get platform() { return 'electron'; }
constructor(
@ -67,7 +62,7 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
) {
super(id, options, contentOptions, extension, webviewThemeDataProvider,
configurationService, contextMenuService, menuService, notificationService, environmentService,
fileService, logService, remoteAuthorityResolverService, telemetryService, tunnelService);
fileService, logService, remoteAuthorityResolverService, telemetryService, tunnelService, instantiationService);
this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, nativeHostService);
@ -82,8 +77,6 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
}));
if (options.enableFindWidget) {
this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this));
this._register(this.onDidHtmlChange((newContent) => {
if (this._findStarted && this._cachedHtmlContent !== newContent) {
this.stopFind(false);
@ -94,42 +87,35 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
this._register(this._webviewMainService.onFoundInFrame((result) => {
this._hasFindResult.fire(result.matches > 0);
}));
this.styledFindWidget();
}
}
public override mountTo(parent: HTMLElement) {
if (!this.element) {
return;
}
if (this._webviewFindWidget) {
parent.appendChild(this._webviewFindWidget.getDomNode()!);
}
parent.appendChild(this.element);
}
protected override get webviewContentEndpoint(): string {
return `${Schemas.vscodeWebview}://${this.id}`;
}
protected override style(): void {
super.style();
this.styledFindWidget();
/**
* Webviews expose a stateful find API.
* Successive calls to find will move forward or backward through onFindResults
* depending on the supplied options.
*
* @param value The string to search for. Empty strings are ignored.
*/
public override find(value: string, previous: boolean): void {
if (!this.element) {
return;
}
if (!this._findStarted) {
this.startFind(value);
} else {
// continuing the find, so set findNext to false
const options: FindInFrameOptions = { forward: !previous, findNext: false, matchCase: false };
this._webviewMainService.findInFrame({ windowId: this.nativeHostService.windowId }, this.id, value, options);
}
}
private styledFindWidget() {
this._webviewFindWidget?.updateTheme(this.webviewThemeDataProvider.getTheme());
}
private readonly _hasFindResult = this._register(new Emitter<boolean>());
public readonly hasFindResult: Event<boolean> = this._hasFindResult.event;
private readonly _onDidStopFind = this._register(new Emitter<void>());
public readonly onDidStopFind: Event<void> = this._onDidStopFind.event;
public startFind(value: string) {
public override startFind(value: string) {
if (!value || !this.element) {
return;
}
@ -147,28 +133,7 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
});
}
/**
* Webviews expose a stateful find API.
* Successive calls to find will move forward or backward through onFindResults
* depending on the supplied options.
*
* @param value The string to search for. Empty strings are ignored.
*/
public find(value: string, previous: boolean): void {
if (!this.element) {
return;
}
if (!this._findStarted) {
this.startFind(value);
} else {
// continuing the find, so set findNext to false
const options: FindInFrameOptions = { forward: !previous, findNext: false, matchCase: false };
this._webviewMainService.findInFrame({ windowId: this.nativeHostService.windowId }, this.id, value, options);
}
}
public stopFind(keepSelection?: boolean): void {
public override stopFind(keepSelection?: boolean): void {
if (!this.element) {
return;
}
@ -179,16 +144,4 @@ export class ElectronIframeWebview extends IFrameWebview implements WebviewFindD
});
this._onDidStopFind.fire();
}
public override showFind() {
this._webviewFindWidget?.reveal();
}
public override hideFind() {
this._webviewFindWidget?.hide();
}
public override runFindAction(previous: boolean) {
this._webviewFindWidget?.find(previous);
}
}

View file

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewElement, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService';
import { ElectronIframeWebview } from 'vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement';
import { ElectronWebviewElement } from 'vs/workbench/contrib/webview/electron-sandbox/webviewElement';
export class ElectronWebviewService extends WebviewService {
@ -14,8 +14,8 @@ export class ElectronWebviewService extends WebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions,
extension: WebviewExtensionDescription | undefined,
): WebviewElement {
const webview = this._instantiationService.createInstance(ElectronIframeWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider);
): IWebviewElement {
const webview = this._instantiationService.createInstance(ElectronWebviewElement, id, options, contentOptions, extension, this._webviewThemeDataProvider);
this.registerNewWebview(webview);
return webview;
}

View file

@ -11,7 +11,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -125,7 +125,7 @@ export class ReloadWebviewAction extends Action2 {
}
}
export function getActiveWebviewEditor(accessor: ServicesAccessor): Webview | undefined {
export function getActiveWebviewEditor(accessor: ServicesAccessor): IWebview | undefined {
const editorService = accessor.get(IEditorService);
const activeEditor = editorService.activeEditor;
return activeEditor instanceof WebviewInput ? activeEditor.webview : undefined;

View file

@ -17,7 +17,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService';
@ -56,7 +56,7 @@ export class WebviewEditor extends EditorPane {
super(WebviewEditor.ID, telemetryService, themeService, storageService);
}
private get webview(): WebviewOverlay | undefined {
private get webview(): IOverlayWebview | undefined {
return this.input instanceof WebviewInput ? this.input.webview : undefined;
}
@ -175,13 +175,13 @@ export class WebviewEditor extends EditorPane {
this._webviewVisibleDisposables.add(this.trackFocus(input.webview));
}
private synchronizeWebviewContainerDimensions(webview: WebviewOverlay, dimension?: DOM.Dimension) {
private synchronizeWebviewContainerDimensions(webview: IOverlayWebview, dimension?: DOM.Dimension) {
if (this._element) {
webview.layoutWebviewOverElement(this._element.parentElement!, dimension);
}
}
private trackFocus(webview: WebviewOverlay): IDisposable {
private trackFocus(webview: IOverlayWebview): IDisposable {
const store = new DisposableStore();
// Track focus in webview content

View file

@ -7,7 +7,7 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { EditorInputCapabilities, GroupIdentifier, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewIconManager, WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager';
export class WebviewInput extends EditorInput {
@ -30,7 +30,7 @@ export class WebviewInput extends EditorInput {
private _iconPath?: WebviewIcons;
private _group?: GroupIdentifier;
private _webview: WebviewOverlay;
private _webview: IOverlayWebview;
private _hasTransfered = false;
@ -45,7 +45,7 @@ export class WebviewInput extends EditorInput {
public readonly id: string,
public readonly viewType: string,
name: string,
webview: WebviewOverlay,
webview: IOverlayWebview,
private readonly _iconManager: WebviewIconManager,
) {
super();
@ -79,7 +79,7 @@ export class WebviewInput extends EditorInput {
this._onDidChangeLabel.fire();
}
public get webview(): WebviewOverlay {
public get webview(): IOverlayWebview {
return this._webview;
}

View file

@ -15,7 +15,7 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiatio
import { GroupIdentifier } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewIconManager, WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
@ -102,7 +102,7 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput {
id: string,
viewType: string,
name: string,
webview: WebviewOverlay,
webview: IOverlayWebview,
@IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService,
) {
super(id, viewType, name, webview, _webviewWorkbenchService.iconManager);

View file

@ -22,7 +22,7 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
import { IWebviewService, WebviewContentPurpose, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IOverlayWebview, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -35,7 +35,7 @@ const storageKeys = {
export class WebviewViewPane extends ViewPane {
private readonly _webview = this._register(new MutableDisposable<WebviewOverlay>());
private readonly _webview = this._register(new MutableDisposable<IOverlayWebview>());
private readonly _webviewDisposables = this._register(new DisposableStore());
private _activated = false;

View file

@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview';
export const IWebviewViewService = createDecorator<IWebviewViewService>('webviewViewService');
@ -15,7 +15,7 @@ export interface WebviewView {
title?: string;
description?: string;
readonly webview: WebviewOverlay;
readonly webview: IOverlayWebview;
readonly onDidChangeVisibility: Event<boolean>;
readonly onDispose: Event<void>;

View file

@ -29,7 +29,7 @@ import { WORKSPACE_TRUST_BANNER, WORKSPACE_TRUST_EMPTY_WINDOW, WORKSPACE_TRUST_E
import { IEditorSerializer, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspaceFoldersWillChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { isWeb } from 'vs/base/common/platform';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
@ -279,10 +279,10 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon
if (!this.workspaceTrustEnablementService.isWorkspaceTrustEnabled()) {
return;
}
const trusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
// eslint-disable-next-line no-async-promise-executor
return e.join(new Promise(async resolve => {
const addWorkspaceFolder = async (e: IWorkspaceFoldersWillChangeEvent): Promise<void> => {
const trusted = this.workspaceTrustManagementService.isWorkspaceTrusted();
// Workspace is trusted and there are added/changed folders
if (trusted && (e.changes.added.length || e.changes.changed.length)) {
const addedFoldersTrustInfo = await Promise.all(e.changes.added.map(folder => this.workspaceTrustManagementService.getUriTrustInfo(folder.uri)));
@ -305,13 +305,11 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon
// Mark added/changed folders as trusted
await this.workspaceTrustManagementService.setUrisTrust(addedFoldersTrustInfo.map(i => i.uri), result.choice === 0);
resolve();
}
}
};
resolve();
}));
return e.join(addWorkspaceFolder(e));
}));
this._register(this.workspaceTrustManagementService.onDidChangeTrust(trusted => {

View file

@ -263,7 +263,7 @@ export class DecorationsService implements IDecorationsService {
}
registerDecorationsProvider(provider: IDecorationsProvider): IDisposable {
const rm = this._provider.push(provider);
const rm = this._provider.unshift(provider);
this._onDidChangeDecorations.fire({
// everything might have changed

View file

@ -300,4 +300,31 @@ suite('DecorationsService', function () {
emitter.fire([uri]);
});
});
test('FileDecorationProvider intermittently fails #133210', async function () {
const invokeOrder: string[] = [];
service.registerDecorationsProvider(new class {
label = 'Provider-1';
onDidChange = Event.None;
provideDecorations() {
invokeOrder.push(this.label);
return undefined;
}
});
service.registerDecorationsProvider(new class {
label = 'Provider-2';
onDidChange = Event.None;
provideDecorations() {
invokeOrder.push(this.label);
return undefined;
}
});
service.getDecoration(URI.parse('test://me/path'), false);
assert.deepStrictEqual(invokeOrder, ['Provider-2', 'Provider-1']);
});
});

View file

@ -547,7 +547,8 @@ export class TunnelModel extends Disposable {
remote: { host: tunnel.remoteHost, port: tunnel.remotePort },
local: tunnel.localPort,
name: tunnel.name,
privacy: tunnel.privacy
privacy: tunnel.privacy,
elevateIfNeeded: true
});
}
}

View file

@ -6,11 +6,14 @@
import * as assert from 'assert';
import { MarkerService } from 'vs/platform/markers/common/markerService';
import { MainThreadDiagnostics } from 'vs/workbench/api/browser/mainThreadDiagnostics';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/workbench/test/common/workbenchTestServices';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { timeout } from 'vs/base/common/async';
suite('MainThreadDiagnostics', function () {
@ -57,4 +60,54 @@ suite('MainThreadDiagnostics', function () {
diag.dispose();
assert.strictEqual(markerService.read().length, 0);
});
test('OnDidChangeDiagnostics triggers twice on same diagnostics #136434', function () {
return runWithFakedTimers({}, async () => {
const changedData: [UriComponents, IMarkerData[]][][] = [];
let diag = new MainThreadDiagnostics(
new class implements IExtHostContext {
remoteAuthority = '';
extensionHostKind = ExtensionHostKind.LocalProcess;
assertRegistered() { }
set(v: any): any { return null; }
getProxy(): any {
return {
$acceptMarkersChange(data: [UriComponents, IMarkerData[]][]) {
changedData.push(data);
}
};
}
drain(): any { return null; }
},
markerService,
new class extends mock<IUriIdentityService>() {
override asCanonicalUri(uri: URI) { return uri; }
}
);
const markerDataStub = {
code: '666',
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
severity: 1,
source: 'me'
};
const target = URI.file('a');
diag.$changeMany('foo', [[target, [{ ...markerDataStub, message: 'same_owner' }]]]);
markerService.changeOne('bar', target, [{ ...markerDataStub, message: 'forgein_owner' }]);
// added one marker via the API and one via the ext host. the latter must not
// trigger an event to the extension host
await timeout(0);
assert.strictEqual(markerService.read().length, 2);
assert.strictEqual(changedData.length, 1);
assert.strictEqual(changedData[0][0][1][0].message, 'forgein_owner');
});
});
});

View file

@ -448,6 +448,13 @@ interface IWorkbenchConstructionOptions {
*/
readonly additionalTrustedDomains?: string[];
/**
* Urls that will be opened externally that are allowed access
* to the opener window. This is primarily used to allow
* `window.close()` to be called from the newly opened window.
*/
readonly openerAllowedExternalUrlPrefixes?: string[];
/**
* Support for URL callbacks.
*/

View file

@ -470,12 +470,12 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@ts-morph/common@~0.10.0":
version "0.10.0"
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.10.0.tgz#d032ea6f6d4115b72fa50ad56009baebcc1e71b8"
integrity sha512-6wC+CovwzxLP+bQZcqHJEbZ7ViaIfsid8VzsVjJRkdfCQ8C8K5mm1+9/wkgmn814BPATtgSgFuDmVJnIb8/leg==
"@ts-morph/common@~0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.11.1.tgz#281af2a0642b19354d8aa07a0d50dfdb4aa8164e"
integrity sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==
dependencies:
fast-glob "^3.2.5"
fast-glob "^3.2.7"
minimatch "^3.0.4"
mkdirp "^1.0.4"
path-browserify "^1.0.1"
@ -1455,7 +1455,7 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
array-back@^3.0.1:
array-back@^3.0.1, array-back@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
@ -2480,12 +2480,12 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
command-line-args@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a"
integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==
command-line-args@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.0.tgz#087b02748272169741f1fd7c785b295df079b9be"
integrity sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==
dependencies:
array-back "^3.0.1"
array-back "^3.1.0"
find-replace "^3.0.0"
lodash.camelcase "^4.3.0"
typical "^4.0.0"
@ -3976,17 +3976,16 @@ fast-glob@^3.1.1, fast-glob@^3.2.4:
micromatch "^4.0.2"
picomatch "^2.2.1"
fast-glob@^3.2.5:
version "3.2.5"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661"
integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==
fast-glob@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.0"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.2"
picomatch "^2.2.1"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
@ -4477,7 +4476,7 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0:
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@ -6517,6 +6516,14 @@ micromatch@^4.0.0, micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
micromatch@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
dependencies:
braces "^3.0.1"
picomatch "^2.2.3"
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@ -7602,6 +7609,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
picomatch@^2.2.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
pidtree@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
@ -9911,12 +9923,12 @@ ts-loader@^9.2.3:
micromatch "^4.0.0"
semver "^7.3.4"
ts-morph@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-11.0.0.tgz#511b3caa194739fef0619367f8e65de9b475e1d4"
integrity sha512-u5y0jaft5c0sRFnU0K8cZhhsvPUtXjZK5L31JLIhP17qcqo9MDjwsSYLg3UryQDzlktv8wyf/UtoqpFLDYHijg==
ts-morph@^12.2.0:
version "12.2.0"
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-12.2.0.tgz#3332319cecd44aff0b7b410f1fe39637499b1a1b"
integrity sha512-WHXLtFDcIRwoqaiu0elAoZ/AmI+SwwDafnPKjgJmdwJ2gRVO0jMKBt88rV2liT/c6MTsXyuWbGFiHe9MRddWJw==
dependencies:
"@ts-morph/common" "~0.10.0"
"@ts-morph/common" "~0.11.1"
code-block-writer "^10.1.1"
tsec@0.1.4:
@ -10447,14 +10459,6 @@ vscode-regexpp@^3.1.0:
resolved "https://registry.yarnpkg.com/vscode-regexpp/-/vscode-regexpp-3.1.0.tgz#42d059b6fffe99bd42939c0d013f632f0cad823f"
integrity sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg==
vscode-ripgrep@^1.11.3:
version "1.11.3"
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.11.3.tgz#a997f4f4535dfeb9d775f04053c1247454d7a37a"
integrity sha512-fdD+BciXiEO1iWTrV/S3sAthlK/tHRBjHF+aJIZDxUMD/q9wpNq+YPFEiLCrW+8epahfR19241DeVHHgX/I4Ww==
dependencies:
https-proxy-agent "^4.0.0"
proxy-from-env "^1.1.0"
vscode-ripgrep@^1.12.1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.12.1.tgz#4a319809d4010ea230659ce605fddacd1e36a589"
@ -10463,14 +10467,14 @@ vscode-ripgrep@^1.12.1:
https-proxy-agent "^4.0.0"
proxy-from-env "^1.1.0"
vscode-telemetry-extractor@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.8.0.tgz#5562106fe2eebfce0593f336c91f5a5ddc154cee"
integrity sha512-jWe+caeLyB/F3V0EqsdkCC98wXx9+XLbm6EoPngz0sC4GOM7lcDSnVhUXzrIhZD/TSRPSPGlxp5r4/CrvhbmMQ==
vscode-telemetry-extractor@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.9.3.tgz#239d89a13b49c5ffe49eb7b2a6f8c1021ff584c7"
integrity sha512-NWmao/CzDj347Minf+xuP/eDxSL7T6+bEABs3z/YcwXV9sHpcorXBqOVL7cZj1n+p0DQmyZUYo9byh3iXsx72g==
dependencies:
command-line-args "^5.1.1"
ts-morph "^11.0.0"
vscode-ripgrep "^1.11.3"
command-line-args "^5.2.0"
ts-morph "^12.2.0"
vscode-ripgrep "^1.12.1"
vscode-textmate@5.4.1:
version "5.4.1"