Merge remote-tracking branch 'origin/main' into alex/main-process-extension-host

This commit is contained in:
Alex Dima 2021-11-11 15:09:20 +01:00
commit 18777d92ea
No known key found for this signature in database
GPG key ID: 39563C1504FDD0C9
133 changed files with 2551 additions and 935 deletions

View file

@ -86,6 +86,7 @@
"splitview",
"table",
"list",
"git"
"git",
"sash"
]
}

View file

@ -233,7 +233,7 @@ steps:
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
APP_NAME="`ls $APP_ROOT | head -n 1`"
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \
yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests
yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote
timeoutInMinutes: 5
displayName: Run smoke tests (Remote)
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))

View file

@ -220,7 +220,7 @@ steps:
set -e
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests
yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote
timeoutInMinutes: 5
displayName: Run smoke tests (Remote)
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))

View file

@ -216,7 +216,7 @@ steps:
$ErrorActionPreference = "Stop"
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
$env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"
exec { yarn smoketest-no-compile --build "$AppRoot" --remote }
exec { yarn smoketest-no-compile --build "$AppRoot" --remote --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests-remote }
displayName: Run smoke tests (Remote)
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))

View file

@ -7,7 +7,7 @@ module.exports = new class ApiEventNaming {
constructor() {
this.meta = {
messages: {
comment: 'region comments should start with the GH issue link, e.g #region https://github.com/microsoft/vscode/issues/<number>',
comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/<number>',
}
};
}
@ -15,14 +15,14 @@ module.exports = new class ApiEventNaming {
const sourceCode = context.getSourceCode();
return {
['Program']: (_node) => {
for (let comment of sourceCode.getAllComments()) {
for (const comment of sourceCode.getAllComments()) {
if (comment.type !== 'Line') {
continue;
}
if (!comment.value.match(/^\s*#region /)) {
if (!/^\s*#region /.test(comment.value)) {
continue;
}
if (!comment.value.match(/https:\/\/github.com\/microsoft\/vscode\/issues\/\d+/i)) {
if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) {
context.report({
node: comment,
messageId: 'comment',

View file

@ -9,7 +9,7 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
comment: 'region comments should start with the GH issue link, e.g #region https://github.com/microsoft/vscode/issues/<number>',
comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/<number>',
}
};
@ -17,18 +17,16 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule {
const sourceCode = context.getSourceCode();
return {
['Program']: (_node: any) => {
for (let comment of sourceCode.getAllComments()) {
for (const comment of sourceCode.getAllComments()) {
if (comment.type !== 'Line') {
continue;
}
if (!comment.value.match(/^\s*#region /)) {
if (!/^\s*#region /.test(comment.value)) {
continue;
}
if (!comment.value.match(/https:\/\/github.com\/microsoft\/vscode\/issues\/\d+/i)) {
if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) {
context.report({
node: <any>comment,
messageId: 'comment',

View file

@ -8,7 +8,7 @@ let err = false;
const majorNodeVersion = parseInt(/^(\d+)\./.exec(process.versions.node)[1]);
if (majorNodeVersion < 14 || majorNodeVersion >= 17) {
console.error('\033[1;31m*** Please use node.js versions >=14 and <=17.\033[0;0m');
console.error('\033[1;31m*** Please use node.js versions >=14 and <17.\033[0;0m');
err = true;
}

View file

@ -49,7 +49,7 @@ export async function wrapWithAbbreviation(args: any): Promise<boolean> {
const helper = getEmmetHelper();
const operationRanges = editor.selections.sort((a, b) => a.start.compareTo(b.start)).map(selection => {
const operationRanges = Array.from(editor.selections).sort((a, b) => a.start.compareTo(b.start)).map(selection => {
let rangeToReplace: vscode.Range = selection;
// wrap around the node if the selection falls inside its open or close tag
{

View file

@ -8,8 +8,8 @@ import { getHtmlFlatNode, offsetRangeToSelection, validate } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
let balanceOutStack: Array<vscode.Selection[]> = [];
let lastBalancedSelections: vscode.Selection[] = [];
let balanceOutStack: Array<readonly vscode.Selection[]> = [];
let lastBalancedSelections: readonly vscode.Selection[] = [];
export function balanceOut() {
balance(true);
@ -31,10 +31,8 @@ function balance(out: boolean) {
}
const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const range = rangeFn(document, rootNode, selection);
newSelections.push(range);
let newSelections: readonly vscode.Selection[] = editor.selections.map(selection => {
return rangeFn(document, rootNode, selection);
});
// check whether we are starting a balance elsewhere
@ -122,7 +120,7 @@ function getRangeToBalanceIn(document: vscode.TextDocument, rootNode: HtmlFlatNo
return offsetRangeToSelection(document, firstChild.start, firstChild.end);
}
function areSameSelections(a: vscode.Selection[], b: vscode.Selection[]): boolean {
function areSameSelections(a: readonly vscode.Selection[], b: readonly vscode.Selection[]): boolean {
if (a.length !== b.length) {
return false;
}

View file

@ -21,7 +21,7 @@ export function mergeLines() {
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
Array.from(editor.selections).reverse().forEach(selection => {
const textEdit = getRangesToReplace(editor.document, selection, rootNode);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);

View file

@ -19,7 +19,7 @@ export function removeTag() {
return;
}
let finalRangesToRemove = editor.selections.reverse()
let finalRangesToRemove = Array.from(editor.selections).reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToRemove(editor.document, rootNode, selection)), []);

View file

@ -21,7 +21,7 @@ export function splitJoinTag() {
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
Array.from(editor.selections).reverse().forEach(selection => {
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);

View file

@ -28,7 +28,7 @@ export function toggleComment(): Thenable<boolean> | undefined {
return editor.edit(editBuilder => {
let allEdits: vscode.TextEdit[][] = [];
editor.selections.reverse().forEach(selection => {
Array.from(editor.selections).reverse().forEach(selection => {
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
if (edits.length > 0) {
allEdits.push(edits);

View file

@ -23,7 +23,7 @@ export function updateImageSize(): Promise<boolean> | undefined {
}
const editor = window.activeTextEditor;
const allUpdatesPromise = editor.selections.reverse().map(selection => {
const allUpdatesPromise = Array.from(editor.selections).reverse().map(selection => {
const position = selection.isReversed ? selection.active : selection.anchor;
if (!isStyleSheet(editor.document.languageId)) {
return updateImageSizeHTML(editor, position);

View file

@ -25,7 +25,7 @@ export async function updateTag(tagName: string | undefined): Promise<boolean |
return;
}
const rangesToUpdate = editor.selections.reverse()
const rangesToUpdate = Array.from(editor.selections).reverse()
.reduce<TagRange[]>((prev, selection) =>
prev.concat(getRangesToUpdate(document, selection, rootNode)), []);
if (!rangesToUpdate.length) {

View file

@ -1,5 +1,5 @@
#!/bin/sh
VSCODE_GIT_ASKPASS_PIPE=`mktemp`
ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_EXTRA_ARGS" "$VSCODE_GIT_ASKPASS_MAIN" $*
ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $VSCODE_GIT_ASKPASS_EXTRA_ARGS $*
cat $VSCODE_GIT_ASKPASS_PIPE
rm $VSCODE_GIT_ASKPASS_PIPE

View file

@ -49,7 +49,7 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
return result.join('');
}
export function toLineRanges(selections: Selection[], textDocument: TextDocument): Range[] {
export function toLineRanges(selections: readonly Selection[], textDocument: TextDocument): Range[] {
const lineRanges = selections.map(s => {
const startLine = textDocument.lineAt(s.start.line);
const endLine = textDocument.lineAt(s.end.line);

View file

@ -56,7 +56,9 @@ suite('git smoke test', function () {
git = ext!.exports.getAPI(1);
if (git.repositories.length === 0) {
await eventToPromise(git.onDidOpenRepository);
const onDidOpenRepository = eventToPromise(git.onDidOpenRepository);
await commands.executeCommand('git.openRepository', cwd);
await onDidOpenRepository;
}
assert.strictEqual(git.repositories.length, 1);

View file

@ -22,7 +22,7 @@
"html.format.wrapAttributes.preservealigned": "Preserve wrapping of attributes but align.",
"html.format.templating.desc": "Honor django, erb, handlebars and php templating language tags.",
"html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.",
"html.format.wrapAttributesIndentSize.desc": "Alignment size when using 'force aligned' and 'aligned multiple' in `#html.format.wrapAttributes#` or `null` to use the default indent size.",
"html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to 'aligned'.",
"html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.",
"html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.",
"html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.",

View file

@ -161,7 +161,6 @@ export const activate: ActivationFunction<void> = (ctx) => {
pre code {
font-family: var(--vscode-editor-font-family);
font-size: var(--vscode-editor-font-size);
line-height: 1.357em;
white-space: pre-wrap;

View file

@ -134,6 +134,7 @@ window.addEventListener('message', async event => {
root.replaceWith(newContent.querySelector('.markdown-body')!);
documentResource = event.data.source;
} else {
// Compare two elements but skip `data-line`
const areEqual = (a: Element, b: Element): boolean => {
if (a.isEqualNode(b)) {
return true;
@ -143,6 +144,23 @@ window.addEventListener('message', async event => {
return false;
}
const aAttrs = a.attributes;
const bAttrs = b.attributes;
if (aAttrs.length !== bAttrs.length) {
return false;
}
for (let i = 0; i < aAttrs.length; ++i) {
const aAttr = aAttrs[i];
const bAttr = bAttrs[i];
if (aAttr.name !== bAttr.name) {
return false;
}
if (aAttr.value !== bAttr.value && aAttr.name !== 'data-line') {
return false;
}
}
const aChildren = Array.from(a.children);
const bChildren = Array.from(b.children);

View file

@ -120,6 +120,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = [];
private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
public readonly onScroll = this._onScrollEmitter.event;
@ -262,13 +263,13 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
* The first call immediately refreshes the preview,
* calls happening shortly thereafter are debounced.
*/
public refresh() {
public refresh(forceUpdate: boolean = false) {
// Schedule update if none is pending
if (!this.throttleTimer) {
if (this.firstUpdate) {
this.updatePreview(true);
} else {
this.throttleTimer = setTimeout(() => this.updatePreview(true), this.delay);
this.throttleTimer = setTimeout(() => this.updatePreview(forceUpdate), this.delay);
}
}
@ -333,7 +334,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
return;
}
const shouldReloadPage = !this.currentVersion || this.currentVersion.resource.toString() !== pendingVersion.resource.toString();
const shouldReloadPage = forceUpdate || !this.currentVersion || this.currentVersion.resource.toString() !== pendingVersion.resource.toString();
this.currentVersion = pendingVersion;
const content = await (shouldReloadPage
@ -429,7 +430,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
if (uri && uri.scheme === 'file' && !this._fileWatchersBySrc.has(src)) {
const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath);
watcher.onDidChange(() => {
this.refresh();
this.refresh(true);
});
this._fileWatchersBySrc.set(src, watcher);
}

View file

@ -15,10 +15,13 @@ function workspaceFile(...segments: string[]) {
}
async function getLinksForFile(file: vscode.Uri): Promise<vscode.DocumentLink[]> {
return (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file))!;
console.log('getting links', file.toString(), Date.now());
const r = (await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', file))!;
console.log('got links', file.toString(), Date.now());
return r;
}
suite('Markdown Document links', () => {
suite.skip('Markdown Document links', () => {
setup(async () => {
// the tests make the assumption that link providers are already registered
@ -94,7 +97,6 @@ suite('Markdown Document links', () => {
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1);
});
test('Should navigate to line number within non-md file', async () => {
await withFileContents(testFileA, '[b](sub/foo.txt#L3)');
@ -147,15 +149,21 @@ function assertActiveDocumentUri(expectedUri: vscode.Uri) {
}
async function withFileContents(file: vscode.Uri, contents: string): Promise<void> {
console.log('openTextDocument', file.toString(), Date.now());
const document = await vscode.workspace.openTextDocument(file);
console.log('showTextDocument', file.toString(), Date.now());
const editor = await vscode.window.showTextDocument(document);
console.log('editTextDocument', file.toString(), Date.now());
await editor.edit(edit => {
edit.replace(new vscode.Range(0, 0, 1000, 0), contents);
});
console.log('opened done', vscode.window.activeTextEditor?.document.toString(), Date.now());
}
async function executeLink(link: vscode.DocumentLink) {
console.log('executeingLink', link.target?.toString(), Date.now());
const args = JSON.parse(decodeURIComponent(link.target!.query));
await vscode.commands.executeCommand(link.target!.path, args);
console.log('executedLink', vscode.window.activeTextEditor?.document.toString(), Date.now());
}

View file

@ -240,6 +240,7 @@ export class AzureActiveDirectoryService {
}
if (added.length || removed.length) {
Logger.info(`Sending change event with ${added.length} added and ${removed.length} removed`);
onDidChangeSessions.fire({ added: added, removed: removed, changed: [] });
}
}
@ -380,7 +381,7 @@ export class AzureActiveDirectoryService {
throw codeRes.err;
}
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope);
this.setToken(token, scope);
await this.setToken(token, scope);
Logger.info(`Login successful for scopes: ${scope}`);
res.writeHead(302, { Location: '/' });
const session = await this.convertToSession(token);
@ -491,7 +492,7 @@ export class AzureActiveDirectoryService {
}
const token = await this.exchangeCodeForToken(code, verifier, scope);
this.setToken(token, scope);
await this.setToken(token, scope);
const session = await this.convertToSession(token);
resolve(session);
@ -509,6 +510,7 @@ export class AzureActiveDirectoryService {
}
private async setToken(token: IToken, scope: string): Promise<void> {
Logger.info(`Setting token for scopes: ${scope}`);
const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId);
if (existingTokenIndex > -1) {
this._tokens.splice(existingTokenIndex, 1, token);
@ -522,6 +524,7 @@ export class AzureActiveDirectoryService {
this._refreshTimeouts.set(token.sessionId, setTimeout(async () => {
try {
const refreshedToken = await this.refreshToken(token.refreshToken, scope, token.sessionId);
Logger.info('Triggering change session event...');
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
} catch (e) {
if (e.message === REFRESH_NETWORK_FAILURE) {
@ -537,7 +540,7 @@ export class AzureActiveDirectoryService {
}, 1000 * (token.expiresIn - 30)));
}
this.storeTokenData();
await this.storeTokenData();
}
private getTokenFromResponse(json: ITokenResponse, scope: string, existingId?: string): IToken {
@ -649,7 +652,7 @@ export class AzureActiveDirectoryService {
if (result.ok) {
const json = await result.json();
const token = this.getTokenFromResponse(json, scope, sessionId);
this.setToken(token, scope);
await this.setToken(token, scope);
Logger.info(`Token refresh success for scopes: ${token.scope}`);
return token;
} else {

View file

@ -83,6 +83,10 @@ export function activate(context: vscode.ExtensionContext) {
const commandArgs = ['--port=0', '--disable-telemetry'];
const env = getNewEnv();
const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), serverDataFolderName || `${dataFolderName}-testresolver`);
const logsDir = process.env['TESTRESOLVER_LOGS_FOLDER'];
if (logsDir) {
commandArgs.push('--logsPath', logsDir);
}
env['VSCODE_AGENT_FOLDER'] = remoteDataDir;
outputChannel.appendLine(`Using data folder at ${remoteDataDir}`);

View file

@ -59,7 +59,7 @@
},
"dependencies": {
"@microsoft/applicationinsights-web": "^2.6.4",
"@parcel/watcher": "2.0.0",
"@parcel/watcher": "2.0.1",
"@vscode/sqlite3": "4.0.12",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",

View file

@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@microsoft/applicationinsights-web": "^2.6.4",
"@parcel/watcher": "2.0.0",
"@parcel/watcher": "2.0.1",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
"cookie": "^0.4.0",

View file

@ -83,10 +83,10 @@
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.4.tgz#40e1c0ad20743fcee1604a7df2c57faf0aa1af87"
integrity sha512-Ot53G927ykMF8cQ3/zq4foZtdk+Tt1YpX7aUTHxBU7UHNdkEiBvBfZSq+rnlUmKCJ19VatwPG4mNzvcGpBj4og==
"@parcel/watcher@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.0.tgz#ebe992a4838b35c3da9a568eb95a71cb26ddf551"
integrity sha512-ByalKmRRXNNAhwZ0X1r0XeIhh1jG8zgdlvjgHk9ZV3YxiersEGNQkwew+RfqJbIL4gOJfvC2ey6lg5kaeRainw==
"@parcel/watcher@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.1.tgz#ec4bb6c43d9588a1ffd3d2abe6df5b501463c62d"
integrity sha512-XegFF4L8sFn1RzU5KKOZxXUuzgOSwd6+X2ez3Cy6MVhYMbiLZ1moceMTqDhuT3N8DNbdumK3zP1wojsIsnX40w==
dependencies:
node-addon-api "^3.2.1"
node-gyp-build "^4.3.0"

View file

@ -21,8 +21,9 @@ IF "%VSCODEUSERDATADIR%" == "" (
set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH%
set VSCODECRASHDIR=%~dp0\..\..\..\.build\crashes
set VSCODELOGSDIR=%~dp0\..\..\..\.build\logs\remote-integration-tests
set VSCODELOGSDIR=%~dp0\..\..\..\.build\logs\integration-tests-remote
set TESTRESOLVER_DATA_FOLDER=%TMP%\testresolverdatafolder-%RANDOM%-%TIME:~6,5%
set TESTRESOLVER_LOGS_FOLDER=%VSCODELOGSDIR%\server
if "%VSCODE_REMOTE_SERVER_PATH%"=="" (
echo "Using remote server out of sources for integration tests"

View file

@ -29,7 +29,7 @@ fi
export REMOTE_VSCODE=$AUTHORITY$EXT_PATH
VSCODECRASHDIR=$ROOT/.build/crashes
VSCODELOGSDIR=$ROOT/.build/logs/remote-integration-tests
VSCODELOGSDIR=$ROOT/.build/logs/integration-tests-remote
# Figure out which Electron to use for running tests
if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ]
@ -74,6 +74,7 @@ else
fi
export TESTRESOLVER_DATA_FOLDER=$TESTRESOLVER_DATA_FOLDER
export TESTRESOLVER_LOGS_FOLDER=$VSCODELOGSDIR/server
# Figure out which remote server to use for running tests
if [ -z "$VSCODE_REMOTE_SERVER_PATH" ]

View file

@ -26,6 +26,7 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
compile-extension:markdown-language-features^
compile-extension:typescript-language-features^
compile-extension:vscode-custom-editor-tests^
compile-extension:vscode-notebook-tests^
compile-extension:emmet^
compile-extension:css-language-features-server^
compile-extension:html-language-features-server^

View file

@ -32,6 +32,7 @@ else
yarn gulp compile-extension:vscode-api-tests \
compile-extension:vscode-colorize-tests \
compile-extension:vscode-custom-editor-tests \
compile-extension:vscode-notebook-tests \
compile-extension:markdown-language-features \
compile-extension:typescript-language-features \
compile-extension:emmet \

View file

@ -45,11 +45,13 @@
async function load(modulePaths, resultCallback, options) {
const isDev = !!safeProcess.env['VSCODE_DEV'];
// Error handler (TODO@sandbox non-sandboxed only)
// Error handler (node.js enabled renderers only)
let showDevtoolsOnError = isDev;
safeProcess.on('uncaughtException', function (/** @type {string | Error} */ error) {
onUnexpectedError(error, showDevtoolsOnError);
});
if (!safeProcess.sandboxed) {
safeProcess.on('uncaughtException', function (/** @type {string | Error} */ error) {
onUnexpectedError(error, showDevtoolsOnError);
});
}
// Await window configuration from preload
const timeout = setTimeout(() => { console.error(`[resolve window config] Could not resolve window configuration within 10 seconds, but will continue to wait...`); }, 10000);
@ -83,7 +85,7 @@
developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding);
}
// Enable ASAR support (TODO@sandbox non-sandboxed only)
// Enable ASAR support (node.js enabled renderers only)
if (!safeProcess.sandboxed) {
globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot);
}
@ -100,9 +102,12 @@
window.document.documentElement.setAttribute('lang', locale);
// Replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only)
// Define `fs` as `original-fs` to disable ASAR support
// in fs-operations (node.js enabled renderers only)
if (!safeProcess.sandboxed) {
require.define('fs', [], function () { return require.__$__nodeRequire('original-fs'); });
require.define('fs', [], function () {
return require.__$__nodeRequire('original-fs');
});
}
window['MonacoEnvironment'] = {};
@ -140,8 +145,9 @@
'tas-client-umd': `${baseNodeModulesPath}/tas-client-umd/lib/tas-client-umd.js`
};
// For priviledged renderers, allow to load built-in and other node.js
// modules via AMD which has a fallback to using node.js `require`
// Allow to load built-in and other node.js modules via AMD
// which has a fallback to using node.js `require`
// (node.js enabled renderers only)
if (!safeProcess.sandboxed) {
loaderConfig.amdModulesPattern = /(^vs\/)|(^vscode-textmate$)|(^vscode-oniguruma$)|(^xterm$)|(^xterm-addon-search$)|(^xterm-addon-unicode11$)|(^xterm-addon-webgl$)|(^iconv-lite-umd$)|(^jschardet$)|(^@vscode\/vscode-languagedetection$)|(^tas-client-umd$)/;
}

5
src/bootstrap.js vendored
View file

@ -42,9 +42,6 @@
//#region Add support for using node_modules.asar
/**
* TODO@sandbox remove the support for passing in `appRoot` once
* sandbox is fully enabled
*
* @param {string=} appRoot
*/
function enableASARSupport(appRoot) {
@ -100,7 +97,7 @@
}
if (!asarPathAdded && appRoot) {
// Assuming that adding just `NODE_MODULES_ASAR_PATH` is sufficient
// because nodejs should find it even if it has a different driver letter case
// because nodejs should find it even if it has a different drive letter case
paths.push(NODE_MODULES_ASAR_PATH);
}
}

View file

@ -1382,5 +1382,3 @@ define(function () { return purify; });
// export const removeHooks = purify.removeHooks;
// export const removeAllHooks = purify.removeAllHooks;
// ESM-uncomment-end
//# sourceMappingURL=purify.es.js.map

View file

@ -9,6 +9,9 @@ import { Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import 'vs/css!./gridview';
import { Box, GridView, IBoundarySashes, IGridViewOptions, IGridViewStyles, IView as IGridViewView, IViewSize, orthogonal, Sizing as GridViewSizing } from './gridview';
import type { GridLocation } from 'vs/base/browser/ui/grid/gridview';
///@ts-ignore
import type { SplitView } from 'vs/base/browser/ui/splitview/splitview';
export { IViewSize, LayoutPriority, Orientation, orthogonal } from './gridview';
@ -28,9 +31,22 @@ function oppositeDirection(direction: Direction): Direction {
}
}
/**
* The interface to implement for views within a {@link Grid}.
*/
export interface IView extends IGridViewView {
readonly preferredHeight?: number;
/**
* The preferred width for when the user double clicks a sash
* adjacent to this view.
*/
readonly preferredWidth?: number;
/**
* The preferred height for when the user double clicks a sash
* adjacent to this view.
*/
readonly preferredHeight?: number;
}
export interface GridLeafNode<T extends IView> {
@ -50,7 +66,7 @@ export function isGridBranchNode<T extends IView>(node: GridNode<T>): node is Gr
return !!(node as any).children;
}
function getGridNode<T extends IView>(node: GridNode<T>, location: number[]): GridNode<T> {
function getGridNode<T extends IView>(node: GridNode<T>, location: GridLocation): GridNode<T> {
if (location.length === 0) {
return node;
}
@ -113,7 +129,7 @@ function findAdjacentBoxLeafNodes<T extends IView>(boxNode: GridNode<T>, directi
return result;
}
function getLocationOrientation(rootOrientation: Orientation, location: number[]): Orientation {
function getLocationOrientation(rootOrientation: Orientation, location: GridLocation): Orientation {
return location.length % 2 === 0 ? orthogonal(rootOrientation) : rootOrientation;
}
@ -121,7 +137,7 @@ function getDirectionOrientation(direction: Direction): Orientation {
return direction === Direction.Up || direction === Direction.Down ? Orientation.VERTICAL : Orientation.HORIZONTAL;
}
export function getRelativeLocation(rootOrientation: Orientation, location: number[], direction: Direction): number[] {
export function getRelativeLocation(rootOrientation: Orientation, location: GridLocation, direction: Direction): GridLocation {
const orientation = getLocationOrientation(rootOrientation, location);
const directionOrientation = getDirectionOrientation(direction);
@ -163,7 +179,7 @@ function indexInParent(element: HTMLElement): number {
*
* This will break as soon as DOM structures of the Splitview or Gridview change.
*/
function getGridLocation(element: HTMLElement): number[] {
function getGridLocation(element: HTMLElement): GridLocation {
const parentElement = element.parentElement;
if (!parentElement) {
@ -191,40 +207,93 @@ export namespace Sizing {
}
export interface IGridStyles extends IGridViewStyles { }
export interface IGridOptions extends IGridViewOptions { }
export interface IGridOptions extends IGridViewOptions {
readonly firstViewVisibleCachedSize?: number;
}
/**
* The {@link Grid} exposes a Grid widget in a friendlier API than the underlying
* {@link GridView} widget. Namely, all mutation operations are addressed by the
* model elements, rather than indexes.
*
* It support the same features as the {@link GridView}.
*/
export class Grid<T extends IView = IView> extends Disposable {
protected gridview: GridView;
private views = new Map<T, HTMLElement>();
/**
* The orientation of the grid. Matches the orientation of the root
* {@link SplitView} in the grid's {@link GridLocation} model.
*/
get orientation(): Orientation { return this.gridview.orientation; }
set orientation(orientation: Orientation) { this.gridview.orientation = orientation; }
/**
* The width of the grid.
*/
get width(): number { return this.gridview.width; }
/**
* The height of the grid.
*/
get height(): number { return this.gridview.height; }
/**
* The minimum width of the grid.
*/
get minimumWidth(): number { return this.gridview.minimumWidth; }
/**
* The minimum height of the grid.
*/
get minimumHeight(): number { return this.gridview.minimumHeight; }
/**
* The maximum width of the grid.
*/
get maximumWidth(): number { return this.gridview.maximumWidth; }
/**
* The maximum height of the grid.
*/
get maximumHeight(): number { return this.gridview.maximumHeight; }
/**
* Fires whenever a view within the grid changes its size constraints.
*/
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
/**
* Fires whenever the user scrolls a {@link SplitView} within
* the grid.
*/
readonly onDidScroll: Event<void>;
/**
* A collection of sashes perpendicular to each edge of the grid.
* Corner sashes will be created for each intersection.
*/
get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; }
set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; }
/**
* Enable/disable edge snapping across all grid views.
*/
set edgeSnapping(edgeSnapping: boolean) { this.gridview.edgeSnapping = edgeSnapping; }
/**
* The DOM element for this view.
*/
get element(): HTMLElement { return this.gridview.element; }
private didLayout = false;
constructor(gridview: GridView, options?: IGridOptions);
constructor(view: T, options?: IGridOptions);
/**
* Create a new {@link Grid}. A grid must *always* have a view
* inside.
*
* @param view An initial view for this Grid.
*/
constructor(view: T | GridView, options: IGridOptions = {}) {
super();
@ -238,12 +307,8 @@ export class Grid<T extends IView = IView> extends Disposable {
this._register(this.gridview);
this._register(this.gridview.onDidSashReset(this.onDidSashReset, this));
const size: number | GridViewSizing = typeof options.firstViewVisibleCachedSize === 'number'
? GridViewSizing.Invisible(options.firstViewVisibleCachedSize)
: 0;
if (!(view instanceof GridView)) {
this._addView(view, size, [0]);
this._addView(view, 0, [0]);
}
this.onDidChange = this.gridview.onDidChange;
@ -254,15 +319,67 @@ export class Grid<T extends IView = IView> extends Disposable {
this.gridview.style(styles);
}
/**
* Layout the {@link Grid}.
*
* Optionally provide a `top` and `left` positions, those will propagate
* as an origin for positions passed to {@link IView.layout}.
*
* @param width The width of the {@link Grid}.
* @param height The height of the {@link Grid}.
* @param top Optional, the top location of the {@link Grid}.
* @param left Optional, the left location of the {@link Grid}.
*/
layout(width: number, height: number, top: number = 0, left: number = 0): void {
this.gridview.layout(width, height, top, left);
this.didLayout = true;
}
hasView(view: T): boolean {
return this.views.has(view);
}
/**
* Add a {@link IView view} to this {@link Grid}, based on another reference view.
*
* Take this grid as an example:
*
* ```
* +-----+---------------+
* | A | B |
* +-----+---------+-----+
* | C | |
* +---------------+ D |
* | E | |
* +---------------+-----+
* ```
*
* Calling `addView(X, Sizing.Distribute, C, Direction.Right)` will make the following
* changes:
*
* ```
* +-----+---------------+
* | A | B |
* +-----+-+-------+-----+
* | C | X | |
* +-------+-------+ D |
* | E | |
* +---------------+-----+
* ```
*
* Or `addView(X, Sizing.Distribute, D, Direction.Down)`:
*
* ```
* +-----+---------------+
* | A | B |
* +-----+---------+-----+
* | C | D |
* +---------------+-----+
* | E | X |
* +---------------+-----+
* ```
*
* @param newView The view to add.
* @param size Either a fixed size, or a dynamic {@link Sizing} strategy.
* @param referenceView Another view to place this new view next to.
* @param direction The direction the new view should be placed next to the reference view.
*/
addView(newView: T, size: number | Sizing, referenceView: T, direction: Direction): void {
if (this.views.has(newView)) {
throw new Error('Can\'t add same view twice');
@ -293,7 +410,7 @@ export class Grid<T extends IView = IView> extends Disposable {
this._addView(newView, viewSize, location);
}
addViewAt(newView: T, size: number | DistributeSizing | InvisibleSizing, location: number[]): void {
private addViewAt(newView: T, size: number | DistributeSizing | InvisibleSizing, location: GridLocation): void {
if (this.views.has(newView)) {
throw new Error('Can\'t add same view twice');
}
@ -311,11 +428,17 @@ export class Grid<T extends IView = IView> extends Disposable {
this._addView(newView, viewSize, location);
}
protected _addView(newView: T, size: number | GridViewSizing, location: number[]): void {
protected _addView(newView: T, size: number | GridViewSizing, location: GridLocation): void {
this.views.set(newView, newView.element);
this.gridview.addView(newView, size, location);
}
/**
* Remove a {@link IView view} from this {@link Grid}.
*
* @param view The {@link IView view} to remove.
* @param sizing Whether to distribute other {@link IView view}'s sizes.
*/
removeView(view: T, sizing?: Sizing): void {
if (this.views.size === 1) {
throw new Error('Can\'t remove last view');
@ -326,6 +449,16 @@ export class Grid<T extends IView = IView> extends Disposable {
this.views.delete(view);
}
/**
* Move a {@link IView view} to another location in the grid.
*
* @remarks See {@link Grid.addView}.
*
* @param view The {@link IView view} to move.
* @param sizing Either a fixed size, or a dynamic {@link Sizing} strategy.
* @param referenceView Another view to place the view next to.
* @param direction The direction the view should be placed next to the reference view.
*/
moveView(view: T, sizing: number | Sizing, referenceView: T, direction: Direction): void {
const sourceLocation = this.getViewLocation(view);
const [sourceParentLocation, from] = tail(sourceLocation);
@ -342,7 +475,16 @@ export class Grid<T extends IView = IView> extends Disposable {
}
}
moveViewTo(view: T, location: number[]): void {
/**
* Move a {@link IView view} to another location in the grid.
*
* @remarks Internal method, do not use without knowing what you're doing.
* @remarks See {@link GridView.moveView}.
*
* @param view The {@link IView view} to move.
* @param location The {@link GridLocation location} to insert the view on.
*/
moveViewTo(view: T, location: GridLocation): void {
const sourceLocation = this.getViewLocation(view);
const [sourceParentLocation, from] = tail(sourceLocation);
const [targetParentLocation, to] = tail(location);
@ -362,17 +504,35 @@ export class Grid<T extends IView = IView> extends Disposable {
}
}
/**
* Swap two {@link IView views} within the {@link Grid}.
*
* @param from One {@link IView view}.
* @param to Another {@link IView view}.
*/
swapViews(from: T, to: T): void {
const fromLocation = this.getViewLocation(from);
const toLocation = this.getViewLocation(to);
return this.gridview.swapViews(fromLocation, toLocation);
}
/**
* Resize a {@link IView view}.
*
* @param view The {@link IView view} to resize.
* @param size The size the view should be.
*/
resizeView(view: T, size: IViewSize): void {
const location = this.getViewLocation(view);
return this.gridview.resizeView(location, size);
}
/**
* Get the size of a {@link IView view}.
*
* @param view The {@link IView view}. Provide `undefined` to get the size
* of the grid itself.
*/
getViewSize(view?: T): IViewSize {
if (!view) {
return this.gridview.getViewSize();
@ -382,34 +542,71 @@ export class Grid<T extends IView = IView> extends Disposable {
return this.gridview.getViewSize(location);
}
/**
* Get the cached visible size of a {@link IView view}. This was the size
* of the view at the moment it last became hidden.
*
* @param view The {@link IView view}.
*/
getViewCachedVisibleSize(view: T): number | undefined {
const location = this.getViewLocation(view);
return this.gridview.getViewCachedVisibleSize(location);
}
/**
* Maximize the size of a {@link IView view} by collapsing all other views
* to their minimum sizes.
*
* @param view The {@link IView view}.
*/
maximizeViewSize(view: T): void {
const location = this.getViewLocation(view);
this.gridview.maximizeViewSize(location);
}
/**
* Distribute the size among all {@link IView views} within the entire
* grid or within a single {@link SplitView}.
*/
distributeViewSizes(): void {
this.gridview.distributeViewSizes();
}
/**
* Returns whether a {@link IView view} is visible.
*
* @param view The {@link IView view}.
*/
isViewVisible(view: T): boolean {
const location = this.getViewLocation(view);
return this.gridview.isViewVisible(location);
}
/**
* Set the visibility state of a {@link IView view}.
*
* @param view The {@link IView view}.
*/
setViewVisible(view: T, visible: boolean): void {
const location = this.getViewLocation(view);
this.gridview.setViewVisible(location, visible);
}
/**
* Returns a descriptor for the entire grid.
*/
getViews(): GridBranchNode<T> {
return this.gridview.getView() as GridBranchNode<T>;
}
/**
* Utility method to return the collection all views which intersect
* a view's edge.
*
* @param view The {@link IView view}.
* @param direction Which direction edge to be considered.
* @param wrap Whether the grid wraps around (from right to left, from bottom to top).
*/
getNeighborViews(view: T, direction: Direction, wrap: boolean = false): T[] {
if (!this.didLayout) {
throw new Error('Can\'t call getNeighborViews before first layout');
@ -436,7 +633,7 @@ export class Grid<T extends IView = IView> extends Disposable {
.map(node => node.view);
}
getViewLocation(view: T): number[] {
private getViewLocation(view: T): GridLocation {
const element = this.views.get(view);
if (!element) {
@ -446,8 +643,8 @@ export class Grid<T extends IView = IView> extends Disposable {
return getGridLocation(element);
}
private onDidSashReset(location: number[]): void {
const resizeToPreferredSize = (location: number[]): boolean => {
private onDidSashReset(location: GridLocation): void {
const resizeToPreferredSize = (location: GridLocation): boolean => {
const node = this.gridview.getView(location) as GridNode<T>;
if (isGridBranchNode(node)) {
@ -510,6 +707,9 @@ export interface ISerializedGrid {
height: number;
}
/**
* A {@link Grid} which can serialize itself.
*/
export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
private static serializeNode<T extends ISerializableView>(node: GridNode<T>, orientation: Orientation): ISerializedNode {
@ -526,6 +726,13 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
return { type: 'branch', data: node.children.map(c => SerializableGrid.serializeNode(c, orthogonal(orientation))), size };
}
/**
* Construct a new {@link SerializableGrid} from a JSON object.
*
* @param json The JSON object.
* @param deserializer A deserializer which can revive each view.
* @returns A new {@link SerializableGrid} instance.
*/
static deserialize<T extends ISerializableView>(json: ISerializedGrid, deserializer: IViewDeserializer<T>, options: IGridOptions = {}): SerializableGrid<T> {
if (typeof json.orientation !== 'number') {
throw new Error('Invalid JSON: \'orientation\' property must be a number.');
@ -547,6 +754,9 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
*/
private initialLayoutContext: boolean = true;
/**
* Serialize this grid into a JSON object.
*/
serialize(): ISerializedGrid {
return {
root: SerializableGrid.serializeNode(this.getViews(), this.orientation),
@ -629,6 +839,10 @@ function getDimensions(node: ISerializedNode, orientation: Orientation): { width
}
}
/**
* Creates a new JSON object from a {@link GridDescriptor}, which can
* be deserialized by {@link SerializableGrid.deserialize}.
*/
export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid {
sanitizeGridNodeDescriptor(gridDescriptor, true);

View file

@ -5,7 +5,7 @@
import { $ } from 'vs/base/browser/dom';
import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { ISplitViewStyles, IView as ISplitView, LayoutPriority, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { DistributeSizing, ISplitViewStyles, IView as ISplitView, LayoutPriority, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { equals as arrayEquals, tail2 as tail } from 'vs/base/common/arrays';
import { Color } from 'vs/base/common/color';
import { Emitter, Event, Relay } from 'vs/base/common/event';
@ -17,6 +17,12 @@ import 'vs/css!./gridview';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export { LayoutPriority, Sizing } from 'vs/base/browser/ui/splitview/splitview';
export interface IGridViewStyles extends ISplitViewStyles { }
const defaultStyles: IGridViewStyles = {
separatorBorder: Color.transparent
};
export interface IViewSize {
readonly width: number;
readonly height: number;
@ -36,17 +42,95 @@ export interface IBoundarySashes {
readonly left?: Sash;
}
/**
* The interface to implement for views within a {@link GridView}.
*/
export interface IView {
/**
* The DOM element for this view.
*/
readonly element: HTMLElement;
/**
* A minimum width for this view.
*
* @remarks If none, set it to `0`.
*/
readonly minimumWidth: number;
/**
* A minimum width for this view.
*
* @remarks If none, set it to `Number.POSITIVE_INFINITY`.
*/
readonly maximumWidth: number;
/**
* A minimum height for this view.
*
* @remarks If none, set it to `0`.
*/
readonly minimumHeight: number;
/**
* A minimum height for this view.
*
* @remarks If none, set it to `Number.POSITIVE_INFINITY`.
*/
readonly maximumHeight: number;
readonly onDidChange: Event<IViewSize | undefined>;
/**
* The priority of the view when the {@link GridView} layout algorithm
* runs. Views with higher priority will be resized first.
*
* @remarks Only used when `proportionalLayout` is false.
*/
readonly priority?: LayoutPriority;
/**
* Whether the view will snap whenever the user reaches its minimum size or
* attempts to grow it beyond the minimum size.
*
* @defaultValue `false`
*/
readonly snap?: boolean;
/**
* View instances are supposed to fire this event whenever any of the constraint
* properties have changed:
*
* - {@link IView.minimumWidth}
* - {@link IView.maximumWidth}
* - {@link IView.minimumHeight}
* - {@link IView.maximumHeight}
* - {@link IView.priority}
* - {@link IView.snap}
*
* The {@link GridView} will relayout whenever that happens. The event can
* optionally emit the view's preferred size for that relayout.
*/
readonly onDidChange: Event<IViewSize | undefined>;
/**
* This will be called by the {@link GridView} during layout. A view meant to
* pass along the layout information down to its descendants.
*/
layout(width: number, height: number, top: number, left: number): void;
/**
* This will be called by the {@link GridView} whenever this view is made
* visible or hidden.
*
* @param visible Whether the view becomes visible.
*/
setVisible?(visible: boolean): void;
/**
* This will be called by the {@link GridView} whenever this view is on
* an edge of the grid and the grid's
* {@link GridView.boundarySashes boundary sashes} change.
*/
setBoundarySashes?(sashes: IBoundarySashes): void;
}
@ -108,29 +192,23 @@ export function isGridBranchNode(node: GridNode): node is GridBranchNode {
return !!(node as any).children;
}
export interface IGridViewStyles extends ISplitViewStyles { }
const defaultStyles: IGridViewStyles = {
separatorBorder: Color.transparent
};
export interface ILayoutController {
readonly isLayoutEnabled: boolean;
}
export class LayoutController implements ILayoutController {
class LayoutController {
constructor(public isLayoutEnabled: boolean) { }
}
export class MultiplexLayoutController implements ILayoutController {
get isLayoutEnabled(): boolean { return this.layoutControllers.every(l => l.isLayoutEnabled); }
constructor(private layoutControllers: ILayoutController[]) { }
}
export interface IGridViewOptions {
/**
* Styles overriding the {@link defaultStyles default ones}.
*/
readonly styles?: IGridViewStyles;
/**
* Resize each view proportionally when resizing the {@link GridView}.
*
* @defaultValue `true`
*/
readonly proportionalLayout?: boolean; // default true
readonly layoutController?: ILayoutController;
}
interface ILayoutContext {
@ -249,8 +327,8 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
private childrenChangeDisposable: IDisposable = Disposable.None;
private readonly _onDidSashReset = new Emitter<number[]>();
readonly onDidSashReset: Event<number[]> = this._onDidSashReset.event;
private readonly _onDidSashReset = new Emitter<GridLocation>();
readonly onDidSashReset: Event<GridLocation> = this._onDidSashReset.event;
private splitviewSashResetDisposable: IDisposable = Disposable.None;
private childrenSashResetDisposable: IDisposable = Disposable.None;
@ -296,7 +374,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
constructor(
readonly orientation: Orientation,
readonly layoutController: ILayoutController,
readonly layoutController: LayoutController,
styles: IGridViewStyles,
readonly proportionalLayout: boolean,
size: number = 0,
@ -685,7 +763,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
private absoluteOrthogonalOffset: number = 0;
readonly onDidScroll: Event<void> = Event.None;
readonly onDidSashReset: Event<number[]> = Event.None;
readonly onDidSashReset: Event<GridLocation> = Event.None;
private _onDidLinkedWidthNodeChange = new Relay<number | undefined>();
private _linkedWidthNode: LeafNode | undefined = undefined;
@ -712,7 +790,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
constructor(
readonly view: IView,
readonly orientation: Orientation,
readonly layoutController: ILayoutController,
readonly layoutController: LayoutController,
orthogonalSize: number,
size: number = 0
) {
@ -871,21 +949,88 @@ function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number)
}
}
/**
* The location of a {@link IView view} within a {@link GridView}.
*
* A GridView is a tree composition of multiple {@link SplitView} instances, orthogonal
* between one another. Here's an example:
*
* ```
* +-----+---------------+
* | A | B |
* +-----+---------+-----+
* | C | |
* +---------------+ D |
* | E | |
* +---------------+-----+
* ```
*
* The above grid's tree structure is:
*
* ```
* Vertical SplitView
* +-Horizontal SplitView
* | +-A
* | +-B
* +- Horizontal SplitView
* +-Vertical SplitView
* | +-C
* | +-E
* +-D
* ```
*
* So, {@link IView views} within a {@link GridView} can be referenced by
* a sequence of indexes, each index referencing each SplitView. Here are
* each view's locations, from the example above:
*
* - `A`: `[0,0]`
* - `B`: `[0,1]`
* - `C`: `[1,0,0]`
* - `D`: `[1,1]`
* - `E`: `[1,0,1]`
*/
export type GridLocation = number[];
/**
* The {@link GridView} is the UI component which implements a two dimensional
* flex-like layout algorithm for a collection of {@link IView} instances, which
* are mostly HTMLElement instances with size constraints. A {@link GridView} is a
* tree composition of multiple {@link SplitView} instances, orthogonal between
* one another. It will respect view's size contraints, just like the SplitView.
*
* It has a low-level index based API, allowing for fine grain performant operations.
* Look into the {@link Grid} widget for a higher-level API.
*
* Features:
* - flex-like layout algorithm
* - snap support
* - corner sash support
* - Alt key modifier behavior, macOS style
* - layout (de)serialization
*/
export class GridView implements IDisposable {
/**
* The DOM element for this view.
*/
readonly element: HTMLElement;
private styles: IGridViewStyles;
private proportionalLayout: boolean;
private _root!: BranchNode;
private onDidSashResetRelay = new Relay<number[]>();
readonly onDidSashReset: Event<number[]> = this.onDidSashResetRelay.event;
private onDidSashResetRelay = new Relay<GridLocation>();
private _onDidScroll = new Relay<void>();
private _onDidChange = new Relay<IViewSize | undefined>();
private _boundarySashes: IBoundarySashes = {};
/**
* The layout controller makes sure layout only propagates
* to the views after the very first call to {@link GridView.layout}.
*/
private layoutController: LayoutController;
private disposable2x2: IDisposable = Disposable.None;
private get root(): BranchNode {
return this._root;
}
private get root(): BranchNode { return this._root; }
private set root(root: BranchNode) {
const oldRoot = this._root;
@ -902,10 +1047,59 @@ export class GridView implements IDisposable {
this._onDidScroll.input = root.onDidScroll;
}
get orientation(): Orientation {
return this._root.orientation;
}
/**
* Fires whenever the user double clicks a {@link Sash sash}.
*/
readonly onDidSashReset = this.onDidSashResetRelay.event;
/**
* Fires whenever the user scrolls a {@link SplitView} within
* the grid.
*/
readonly onDidScroll = this._onDidScroll.event;
/**
* Fires whenever a view within the grid changes its size constraints.
*/
readonly onDidChange = this._onDidChange.event;
/**
* The width of the grid.
*/
get width(): number { return this.root.width; }
/**
* The height of the grid.
*/
get height(): number { return this.root.height; }
/**
* The minimum width of the grid.
*/
get minimumWidth(): number { return this.root.minimumWidth; }
/**
* The minimum height of the grid.
*/
get minimumHeight(): number { return this.root.minimumHeight; }
/**
* The maximum width of the grid.
*/
get maximumWidth(): number { return this.root.maximumHeight; }
/**
* The maximum height of the grid.
*/
get maximumHeight(): number { return this.root.maximumHeight; }
get orientation(): Orientation { return this._root.orientation; }
get boundarySashes(): IBoundarySashes { return this._boundarySashes; }
/**
* The orientation of the grid. Matches the orientation of the root
* {@link SplitView} in the grid's tree model.
*/
set orientation(orientation: Orientation) {
if (this._root.orientation === orientation) {
return;
@ -917,77 +1111,67 @@ export class GridView implements IDisposable {
this.boundarySashes = this.boundarySashes;
}
get width(): number { return this.root.width; }
get height(): number { return this.root.height; }
get minimumWidth(): number { return this.root.minimumWidth; }
get minimumHeight(): number { return this.root.minimumHeight; }
get maximumWidth(): number { return this.root.maximumHeight; }
get maximumHeight(): number { return this.root.maximumHeight; }
private _onDidScroll = new Relay<void>();
readonly onDidScroll = this._onDidScroll.event;
private _onDidChange = new Relay<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event;
private _boundarySashes: IBoundarySashes = {};
get boundarySashes(): IBoundarySashes { return this._boundarySashes; }
/**
* A collection of sashes perpendicular to each edge of the grid.
* Corner sashes will be created for each intersection.
*/
set boundarySashes(boundarySashes: IBoundarySashes) {
this._boundarySashes = boundarySashes;
this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation);
}
/**
* Enable/disable edge snapping across all grid views.
*/
set edgeSnapping(edgeSnapping: boolean) {
this.root.edgeSnapping = edgeSnapping;
}
/**
* The first layout controller makes sure layout only propagates
* to the views after the very first call to gridview.layout()
* Create a new {@link GridView} instance.
*
* @remarks It's the caller's responsibility to append the
* {@link GridView.element} to the page's DOM.
*/
private firstLayoutController: LayoutController;
private layoutController: LayoutController;
constructor(options: IGridViewOptions = {}) {
this.element = $('.monaco-grid-view');
this.styles = options.styles || defaultStyles;
this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true;
this.firstLayoutController = new LayoutController(false);
this.layoutController = new MultiplexLayoutController([
this.firstLayoutController,
...(options.layoutController ? [options.layoutController] : [])
]);
this.layoutController = new LayoutController(false);
this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout);
}
getViewMap(map: Map<IView, HTMLElement>, node?: Node): void {
if (!node) {
node = this.root;
}
if (node instanceof BranchNode) {
node.children.forEach(child => this.getViewMap(map, child));
} else {
map.set(node.view, node.element);
}
}
style(styles: IGridViewStyles): void {
this.styles = styles;
this.root.style(styles);
}
/**
* Layout the {@link GridView}.
*
* Optionally provide a `top` and `left` positions, those will propagate
* as an origin for positions passed to {@link IView.layout}.
*
* @param width The width of the {@link GridView}.
* @param height The height of the {@link GridView}.
* @param top Optional, the top location of the {@link GridView}.
* @param left Optional, the left location of the {@link GridView}.
*/
layout(width: number, height: number, top: number = 0, left: number = 0): void {
this.firstLayoutController.isLayoutEnabled = true;
this.layoutController.isLayoutEnabled = true;
const [size, orthogonalSize, offset, orthogonalOffset] = this.root.orientation === Orientation.HORIZONTAL ? [height, width, top, left] : [width, height, left, top];
this.root.layout(size, offset, { orthogonalSize, absoluteOffset: offset, absoluteOrthogonalOffset: orthogonalOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
}
addView(view: IView, size: number | Sizing, location: number[]): void {
/**
* Add a {@link IView view} to this {@link GridView}.
*
* @param view The view to add.
* @param size Either a fixed size, or a dynamic {@link Sizing} strategy.
* @param location The {@link GridLocation location} to insert the view on.
*/
addView(view: IView, size: number | Sizing, location: GridLocation): void {
this.disposable2x2.dispose();
this.disposable2x2 = Disposable.None;
@ -1028,7 +1212,13 @@ export class GridView implements IDisposable {
this.trySet2x2();
}
removeView(location: number[], sizing?: Sizing): IView {
/**
* Remove a {@link IView view} from this {@link GridView}.
*
* @param location The {@link GridLocation location} of the {@link IView view}.
* @param sizing Whether to distribute other {@link IView view}'s sizes.
*/
removeView(location: GridLocation, sizing?: DistributeSizing): IView {
this.disposable2x2.dispose();
this.disposable2x2 = Disposable.None;
@ -1102,7 +1292,14 @@ export class GridView implements IDisposable {
return node.view;
}
moveView(parentLocation: number[], from: number, to: number): void {
/**
* Move a {@link IView view} within its parent.
*
* @param parentLocation The {@link GridLocation location} of the {@link IView view}'s parent.
* @param from The index of the {@link IView view} to move.
* @param to The index where the {@link IView view} should move to.
*/
moveView(parentLocation: GridLocation, from: number, to: number): void {
const [, parent] = this.getNode(parentLocation);
if (!(parent instanceof BranchNode)) {
@ -1114,7 +1311,13 @@ export class GridView implements IDisposable {
this.trySet2x2();
}
swapViews(from: number[], to: number[]): void {
/**
* Swap two {@link IView views} within the {@link GridView}.
*
* @param from The {@link GridLocation location} of one view.
* @param to The {@link GridLocation location} of another view.
*/
swapViews(from: GridLocation, to: GridLocation): void {
const [fromRest, fromIndex] = tail(from);
const [, fromParent] = this.getNode(fromRest);
@ -1156,7 +1359,13 @@ export class GridView implements IDisposable {
this.trySet2x2();
}
resizeView(location: number[], { width, height }: Partial<IViewSize>): void {
/**
* Resize a {@link IView view}.
*
* @param location The {@link GridLocation location} of the view.
* @param size The size the view should be. Optionally provide a single dimension.
*/
resizeView(location: GridLocation, size: Partial<IViewSize>): void {
const [rest, index] = tail(location);
const [pathToParent, parent] = this.getNode(rest);
@ -1164,11 +1373,11 @@ export class GridView implements IDisposable {
throw new Error('Invalid location');
}
if (!width && !height) {
if (!size.width && !size.height) {
return;
}
const [parentSize, grandParentSize] = parent.orientation === Orientation.HORIZONTAL ? [width, height] : [height, width];
const [parentSize, grandParentSize] = parent.orientation === Orientation.HORIZONTAL ? [size.width, size.height] : [size.height, size.width];
if (typeof grandParentSize === 'number' && pathToParent.length > 0) {
const [, grandParent] = tail(pathToParent);
@ -1184,7 +1393,13 @@ export class GridView implements IDisposable {
this.trySet2x2();
}
getViewSize(location?: number[]): IViewSize {
/**
* Get the size of a {@link IView view}.
*
* @param location The {@link GridLocation location} of the view. Provide `undefined` to get
* the size of the grid itself.
*/
getViewSize(location?: GridLocation): IViewSize {
if (!location) {
return { width: this.root.width, height: this.root.height };
}
@ -1193,7 +1408,13 @@ export class GridView implements IDisposable {
return { width: node.width, height: node.height };
}
getViewCachedVisibleSize(location: number[]): number | undefined {
/**
* Get the cached visible size of a {@link IView view}. This was the size
* of the view at the moment it last became hidden.
*
* @param location The {@link GridLocation location} of the view.
*/
getViewCachedVisibleSize(location: GridLocation): number | undefined {
const [rest, index] = tail(location);
const [, parent] = this.getNode(rest);
@ -1204,7 +1425,13 @@ export class GridView implements IDisposable {
return parent.getChildCachedVisibleSize(index);
}
maximizeViewSize(location: number[]): void {
/**
* Maximize the size of a {@link IView view} by collapsing all other views
* to their minimum sizes.
*
* @param location The {@link GridLocation location} of the view.
*/
maximizeViewSize(location: GridLocation): void {
const [ancestors, node] = this.getNode(location);
if (!(node instanceof LeafNode)) {
@ -1216,7 +1443,16 @@ export class GridView implements IDisposable {
}
}
distributeViewSizes(location?: number[]): void {
/**
* Distribute the size among all {@link IView views} within the entire
* grid or within a single {@link SplitView}.
*
* @param location The {@link GridLocation location} of a view containing
* children views, which will have their sizes distributed within the parent
* view's size. Provide `undefined` to recursively distribute all views' sizes
* in the entire grid.
*/
distributeViewSizes(location?: GridLocation): void {
if (!location) {
this.root.distributeViewSizes(true);
return;
@ -1232,7 +1468,12 @@ export class GridView implements IDisposable {
this.trySet2x2();
}
isViewVisible(location: number[]): boolean {
/**
* Returns whether a {@link IView view} is visible.
*
* @param location The {@link GridLocation location} of the view.
*/
isViewVisible(location: GridLocation): boolean {
const [rest, index] = tail(location);
const [, parent] = this.getNode(rest);
@ -1243,7 +1484,12 @@ export class GridView implements IDisposable {
return parent.isChildVisible(index);
}
setViewVisible(location: number[], visible: boolean): void {
/**
* Set the visibility state of a {@link IView view}.
*
* @param location The {@link GridLocation location} of the view.
*/
setViewVisible(location: GridLocation, visible: boolean): void {
const [rest, index] = tail(location);
const [, parent] = this.getNode(rest);
@ -1254,13 +1500,31 @@ export class GridView implements IDisposable {
parent.setChildVisible(index, visible);
}
/**
* Returns a descriptor for the entire grid.
*/
getView(): GridBranchNode;
getView(location?: number[]): GridNode;
getView(location?: number[]): GridNode {
/**
* Returns a descriptor for a {@link GridLocation subtree} within the
* {@link GridView}.
*
* @param location The {@link GridLocation location} of the root of
* the {@link GridLocation subtree}.
*/
getView(location: GridLocation): GridNode;
getView(location?: GridLocation): GridNode {
const node = location ? this.getNode(location)[1] : this._root;
return this._getViews(node, this.orientation);
}
/**
* Construct a new {@link GridView} from a JSON object.
*
* @param json The JSON object.
* @param deserializer A deserializer which can revive each view.
* @returns A new {@link GridView} instance.
*/
static deserialize<T extends ISerializableView>(json: ISerializedGridView, deserializer: IViewDeserializer<T>, options: IGridViewOptions = {}): GridView {
if (typeof json.orientation !== 'number') {
throw new Error('Invalid JSON: \'orientation\' property must be a number.');
@ -1323,7 +1587,7 @@ export class GridView implements IDisposable {
return { children, box };
}
private getNode(location: number[], node: Node = this.root, path: BranchNode[] = []): [BranchNode[], Node] {
private getNode(location: GridLocation, node: Node = this.root, path: BranchNode[] = []): [BranchNode[], Node] {
if (location.length === 0) {
return [path, node];
}
@ -1344,6 +1608,13 @@ export class GridView implements IDisposable {
return this.getNode(rest, child, path);
}
/**
* Attempt to lock the {@link Sash sashes} in this {@link GridView} so
* the grid behaves as a 2x2 matrix, with a corner sash in the middle.
*
* In case the grid isn't a 2x2 grid _and_ all sashes are not aligned,
* this method is a no-op.
*/
trySet2x2(): void {
this.disposable2x2.dispose();
this.disposable2x2 = Disposable.None;
@ -1361,6 +1632,22 @@ export class GridView implements IDisposable {
this.disposable2x2 = first.trySet2x2(second);
}
/**
* Populate a map with views to DOM nodes.
* @remarks To be used internally only.
*/
getViewMap(map: Map<IView, HTMLElement>, node?: Node): void {
if (!node) {
node = this.root;
}
if (node instanceof BranchNode) {
node.children.forEach(child => this.getViewMap(map, child));
} else {
map.set(node.view, node.element);
}
}
dispose(): void {
this.onDidSashResetRelay.dispose();
this.root.dispose();

View file

@ -34,6 +34,7 @@ export interface IMenuBarOptions {
getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined;
alwaysOnMnemonics?: boolean;
compactMode?: Direction;
actionRunner?: IActionRunner;
getCompactMenuActions?: () => IAction[]
}
@ -109,7 +110,7 @@ export class MenuBar extends Disposable {
this.menuUpdater = this._register(new RunOnceScheduler(() => this.update(), 200));
this.actionRunner = this._register(new ActionRunner());
this.actionRunner = this.options.actionRunner ?? this._register(new ActionRunner());
this._register(this.actionRunner.onBeforeRun(() => {
this.setUnfocusedState();
}));

View file

@ -13,29 +13,39 @@ import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecy
import { isMacintosh } from 'vs/base/common/platform';
import 'vs/css!./sash';
/**
* Allow the sashes to be visible at runtime.
* @remark Use for development purposes only.
*/
let DEBUG = false;
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
export interface ISashLayoutProvider { }
export interface IVerticalSashLayoutProvider extends ISashLayoutProvider {
/**
* A vertical sash layout provider provides position and height for a sash.
*/
export interface IVerticalSashLayoutProvider {
getVerticalSashLeft(sash: Sash): number;
getVerticalSashTop?(sash: Sash): number;
getVerticalSashHeight?(sash: Sash): number;
}
export interface IHorizontalSashLayoutProvider extends ISashLayoutProvider {
/**
* A vertical sash layout provider provides position and width for a sash.
*/
export interface IHorizontalSashLayoutProvider {
getHorizontalSashTop(sash: Sash): number;
getHorizontalSashLeft?(sash: Sash): number;
getHorizontalSashWidth?(sash: Sash): number;
}
type ISashLayoutProvider = IVerticalSashLayoutProvider | IHorizontalSashLayoutProvider;
export interface ISashEvent {
startX: number;
currentX: number;
startY: number;
currentY: number;
altKey: boolean;
readonly startX: number;
readonly currentX: number;
readonly startY: number;
readonly currentY: number;
readonly altKey: boolean;
}
export enum OrthogonalEdge {
@ -46,10 +56,41 @@ export enum OrthogonalEdge {
}
export interface ISashOptions {
/**
* Whether a sash is horizontal or vertical.
*/
readonly orientation: Orientation;
readonly orthogonalStartSash?: Sash;
readonly orthogonalEndSash?: Sash;
/**
* The width or height of a vertical or horizontal sash, respectively.
*/
readonly size?: number;
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the start of this one. A corner sash will be created
* automatically at that location.
*
* The start of a horizontal sash is its left-most position.
* The start of a vertical sash is its top-most position.
*/
readonly orthogonalStartSash?: Sash;
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the end of this one. A corner sash will be created
* automatically at that location.
*
* The end of a horizontal sash is its right-most position.
* The end of a vertical sash is its bottom-most position.
*/
readonly orthogonalEndSash?: Sash;
/**
* Provides a hint as to what mouse cursor to use whenever the user
* hovers over a corner sash provided by this and an orthogonal sash.
*/
readonly orthogonalEdge?: OrthogonalEdge;
}
@ -67,9 +108,31 @@ export const enum Orientation {
}
export const enum SashState {
/**
* Disable any UI interaction.
*/
Disabled,
Minimum,
Maximum,
/**
* Allow dragging down or to the right, depending on the sash orientation.
*
* Some OSs allow customizing the mouse cursor differently whenever
* some resizable component can't be any smaller, but can be larger.
*/
AtMinimum,
/**
* Allow dragging up or to the left, depending on the sash orientation.
*
* Some OSs allow customizing the mouse cursor differently whenever
* some resizable component can't be any larger, but can be smaller.
*/
AtMaximum,
/**
* Enable dragging.
*/
Enabled
}
@ -159,52 +222,101 @@ class OrthogonalPointerEventFactory implements IPointerEventFactory {
}
}
/**
* The {@link Sash} is the UI component which allows the user to resize other
* components. It's usually an invisible horizontal or vertical line which, when
* hovered, becomes highlighted and can be dragged along the perpendicular dimension
* to its direction.
*
* Features:
* - Touch event handling
* - Corner sash support
* - Hover with different mouse cursor support
* - Configurable hover size
* - Linked sash support, for 2x2 corner sashes
*/
export class Sash extends Disposable {
private el: HTMLElement;
private layoutProvider: ISashLayoutProvider;
private orientation!: Orientation;
private orientation: Orientation;
private size: number;
private hoverDelay = globalHoverDelay;
private hoverDelayer = this._register(new Delayer(this.hoverDelay));
private _state: SashState = SashState.Enabled;
private readonly onDidEnablementChange = this._register(new Emitter<SashState>());
private readonly _onDidStart = this._register(new Emitter<ISashEvent>());
private readonly _onDidChange = this._register(new Emitter<ISashEvent>());
private readonly _onDidReset = this._register(new Emitter<void>());
private readonly _onDidEnd = this._register(new Emitter<void>());
private readonly orthogonalStartSashDisposables = this._register(new DisposableStore());
private _orthogonalStartSash: Sash | undefined;
private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalStartDragHandle: HTMLElement | undefined;
private readonly orthogonalEndSashDisposables = this._register(new DisposableStore());
private _orthogonalEndSash: Sash | undefined;
private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalEndDragHandle: HTMLElement | undefined;
get state(): SashState { return this._state; }
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
/**
* The state of a sash defines whether it can be interacted with by the user
* as well as what mouse cursor to use, when hovered.
*/
set state(state: SashState) {
if (this._state === state) {
return;
}
this.el.classList.toggle('disabled', state === SashState.Disabled);
this.el.classList.toggle('minimum', state === SashState.Minimum);
this.el.classList.toggle('maximum', state === SashState.Maximum);
this.el.classList.toggle('minimum', state === SashState.AtMinimum);
this.el.classList.toggle('maximum', state === SashState.AtMaximum);
this._state = state;
this._onDidEnablementChange.fire(state);
this.onDidEnablementChange.fire(state);
}
private readonly _onDidEnablementChange = this._register(new Emitter<SashState>());
readonly onDidEnablementChange: Event<SashState> = this._onDidEnablementChange.event;
private readonly _onDidStart = this._register(new Emitter<ISashEvent>());
/**
* An event which fires whenever the user starts dragging this sash.
*/
readonly onDidStart: Event<ISashEvent> = this._onDidStart.event;
private readonly _onDidChange = this._register(new Emitter<ISashEvent>());
/**
* An event which fires whenever the user moves the mouse while
* dragging this sash.
*/
readonly onDidChange: Event<ISashEvent> = this._onDidChange.event;
private readonly _onDidReset = this._register(new Emitter<void>());
/**
* An event which fires whenever the user double clicks this sash.
*/
readonly onDidReset: Event<void> = this._onDidReset.event;
private readonly _onDidEnd = this._register(new Emitter<void>());
/**
* An event which fires whenever the user stops dragging this sash.
*/
readonly onDidEnd: Event<void> = this._onDidEnd.event;
/**
* A linked sash will be forwarded the same user interactions and events
* so it moves exactly the same way as this sash.
*
* Useful in 2x2 grids. Not meant for widespread usage.
*/
linkedSash: Sash | undefined = undefined;
private readonly orthogonalStartSashDisposables = this._register(new DisposableStore());
private _orthogonalStartSash: Sash | undefined;
private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalStartDragHandle: HTMLElement | undefined;
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the start of this one. A corner sash will be created
* automatically at that location.
*
* The start of a horizontal sash is its left-most position.
* The start of a vertical sash is its top-most position.
*/
set orthogonalStartSash(sash: Sash | undefined) {
this.orthogonalStartDragHandleDisposables.clear();
this.orthogonalStartSashDisposables.clear();
@ -223,18 +335,22 @@ export class Sash extends Disposable {
}
};
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(onChange, this));
this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}
this._orthogonalStartSash = sash;
}
private readonly orthogonalEndSashDisposables = this._register(new DisposableStore());
private _orthogonalEndSash: Sash | undefined;
private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
private _orthogonalEndDragHandle: HTMLElement | undefined;
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
/**
* A reference to another sash, perpendicular to this one, which
* aligns at the end of this one. A corner sash will be created
* automatically at that location.
*
* The end of a horizontal sash is its right-most position.
* The end of a vertical sash is its bottom-most position.
*/
set orthogonalEndSash(sash: Sash | undefined) {
this.orthogonalEndDragHandleDisposables.clear();
this.orthogonalEndSashDisposables.clear();
@ -253,15 +369,30 @@ export class Sash extends Disposable {
}
};
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(onChange, this));
this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
onChange(sash.state);
}
this._orthogonalEndSash = sash;
}
constructor(container: HTMLElement, layoutProvider: IVerticalSashLayoutProvider, options: ISashOptions);
constructor(container: HTMLElement, layoutProvider: IHorizontalSashLayoutProvider, options: ISashOptions);
/**
* Create a new vertical sash.
*
* @param container A DOM node to append the sash to.
* @param verticalLayoutProvider A vertical layout provider.
* @param options The options.
*/
constructor(container: HTMLElement, verticalLayoutProvider: IVerticalSashLayoutProvider, options: IVerticalSashOptions);
/**
* Create a new horizontal sash.
*
* @param container A DOM node to append the sash to.
* @param horizontalLayoutProvider A horizontal layout provider.
* @param options The options.
*/
constructor(container: HTMLElement, horizontalLayoutProvider: IHorizontalSashLayoutProvider, options: IHorizontalSashOptions);
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions) {
super();
@ -381,17 +512,17 @@ export class Sash extends Disposable {
if (isMultisashResize) {
cursor = 'all-scroll';
} else if (this.orientation === Orientation.HORIZONTAL) {
if (this.state === SashState.Minimum) {
if (this.state === SashState.AtMinimum) {
cursor = 's-resize';
} else if (this.state === SashState.Maximum) {
} else if (this.state === SashState.AtMaximum) {
cursor = 'n-resize';
} else {
cursor = isMacintosh ? 'row-resize' : 'ns-resize';
}
} else {
if (this.state === SashState.Minimum) {
if (this.state === SashState.AtMinimum) {
cursor = 'e-resize';
} else if (this.state === SashState.Maximum) {
} else if (this.state === SashState.AtMaximum) {
cursor = 'w-resize';
} else {
cursor = isMacintosh ? 'col-resize' : 'ew-resize';
@ -406,7 +537,7 @@ export class Sash extends Disposable {
updateStyle();
if (!isMultisashResize) {
this.onDidEnablementChange(updateStyle, null, disposables);
this.onDidEnablementChange.event(updateStyle, null, disposables);
}
const onPointerMove = (e: PointerEvent) => {
@ -472,10 +603,19 @@ export class Sash extends Disposable {
}
}
/**
* Forcefully stop any user interactions with this sash.
* Useful when hiding a parent component, while the user is still
* interacting with the sash.
*/
clearSashHoverState(): void {
Sash.onMouseLeave(this);
}
/**
* Layout the sash. The sash will size and position itself
* based on its provided {@link ISashLayoutProvider layout provider}.
*/
layout(): void {
if (this.orientation === Orientation.VERTICAL) {
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);

View file

@ -17,45 +17,178 @@ import 'vs/css!./splitview';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface ISplitViewStyles {
separatorBorder: Color;
readonly separatorBorder: Color;
}
const defaultStyles: ISplitViewStyles = {
separatorBorder: Color.transparent
};
export interface ISplitViewOptions<TLayoutContext = undefined> {
readonly orientation?: Orientation; // default Orientation.VERTICAL
readonly styles?: ISplitViewStyles;
readonly orthogonalStartSash?: Sash;
readonly orthogonalEndSash?: Sash;
readonly inverseAltBehavior?: boolean;
readonly proportionalLayout?: boolean; // default true,
readonly descriptor?: ISplitViewDescriptor<TLayoutContext>;
readonly scrollbarVisibility?: ScrollbarVisibility;
readonly getSashOrthogonalSize?: () => number;
}
/**
* Only used when `proportionalLayout` is false.
*/
export const enum LayoutPriority {
Normal,
Low,
High
}
/**
* The interface to implement for views within a {@link SplitView}.
*
* An optional {@link TLayoutContext layout context type} may be used in order to
* pass along layout contextual data from the {@link SplitView.layout} method down
* to each view's {@link IView.layout} calls.
*/
export interface IView<TLayoutContext = undefined> {
/**
* The DOM element for this view.
*/
readonly element: HTMLElement;
/**
* A minimum size for this view.
*
* @remarks If none, set it to `0`.
*/
readonly minimumSize: number;
/**
* A minimum size for this view.
*
* @remarks If none, set it to `Number.POSITIVE_INFINITY`.
*/
readonly maximumSize: number;
readonly onDidChange: Event<number | undefined>;
/**
* The priority of the view when the {@link SplitView.resize layout} algorithm
* runs. Views with higher priority will be resized first.
*
* @remarks Only used when `proportionalLayout` is false.
*/
readonly priority?: LayoutPriority;
/**
* Whether the view will snap whenever the user reaches its minimum size or
* attempts to grow it beyond the minimum size.
*
* @defaultValue `false`
*/
readonly snap?: boolean;
/**
* View instances are supposed to fire the {@link IView.onDidChange} event whenever
* any of the constraint properties have changed:
*
* - {@link IView.minimumSize}
* - {@link IView.maximumSize}
* - {@link IView.priority}
* - {@link IView.snap}
*
* The SplitView will relayout whenever that happens. The event can optionally emit
* the view's preferred size for that relayout.
*/
readonly onDidChange: Event<number | undefined>;
/**
* This will be called by the {@link SplitView} during layout. A view meant to
* pass along the layout information down to its descendants.
*
* @param size The size of this view, in pixels.
* @param offset The offset of this view, relative to the start of the {@link SplitView}.
* @param context The optional {@link IView layout context} passed to {@link SplitView.layout}.
*/
layout(size: number, offset: number, context: TLayoutContext | undefined): void;
/**
* This will be called by the {@link SplitView} whenever this view is made
* visible or hidden.
*
* @param visible Whether the view becomes visible.
*/
setVisible?(visible: boolean): void;
}
/**
* A descriptor for a {@link SplitView} instance.
*/
export interface ISplitViewDescriptor<TLayoutContext = undefined> {
/**
* The layout size of the {@link SplitView}.
*/
readonly size: number;
/**
* Descriptors for each {@link IView view}.
*/
readonly views: {
/**
* Whether the {@link IView view} is visible.
*
* @defaultValue `true`
*/
readonly visible?: boolean;
/**
* The size of the {@link IView view}.
*
* @defaultValue `true`
*/
readonly size: number;
/**
* The size of the {@link IView view}.
*
* @defaultValue `true`
*/
readonly view: IView<TLayoutContext>;
}[];
}
export interface ISplitViewOptions<TLayoutContext = undefined> {
/**
* Which axis the views align on.
*
* @defaultValue `Orientation.VERTICAL`
*/
readonly orientation?: Orientation;
/**
* Styles overriding the {@link defaultStyles default ones}.
*/
readonly styles?: ISplitViewStyles;
/**
* Make Alt-drag the default drag operation.
*/
readonly inverseAltBehavior?: boolean;
/**
* Resize each view proportionally when resizing the SplitView.
*
* @defaultValue `true`
*/
readonly proportionalLayout?: boolean;
/**
* An initial description of this {@link SplitView} instance, allowing
* to initialze all views within the ctor.
*/
readonly descriptor?: ISplitViewDescriptor<TLayoutContext>;
/**
* The scrollbar visibility setting for whenever the views within
* the {@link SplitView} overflow.
*/
readonly scrollbarVisibility?: ScrollbarVisibility;
/**
* Override the orthogonal size of sashes.
*/
readonly getSashOrthogonalSize?: () => number;
}
interface ISashEvent {
readonly sash: Sash;
readonly start: number;
@ -190,30 +323,89 @@ enum State {
Busy
}
/**
* When adding or removing views, distribute the delta space among
* all other views.
*/
export type DistributeSizing = { type: 'distribute' };
/**
* When adding or removing views, split the delta space with another
* specific view, indexed by the provided `index`.
*/
export type SplitSizing = { type: 'split', index: number };
/**
* When adding or removing views, assume the view is invisible.
*/
export type InvisibleSizing = { type: 'invisible', cachedVisibleSize: number };
/**
* When adding or removing views, the sizing provides fine grained
* control over how other views get resized.
*/
export type Sizing = DistributeSizing | SplitSizing | InvisibleSizing;
export namespace Sizing {
/**
* When adding or removing views, distribute the delta space among
* all other views.
*/
export const Distribute: DistributeSizing = { type: 'distribute' };
/**
* When adding or removing views, split the delta space with another
* specific view, indexed by the provided `index`.
*/
export function Split(index: number): SplitSizing { return { type: 'split', index }; }
/**
* When adding or removing views, assume the view is invisible.
*/
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
}
export interface ISplitViewDescriptor<TLayoutContext = undefined> {
size: number;
views: {
visible?: boolean;
size: number;
view: IView<TLayoutContext>;
}[];
}
/**
* The {@link SplitView} is the UI component which implements a one dimensional
* flex-like layout algorithm for a collection of {@link IView} instances, which
* are essentially HTMLElement instances with the following size constraints:
*
* - {@link IView.minimumSize}
* - {@link IView.maximumSize}
* - {@link IView.priority}
* - {@link IView.snap}
*
* In case the SplitView doesn't have enough size to fit all views, it will overflow
* its content with a scrollbar.
*
* In between each pair of views there will be a {@link Sash} allowing the user
* to resize the views, making sure the constraints are respected.
*
* An optional {@link TLayoutContext layout context type} may be used in order to
* pass along layout contextual data from the {@link SplitView.layout} method down
* to each view's {@link IView.layout} calls.
*
* Features:
* - Flex-like layout algorithm
* - Snap support
* - Orthogonal sash support, for corner sashes
* - View hide/show support
* - View swap/move support
* - Alt key modifier behavior, macOS style
*/
export class SplitView<TLayoutContext = undefined> extends Disposable {
/**
* This {@link SplitView}'s orientation.
*/
readonly orientation: Orientation;
/**
* The DOM element representing this {@link SplitView}.
*/
readonly el: HTMLElement;
private sashContainer: HTMLElement;
private viewContainer: HTMLElement;
private scrollable: Scrollable;
@ -231,27 +423,58 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
private readonly getSashOrthogonalSize: { (): number } | undefined;
private _onDidSashChange = this._register(new Emitter<number>());
private _onDidSashReset = this._register(new Emitter<number>());
private _orthogonalStartSash: Sash | undefined;
private _orthogonalEndSash: Sash | undefined;
private _startSnappingEnabled = true;
private _endSnappingEnabled = true;
/**
* Fires whenever the user resizes a {@link Sash sash}.
*/
readonly onDidSashChange = this._onDidSashChange.event;
private _onDidSashReset = this._register(new Emitter<number>());
/**
* Fires whenever the user double clicks a {@link Sash sash}.
*/
readonly onDidSashReset = this._onDidSashReset.event;
/**
* Fires whenever the split view is scrolled.
*/
readonly onDidScroll: Event<ScrollEvent>;
/**
* The amount of views in this {@link SplitView}.
*/
get length(): number {
return this.viewItems.length;
}
/**
* The minimum size of this {@link SplitView}.
*/
get minimumSize(): number {
return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
}
/**
* The maximum size of this {@link SplitView}.
*/
get maximumSize(): number {
return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
}
private _orthogonalStartSash: Sash | undefined;
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
get startSnappingEnabled(): boolean { return this._startSnappingEnabled; }
get endSnappingEnabled(): boolean { return this._endSnappingEnabled; }
/**
* A reference to a sash, perpendicular to all sashes in this {@link SplitView},
* located at the left- or top-most side of the SplitView.
* Corner sashes will be created automatically at the intersections.
*/
set orthogonalStartSash(sash: Sash | undefined) {
for (const sashItem of this.sashItems) {
sashItem.sash.orthogonalStartSash = sash;
@ -260,8 +483,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this._orthogonalStartSash = sash;
}
private _orthogonalEndSash: Sash | undefined;
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
/**
* A reference to a sash, perpendicular to all sashes in this {@link SplitView},
* located at the right- or bottom-most side of the SplitView.
* Corner sashes will be created automatically at the intersections.
*/
set orthogonalEndSash(sash: Sash | undefined) {
for (const sashItem of this.sashItems) {
sashItem.sash.orthogonalEndSash = sash;
@ -270,12 +496,16 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this._orthogonalEndSash = sash;
}
get sashes(): Sash[] {
/**
* The internal sashes within this {@link SplitView}.
*/
get sashes(): readonly Sash[] {
return this.sashItems.map(s => s.sash);
}
private _startSnappingEnabled = true;
get startSnappingEnabled(): boolean { return this._startSnappingEnabled; }
/**
* Enable/disable snapping at the beginning of this {@link SplitView}.
*/
set startSnappingEnabled(startSnappingEnabled: boolean) {
if (this._startSnappingEnabled === startSnappingEnabled) {
return;
@ -285,8 +515,9 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.updateSashEnablement();
}
private _endSnappingEnabled = true;
get endSnappingEnabled(): boolean { return this._endSnappingEnabled; }
/**
* Enable/disable snapping at the end of this {@link SplitView}.
*/
set endSnappingEnabled(endSnappingEnabled: boolean) {
if (this._endSnappingEnabled === endSnappingEnabled) {
return;
@ -296,12 +527,15 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.updateSashEnablement();
}
/**
* Create a new {@link SplitView} instance.
*/
constructor(container: HTMLElement, options: ISplitViewOptions<TLayoutContext> = {}) {
super();
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.inverseAltBehavior = !!options.inverseAltBehavior;
this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout;
this.orientation = options.orientation ?? Orientation.VERTICAL;
this.inverseAltBehavior = options.inverseAltBehavior ?? false;
this.proportionalLayout = options.proportionalLayout ?? true;
this.getSashOrthogonalSize = options.getSashOrthogonalSize;
this.el = document.createElement('div');
@ -354,10 +588,24 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
}
}
/**
* Add a {@link IView view} to this {@link SplitView}.
*
* @param view The view to add.
* @param size Either a fixed size, or a dynamic {@link Sizing} strategy.
* @param index The index to insert the view on.
* @param skipLayout Whether layout should be skipped.
*/
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
this.doAddView(view, size, index, skipLayout);
}
/**
* Remove a {@link IView view} from this {@link SplitView}.
*
* @param index The index where the {@link IView view} is located.
* @param sizing Whether to distribute other {@link IView view}'s sizes.
*/
removeView(index: number, sizing?: Sizing): IView<TLayoutContext> {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
@ -383,13 +631,19 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.relayout();
this.state = State.Idle;
if (sizing && sizing.type === 'distribute') {
if (sizing?.type === 'distribute') {
this.distributeViewSizes();
}
return view;
}
/**
* Move a {@link IView view} to a different index.
*
* @param from The source index.
* @param to The target index.
*/
moveView(from: number, to: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
@ -401,6 +655,13 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.addView(view, sizing, to);
}
/**
* Swap two {@link IView views}.
*
* @param from The source index.
* @param to The target index.
*/
swapViews(from: number, to: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
@ -419,6 +680,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.addView(fromView, toSize, to);
}
/**
* Returns whether the {@link IView view} is visible.
*
* @param index The {@link IView view} index.
*/
isViewVisible(index: number): boolean {
if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds');
@ -428,6 +694,12 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
return viewItem.visible;
}
/**
* Set a {@link IView view}'s visibility.
*
* @param index The {@link IView view} index.
* @param visible Whether the {@link IView view} should be visible.
*/
setViewVisible(index: number, visible: boolean): void {
if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds');
@ -441,6 +713,11 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.saveProportions();
}
/**
* Returns the {@link IView view}'s size previously to being hidden.
*
* @param index The {@link IView view} index.
*/
getViewCachedVisibleSize(index: number): number | undefined {
if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds');
@ -450,6 +727,12 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
return viewItem.cachedVisibleSize;
}
/**
* Layout the {@link SplitView}.
*
* @param size The entire size of the {@link SplitView}.
* @param layoutContext An optional layout context to pass along to {@link IView views}.
*/
layout(size: number, layoutContext?: TLayoutContext): void {
const previousSize = Math.max(this.size, this.contentSize);
this.size = size;
@ -616,6 +899,12 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
}
}
/**
* Resize a {@link IView view} within the {@link SplitView}.
*
* @param index The {@link IView view} index.
* @param size The {@link IView view} size.
*/
resizeView(index: number, size: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
@ -640,6 +929,9 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.state = State.Idle;
}
/**
* Distribute the entire {@link SplitView} size among all {@link IView views}.
*/
distributeViewSizes(): void {
const flexibleViewItems: ViewItem<TLayoutContext>[] = [];
let flexibleSize = 0;
@ -664,6 +956,9 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.relayout(lowPriorityIndexes, highPriorityIndexes);
}
/**
* Returns the size of a {@link IView view}.
*/
getViewSize(index: number): number {
if (index < 0 || index >= this.viewItems.length) {
return -1;
@ -964,16 +1259,16 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible;
if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) {
sash.state = SashState.Minimum;
sash.state = SashState.AtMinimum;
} else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) {
sash.state = SashState.Maximum;
sash.state = SashState.AtMaximum;
} else {
sash.state = SashState.Disabled;
}
} else if (min && !max) {
sash.state = SashState.Minimum;
sash.state = SashState.AtMinimum;
} else if (!min && max) {
sash.state = SashState.Maximum;
sash.state = SashState.AtMaximum;
} else {
sash.state = SashState.Enabled;
}

View file

@ -229,9 +229,9 @@ export function getCaseInsensitive(target: obj, key: string): any {
export function filter(obj: obj, predicate: (key: string, value: any) => boolean): obj {
const result = Object.create(null);
for (const key of Object.keys(obj)) {
if (predicate(key, obj[key])) {
result[key] = obj[key];
for (const [key, value] of Object.entries(obj)) {
if (predicate(key, value)) {
result[key] = value;
}
}
return result;

View file

@ -63,6 +63,90 @@ function doFindFreePort(startPort: number, giveUpAfter: number, stride: number,
client.connect(startPort, '127.0.0.1');
}
// Reference: https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/net/base/port_util.cc#56
export const BROWSER_RESTRICTED_PORTS: any = {
1: true, // tcpmux
7: true, // echo
9: true, // discard
11: true, // systat
13: true, // daytime
15: true, // netstat
17: true, // qotd
19: true, // chargen
20: true, // ftp data
21: true, // ftp access
22: true, // ssh
23: true, // telnet
25: true, // smtp
37: true, // time
42: true, // name
43: true, // nicname
53: true, // domain
69: true, // tftp
77: true, // priv-rjs
79: true, // finger
87: true, // ttylink
95: true, // supdup
101: true, // hostriame
102: true, // iso-tsap
103: true, // gppitnp
104: true, // acr-nema
109: true, // pop2
110: true, // pop3
111: true, // sunrpc
113: true, // auth
115: true, // sftp
117: true, // uucp-path
119: true, // nntp
123: true, // NTP
135: true, // loc-srv /epmap
137: true, // netbios
139: true, // netbios
143: true, // imap2
161: true, // snmp
179: true, // BGP
389: true, // ldap
427: true, // SLP (Also used by Apple Filing Protocol)
465: true, // smtp+ssl
512: true, // print / exec
513: true, // login
514: true, // shell
515: true, // printer
526: true, // tempo
530: true, // courier
531: true, // chat
532: true, // netnews
540: true, // uucp
548: true, // AFP (Apple Filing Protocol)
554: true, // rtsp
556: true, // remotefs
563: true, // nntp+ssl
587: true, // smtp (rfc6409)
601: true, // syslog-conn (rfc3195)
636: true, // ldap+ssl
989: true, // ftps-data
990: true, // ftps
993: true, // ldap+ssl
995: true, // pop3+ssl
1719: true, // h323gatestat
1720: true, // h323hostcall
1723: true, // pptp
2049: true, // nfs
3659: true, // apple-sasl / PasswordServer
4045: true, // lockd
5060: true, // sip
5061: true, // sips
6000: true, // X11
6566: true, // sane-port
6665: true, // Alternate IRC [Apple addition]
6666: true, // Alternate IRC [Apple addition]
6667: true, // Standard IRC [Apple addition]
6668: true, // Alternate IRC [Apple addition]
6669: true, // Alternate IRC [Apple addition]
6697: true, // IRC + TLS
10080: true // Amanda
};
/**
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
*/

View file

@ -297,12 +297,12 @@ suite('Splitview', () => {
assert.strictEqual(sashes[1].state, SashState.Disabled, 'second sash is disabled');
view1.maximumSize = 300;
assert.strictEqual(sashes[0].state, SashState.Minimum, 'first sash is enabled');
assert.strictEqual(sashes[1].state, SashState.Minimum, 'second sash is enabled');
assert.strictEqual(sashes[0].state, SashState.AtMinimum, 'first sash is enabled');
assert.strictEqual(sashes[1].state, SashState.AtMinimum, 'second sash is enabled');
view2.maximumSize = 200;
assert.strictEqual(sashes[0].state, SashState.Minimum, 'first sash is enabled');
assert.strictEqual(sashes[1].state, SashState.Minimum, 'second sash is enabled');
assert.strictEqual(sashes[0].state, SashState.AtMinimum, 'first sash is enabled');
assert.strictEqual(sashes[1].state, SashState.AtMinimum, 'second sash is enabled');
splitview.resizeView(0, 40);
assert.strictEqual(sashes[0].state, SashState.Enabled, 'first sash is enabled');

View file

@ -118,7 +118,7 @@ class CodeMain {
});
// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)
bufferLogService.logger = new SpdLogLogger('main', join(environmentMainService.logsPath, 'main.log'), true, bufferLogService.getLevel());
bufferLogService.logger = new SpdLogLogger('main', join(environmentMainService.logsPath, 'main.log'), true, false, bufferLogService.getLevel());
// Lifecycle
once(lifecycleMainService.onWillShutdown)(evt => {

View file

@ -110,7 +110,7 @@ class CliMain extends Disposable {
// Log
const logLevel = getLogLevel(environmentService);
const loggers: ILogger[] = [];
loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, logLevel));
loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, false, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogger(logLevel));
}

View file

@ -202,9 +202,12 @@ export class DecorationsOverlay extends DynamicViewOverlay {
if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) {
const singleVisibleRange = lineVisibleRanges.ranges[0];
if (singleVisibleRange.width === 0) {
// collapsed range case => make the decoration visible by faking its width
lineVisibleRanges.ranges[0] = new HorizontalRange(singleVisibleRange.left, this._typicalHalfwidthCharacterWidth);
if (singleVisibleRange.width < this._typicalHalfwidthCharacterWidth) {
// collapsed/very small range case => make the decoration visible by expanding its width
// expand its size on both sides (both to the left and to the right, keeping it centered)
const center = Math.round(singleVisibleRange.left + singleVisibleRange.width / 2);
const left = Math.max(0, Math.round(center - this._typicalHalfwidthCharacterWidth / 2));
lineVisibleRanges.ranges[0] = new HorizontalRange(left, this._typicalHalfwidthCharacterWidth);
}
}

View file

@ -4373,7 +4373,7 @@ export const EditorOptions = {
default: 0,
minimum: 0,
maximum: 100,
markdownDescription: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, the 90% of `#editor.fontSize#` is used.")
markdownDescription: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, 90% of `#editor.fontSize#` is used.")
})),
colorDecorators: register(new EditorBooleanOption(
EditorOption.colorDecorators, 'colorDecorators', true,

View file

@ -27,6 +27,7 @@ export class DocumentSemanticTokensResult {
constructor(
public readonly provider: DocumentSemanticTokensProvider,
public readonly tokens: SemanticTokens | SemanticTokensEdits | null,
public readonly error: any
) { }
}
@ -45,10 +46,11 @@ export async function getDocumentSemanticTokens(model: ITextModel, lastProvider:
// Get tokens from all providers at the same time.
const results = await Promise.all(providers.map(async (provider) => {
let result: SemanticTokens | SemanticTokensEdits | null | undefined;
let error: any = null;
try {
result = await provider.provideDocumentSemanticTokens(model, (provider === lastProvider ? lastResultId : null), token);
} catch (err) {
onUnexpectedExternalError(err);
error = err;
result = null;
}
@ -56,11 +58,15 @@ export async function getDocumentSemanticTokens(model: ITextModel, lastProvider:
result = null;
}
return new DocumentSemanticTokensResult(provider, result);
return new DocumentSemanticTokensResult(provider, result, error);
}));
// Try to return the first result with actual tokens
// Try to return the first result with actual tokens or
// the first result which threw an error (!!)
for (const result of results) {
if (result.error) {
throw result.error;
}
if (result.tokens) {
return result;
}

View file

@ -474,6 +474,11 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
}
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
if (input.renderControlCharacters && !input.isBasicASCII) {
// Calling `extractControlCharacters` before adding (possibly empty) line parts
// for inline decorations. `extractControlCharacters` removes empty line parts.
tokens = extractControlCharacters(lineContent, tokens);
}
if (input.renderWhitespace === RenderWhitespace.All ||
input.renderWhitespace === RenderWhitespace.Boundary ||
(input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine) ||
@ -500,9 +505,6 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
// We can never split RTL text, as it ruins the rendering
tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures);
}
if (input.renderControlCharacters && !input.isBasicASCII) {
tokens = extractControlCharacters(lineContent, tokens);
}
return new ResolvedRenderLineInput(
input.useMonospaceOptimizations,

View file

@ -21,6 +21,7 @@ import { IRange } from 'vs/editor/common/core/range';
import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITextModel } from 'vs/editor/common/model';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { FoldingRangeKind, FoldingRangeProviderRegistry } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/foldingModel';
@ -130,7 +131,7 @@ export class FoldingController extends Disposable implements IEditorContribution
const options = this.editor.getOptions();
this.foldingDecorationProvider.autoHideFoldingControls = options.get(EditorOption.showFoldingControls) === 'mouseover';
this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);
this.onModelContentChanged();
this.triggerFoldingModelChanged();
}
if (e.hasChanged(EditorOption.foldingStrategy)) {
this._useFoldingProviders = this.editor.getOptions().get(EditorOption.foldingStrategy) !== 'indentation';
@ -225,7 +226,7 @@ export class FoldingController extends Disposable implements IEditorContribution
this.localToDispose.add(this.cursorChangedScheduler);
this.localToDispose.add(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged()));
this.localToDispose.add(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well
this.localToDispose.add(this.editor.onDidChangeModelContent(() => this.onModelContentChanged()));
this.localToDispose.add(this.editor.onDidChangeModelContent(e => this.onDidChangeModelContent(e)));
this.localToDispose.add(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged()));
this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
@ -250,7 +251,7 @@ export class FoldingController extends Disposable implements IEditorContribution
this.rangeProvider = null;
}
});
this.onModelContentChanged();
this.triggerFoldingModelChanged();
}
private onFoldingStrategyChanged() {
@ -258,7 +259,7 @@ export class FoldingController extends Disposable implements IEditorContribution
this.rangeProvider.dispose();
}
this.rangeProvider = null;
this.onModelContentChanged();
this.triggerFoldingModelChanged();
}
private getRangeProvider(editorModel: ITextModel): RangeProvider {
@ -278,7 +279,7 @@ export class FoldingController extends Disposable implements IEditorContribution
}, 30000);
return rangeProvider; // keep memento in case there are still no foldingProviders on the next request.
} else if (foldingProviders.length > 0) {
this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.onModelContentChanged());
this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.triggerFoldingModelChanged());
}
}
this.foldingStateMemento = null;
@ -289,7 +290,12 @@ export class FoldingController extends Disposable implements IEditorContribution
return this.foldingModelPromise;
}
private onModelContentChanged() {
private onDidChangeModelContent(e: IModelContentChangedEvent) {
this.hiddenRangeModel?.notifyChangeModelContent(e);
this.triggerFoldingModelChanged();
}
private triggerFoldingModelChanged() {
if (this.updateScheduler) {
if (this.foldingRegionPromise) {
this.foldingRegionPromise.cancel();

View file

@ -4,17 +4,22 @@
*--------------------------------------------------------------------------------------------*/
import { findFirstInSorted } from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { countEOL } from 'vs/editor/common/model/tokensStore';
import { CollapseMemento, FoldingModel } from 'vs/editor/contrib/folding/foldingModel';
export class HiddenRangeModel {
private readonly _foldingModel: FoldingModel;
private _hiddenRanges: IRange[];
private _foldingModelListener: IDisposable | null;
private readonly _updateEventEmitter = new Emitter<IRange[]>();
private _hasLineChanges: boolean = false;
public get onDidChange(): Event<IRange[]> { return this._updateEventEmitter.event; }
public get hiddenRanges() { return this._hiddenRanges; }
@ -28,6 +33,14 @@ export class HiddenRangeModel {
}
}
public notifyChangeModelContent(e: IModelContentChangedEvent) {
if (this._hiddenRanges.length && !this._hasLineChanges) {
this._hasLineChanges = e.changes.some(change => {
return change.range.endLineNumber !== change.range.startLineNumber || countEOL(change.text)[0] !== 0;
});
}
}
private updateHiddenRanges(): void {
let updateHiddenAreas = false;
let newHiddenAreas: IRange[] = [];
@ -61,7 +74,7 @@ export class HiddenRangeModel {
lastCollapsedStart = startLineNumber;
lastCollapsedEnd = endLineNumber;
}
if (updateHiddenAreas || k < this._hiddenRanges.length) {
if (this._hasLineChanges || updateHiddenAreas || k < this._hiddenRanges.length) {
this.applyHiddenRanges(newHiddenAreas);
}
}
@ -90,6 +103,7 @@ export class HiddenRangeModel {
private applyHiddenRanges(newHiddenAreas: IRange[]) {
this._hiddenRanges = newHiddenAreas;
this._hasLineChanges = false;
this._updateEventEmitter.fire(newHiddenAreas);
}

View file

@ -105,9 +105,17 @@ class ModesContentComputer implements IHoverComputer<IHoverPart[]> {
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) {
return false;
if (d.options.showIfCollapsed) {
// Relax check around `showIfCollapsed` decorations to also include +/- 1 character
if (startColumn > anchor.range.startColumn + 1 || anchor.range.endColumn - 1 > endColumn) {
return false;
}
} else {
if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) {
return false;
}
}
return true;
});
}

View file

@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
import { DocumentSemanticTokensProvider, DocumentSemanticTokensProviderRegistry, ProviderResult, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/modes';
import { getDocumentSemanticTokens } from 'vs/editor/common/services/getSemanticTokens';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
suite('getSemanticTokens', () => {
test('issue #136540: semantic highlighting flickers', async () => {
const disposables = new DisposableStore();
const provider = new class implements DocumentSemanticTokensProvider {
getLegend(): SemanticTokensLegend {
return { tokenTypes: ['test'], tokenModifiers: [] };
}
provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensEdits> {
throw canceled();
}
releaseDocumentSemanticTokens(resultId: string | undefined): void {
}
};
disposables.add(DocumentSemanticTokensProviderRegistry.register('testLang', provider));
const textModel = disposables.add(createTextModel('example', undefined, 'testLang'));
await getDocumentSemanticTokens(textModel, null, null, CancellationToken.None).then((res) => {
assert.fail();
}, (err) => {
assert.ok(!!err);
});
disposables.dispose();
});
});

View file

@ -1722,6 +1722,46 @@ suite('viewLineRenderer.renderLine 2', () => {
assert.deepStrictEqual(actual.html, expected);
});
test('issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on', () => {
let actual = renderViewLine(new RenderLineInput(
true,
true,
'some text £',
false,
false,
false,
0,
createViewLineTokens([createPart(11, 3)]),
[
new LineDecoration(5, 5, 'inlineDec1', InlineDecorationType.After),
new LineDecoration(6, 6, 'inlineDec2', InlineDecorationType.Before),
],
4,
0,
10,
10,
10,
10000,
'none',
true,
false,
null
));
let expected = [
'<span>',
'<span class="mtk3">some</span>',
'<span class="mtk3 inlineDec1"></span>',
'<span class="mtk3">\u00a0</span>',
'<span class="mtk3 inlineDec2"></span>',
'<span class="mtk3">text\u00a0£</span>',
'</span>'
].join('');
assert.deepStrictEqual(actual.html, expected);
});
test('issue #22832: Consider fullwidth characters when rendering tabs', () => {
let actual = renderViewLine(new RenderLineInput(

View file

@ -101,6 +101,7 @@ export class MenuId {
static readonly ExplorerContext = new MenuId('ExplorerContext');
static readonly ExtensionContext = new MenuId('ExtensionContext');
static readonly GlobalActivity = new MenuId('GlobalActivity');
static readonly LayoutControlMenu = new MenuId('LayoutControlMenu');
static readonly MenubarMainMenu = new MenuId('MenubarMainMenu');
static readonly MenubarAppearanceMenu = new MenuId('MenubarAppearanceMenu');
static readonly MenubarDebugMenu = new MenuId('MenubarDebugMenu');

View file

@ -9,40 +9,6 @@ import { language, locale } from 'vs/base/common/platform';
import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver';
import localizedStrings from 'vs/platform/localizations/common/localizedStrings';
function serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
for (let j = 0; j < element.attributes.length; j++) {
const attr = element.attributes.item(j);
if (attr) {
attributes[attr.name] = attr.value;
}
}
const children: IElement[] = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
const child = element.children.item(i);
if (child) {
children.push(serializeElement(child, true));
}
}
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
return {
tagName: element.tagName,
className: element.className,
textContent: element.textContent || '',
attributes,
children,
left,
top
};
}
export abstract class BaseWindowDriver implements IWindowDriver {
abstract click(selector: string, xoffset?: number, yoffset?: number): Promise<void>;
@ -94,12 +60,46 @@ export abstract class BaseWindowDriver implements IWindowDriver {
for (let i = 0; i < query.length; i++) {
const element = query.item(i);
result.push(serializeElement(element, recursive));
result.push(this.serializeElement(element, recursive));
}
return result;
}
private serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
for (let j = 0; j < element.attributes.length; j++) {
const attr = element.attributes.item(j);
if (attr) {
attributes[attr.name] = attr.value;
}
}
const children: IElement[] = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
const child = element.children.item(i);
if (child) {
children.push(this.serializeElement(child, true));
}
}
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
return {
tagName: element.tagName,
className: element.className,
textContent: element.textContent || '',
attributes,
children,
left,
top
};
}
async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this._getElementXY(selector, offset);

View file

@ -84,42 +84,13 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
}
if (!await this.canInstall(extension)) {
const targetPlatform = await this.getTargetPlatform();
const error = new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.Incompatible);
this.logService.error(`Cannot install extension.`, extension.identifier.id, error.message);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
try {
extension = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion);
return await this.doInstallFromGallery(extension, options);
} catch (error) {
this.logService.error(getErrorMessage(error));
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
this.logService.error(`Failed to install extension.`, extension.identifier.id);
this.logService.error(error);
throw toExtensionManagementError(error);
}
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
if (manifest === null) {
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
if (manifest.version !== extension.version) {
const error = new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
this.logService.error(error.message);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
return this.installExtension(manifest, extension, options);
}
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
@ -157,6 +128,41 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
this.participants.push(participant);
}
private async doInstallFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
}
if (!await this.canInstall(extension)) {
const targetPlatform = await this.getTargetPlatform();
const error = new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.Incompatible);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
try {
extension = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion);
} catch (error) {
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
if (manifest === null) {
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
if (manifest.version !== extension.version) {
const error = new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
throw error;
}
return this.installExtension(manifest, extension, options);
}
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
@ -209,7 +215,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
} else {
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
this.logService.error(error);
throw error;
}
}
@ -253,7 +258,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, error);
}
this.logService.error('Error while installing the extension:', task.identifier.id);
this.logService.error(error);
throw error;
} finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); }
}));
@ -287,12 +291,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
}
this.logService.error(`Failed to install extension:`, installExtensionTask.identifier.id, getErrorMessage(error));
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source })));
if (error instanceof Error) {
error.name = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ExtensionManagementErrorCode.Internal;
}
throw error;
} finally {
/* Remove the gallery tasks from the cache */
@ -619,6 +618,15 @@ export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | stri
}, new Error(''));
}
function toExtensionManagementError(error: Error): ExtensionManagementError {
if (error instanceof ExtensionManagementError) {
return error;
}
const e = new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Internal);
e.stack = error.stack;
return e;
}
export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ExtensionManagementErrorCode.Internal : undefined;
/* __GDPR__

View file

@ -10,7 +10,7 @@ export interface IKeyboardMapper {
dumpDebugInfo(): string;
resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[];
resolveUserBinding(parts: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[];
}
export class CachedKeyboardMapper implements IKeyboardMapper {

View file

@ -154,7 +154,7 @@ export class FileLoggerService extends AbstractLoggerService implements ILoggerS
protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger {
const logger = new BufferLogService(logLevel);
whenProviderRegistered(resource, this.fileService).then(() => (<BufferLogService>logger).logger = this.instantiationService.createInstance(FileLogger, basename(resource), resource, logger.getLevel(), !!options?.donotUseFormatters));
whenProviderRegistered(resource, this.fileService).then(() => (<BufferLogService>logger).logger = this.instantiationService.createInstance(FileLogger, options?.name || basename(resource), resource, logger.getLevel(), !!options?.donotUseFormatters));
return logger;
}
}

View file

@ -23,11 +23,7 @@ export class LoggerService extends AbstractLoggerService implements ILoggerServi
protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger {
if (resource.scheme === Schemas.file) {
const logger = new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, logLevel);
if (options?.donotUseFormatters) {
(<SpdLogLogger>logger).clearFormatters();
}
return logger;
return new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, !!options?.donotUseFormatters, logLevel);
} else {
return new FileLogger(options?.name ?? basename(resource), resource, logLevel, !!options?.donotUseFormatters, this.fileService);
}

View file

@ -7,12 +7,16 @@ import * as spdlog from 'spdlog';
import { ByteSize } from 'vs/platform/files/common/files';
import { AbstractMessageLogger, ILogger, LogLevel } from 'vs/platform/log/common/log';
async function createSpdLogLogger(name: string, logfilePath: string, filesize: number, filecount: number): Promise<spdlog.Logger | null> {
async function createSpdLogLogger(name: string, logfilePath: string, filesize: number, filecount: number, donotUseFormatters: boolean): Promise<spdlog.Logger | null> {
// Do not crash if spdlog cannot be loaded
try {
const _spdlog = await import('spdlog');
_spdlog.setFlushOn(LogLevel.Trace);
return _spdlog.createAsyncRotatingLogger(name, logfilePath, filesize, filecount);
const logger = await _spdlog.createAsyncRotatingLogger(name, logfilePath, filesize, filecount);
if (donotUseFormatters) {
logger.clearFormatters();
}
return logger;
} catch (e) {
console.error(e);
}
@ -49,14 +53,15 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger {
private _logger: spdlog.Logger | undefined;
constructor(
private readonly name: string,
private readonly filepath: string,
private readonly rotating: boolean,
level: LogLevel
name: string,
filepath: string,
rotating: boolean,
donotUseFormatters: boolean,
level: LogLevel,
) {
super();
this.setLevel(level);
this._loggerCreationPromise = this._createSpdLogLogger();
this._loggerCreationPromise = this._createSpdLogLogger(name, filepath, rotating, donotUseFormatters);
this._register(this.onDidChangeLogLevel(level => {
if (this._logger) {
this._logger.setLevel(level);
@ -64,20 +69,18 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger {
}));
}
private _createSpdLogLogger(): Promise<void> {
const filecount = this.rotating ? 6 : 1;
private async _createSpdLogLogger(name: string, filepath: string, rotating: boolean, donotUseFormatters: boolean): Promise<void> {
const filecount = rotating ? 6 : 1;
const filesize = (30 / filecount) * ByteSize.MB;
return createSpdLogLogger(this.name, this.filepath, filesize, filecount)
.then(logger => {
if (logger) {
this._logger = logger;
this._logger.setLevel(this.getLevel());
for (const { level, message } of this.buffer) {
log(this._logger, level, message);
}
this.buffer = [];
}
});
const logger = await createSpdLogLogger(name, filepath, filesize, filecount, donotUseFormatters);
if (logger) {
this._logger = logger;
this._logger.setLevel(this.getLevel());
for (const { level, message } of this.buffer) {
log(this._logger, level, message);
}
this.buffer = [];
}
}
protected log(level: LogLevel, message: string): void {
@ -88,14 +91,6 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger {
}
}
clearFormatters(): void {
if (this._logger) {
this._logger.clearFormatters();
} else {
this._loggerCreationPromise.then(() => this.clearFormatters());
}
}
override flush(): void {
if (this._logger) {
this._logger.flush();

View file

@ -57,7 +57,7 @@ else {
// Running out of sources
if (Object.keys(product).length === 0) {
Object.assign(product, {
version: '1.62.0-dev',
version: '1.63.0-dev',
nameShort: 'Code - OSS Dev',
nameLong: 'Code - OSS Dev',
applicationName: 'code-oss',

View file

@ -24,6 +24,8 @@ export interface ResolvedOptions {
export interface TunnelDescription {
remoteAddress: { port: number, host: string };
localAddress: { port: number, host: string } | string;
privacy?: string;
protocol?: string;
}
export interface TunnelInformation {
environmentTunnels?: TunnelDescription[];

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as net from 'net';
import { findFreePortFaster } from 'vs/base/node/ports';
import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from 'vs/base/node/ports';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
@ -18,8 +18,18 @@ import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as IShare
import { ISignService } from 'vs/platform/sign/common/sign';
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
return tunnel.waitForReady();
let readyTunnel: NodeRemoteTunnel | undefined;
for (let attempts = 3; attempts; attempts--) {
if (readyTunnel) {
readyTunnel.dispose();
}
const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
readyTunnel = await tunnel.waitForReady();
if ((tunnelLocalPort && BROWSER_RESTRICTED_PORTS[tunnelLocalPort]) || !BROWSER_RESTRICTED_PORTS[readyTunnel.tunnelLocalPort]) {
break;
}
}
return readyTunnel!;
}
class NodeRemoteTunnel extends Disposable implements RemoteTunnel {

View file

@ -75,7 +75,7 @@ class CliMain extends Disposable {
const environmentService = new ServerEnvironmentService(this.args, productService);
services.set(IServerEnvironmentService, environmentService);
const logService: ILogService = new LogService(new SpdLogLogger(RemoteExtensionLogFileName, join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, getLogLevel(environmentService)));
const logService: ILogService = new LogService(new SpdLogLogger(RemoteExtensionLogFileName, join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, false, getLogLevel(environmentService)));
services.set(ILogService, logService);
logService.trace(`Remote configuration data at ${this.remoteDataFolder}`);
logService.trace('process arguments:', this.args);

View file

@ -1043,7 +1043,7 @@ const getOrCreateSpdLogService: (environmentService: IServerEnvironmentService)
let _logService: ILogService | null;
return function getLogService(environmentService: IServerEnvironmentService): ILogService {
if (!_logService) {
_logService = new LogService(new SpdLogLogger(RemoteExtensionLogFileName, join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, getLogLevel(environmentService)));
_logService = new LogService(new SpdLogLogger(RemoteExtensionLogFileName, join(environmentService.logsPath, `${RemoteExtensionLogFileName}.log`), true, false, getLogLevel(environmentService)));
}
return _logService;
};

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

@ -818,7 +818,7 @@ declare module 'vscode' {
/**
* The optional ThemeColor of the icon. The color is currently only used in {@link TreeItem}.
*/
readonly color?: ThemeColor;
readonly color?: ThemeColor | undefined;
/**
* Creates a reference to a theme icon.
@ -1107,13 +1107,13 @@ declare module 'vscode' {
/**
* The selections in this text editor. The primary selection is always at index 0.
*/
selections: Selection[];
selections: readonly Selection[];
/**
* The current visible ranges in the editor (vertically).
* This accounts only for vertical scrolling, and not for horizontal scrolling.
*/
readonly visibleRanges: Range[];
readonly visibleRanges: readonly Range[];
/**
* Text editor options.
@ -2711,7 +2711,7 @@ declare module 'vscode' {
/*
* If specified the expression overrides the extracted expression.
*/
readonly expression?: string;
readonly expression?: string | undefined;
/**
* Creates a new evaluatable expression object.
@ -2778,7 +2778,7 @@ declare module 'vscode' {
/**
* If specified the name of the variable to look up.
*/
readonly variableName?: string;
readonly variableName?: string | undefined;
/**
* How to perform the lookup.
*/
@ -2807,7 +2807,7 @@ declare module 'vscode' {
/**
* If specified the expression overrides the extracted expression.
*/
readonly expression?: string;
readonly expression?: string | undefined;
/**
* Creates a new InlineValueEvaluatableExpression object.
*
@ -4907,7 +4907,7 @@ declare module 'vscode' {
* An optional word pattern that describes valid contents for the given ranges.
* If no pattern is provided, the language configuration's word pattern will be used.
*/
readonly wordPattern?: RegExp;
readonly wordPattern: RegExp | undefined;
}
/**
@ -6577,7 +6577,7 @@ declare module 'vscode' {
* Whether the task that is part of this group is the default for the group.
* This property cannot be set through API, and is controlled by a user's task configurations.
*/
readonly isDefault?: boolean;
readonly isDefault: boolean | undefined;
/**
* The ID of the task group. Is one of TaskGroup.Clean.id, TaskGroup.Build.id, TaskGroup.Rebuild.id, or TaskGroup.Test.id.
@ -6913,7 +6913,7 @@ declare module 'vscode' {
/**
* The task's scope.
*/
readonly scope?: TaskScope.Global | TaskScope.Workspace | WorkspaceFolder;
readonly scope: TaskScope.Global | TaskScope.Workspace | WorkspaceFolder | undefined;
/**
* The task's name
@ -8656,7 +8656,7 @@ declare module 'vscode' {
/**
* The currently visible editors or an empty array.
*/
export let visibleTextEditors: TextEditor[];
export let visibleTextEditors: readonly TextEditor[];
/**
* An {@link Event} which fires when the {@link window.activeTextEditor active editor}
@ -8669,7 +8669,7 @@ declare module 'vscode' {
* An {@link Event} which fires when the array of {@link window.visibleTextEditors visible editors}
* has changed.
*/
export const onDidChangeVisibleTextEditors: Event<TextEditor[]>;
export const onDidChangeVisibleTextEditors: Event<readonly TextEditor[]>;
/**
* An {@link Event} which fires when the selection in an editor has changed.
@ -9354,7 +9354,7 @@ declare module 'vscode' {
/**
* Selected elements.
*/
readonly selection: T[];
readonly selection: readonly T[];
}
@ -9388,7 +9388,7 @@ declare module 'vscode' {
/**
* Currently selected elements.
*/
readonly selection: T[];
readonly selection: readonly T[];
/**
* Event that is fired when the {@link TreeView.selection selection} has changed
@ -13078,7 +13078,7 @@ declare module 'vscode' {
/**
* The host.
*/
readonly host?: string;
readonly host?: string | undefined;
/**
* Create a description for a debug adapter running as a socket based server.
@ -13251,15 +13251,15 @@ declare module 'vscode' {
/**
* An optional expression for conditional breakpoints.
*/
readonly condition?: string;
readonly condition?: string | undefined;
/**
* An optional expression that controls how many hits of the breakpoint are ignored.
*/
readonly hitCondition?: string;
readonly hitCondition?: string | undefined;
/**
* An optional message that gets logged when this breakpoint is hit. Embedded expressions within {} are interpolated by the debug adapter.
*/
readonly logMessage?: string;
readonly logMessage?: string | undefined;
protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string);
}
@ -13387,7 +13387,7 @@ declare module 'vscode' {
/**
* List of breakpoints.
*/
export let breakpoints: Breakpoint[];
export let breakpoints: readonly Breakpoint[];
/**
* An {@link Event} which fires when the {@link debug.activeDebugSession active debug session}
@ -14322,7 +14322,7 @@ declare module 'vscode' {
* The process of running tests should resolve the children of any test
* items who have not yet been resolved.
*/
readonly include: TestItem[] | undefined;
readonly include: readonly TestItem[] | undefined;
/**
* An array of tests the user has marked as excluded from the test included
@ -14331,7 +14331,7 @@ declare module 'vscode' {
* May be omitted if no exclusions were requested. Test controllers should
* not run excluded tests or any children of excluded tests.
*/
readonly exclude: TestItem[] | undefined;
readonly exclude: readonly TestItem[] | undefined;
/**
* The profile used for this request. This will always be defined

View file

@ -16,8 +16,7 @@
declare module 'vscode' {
// eslint-disable-next-line vscode-dts-region-comments
//#region @alexdima - resolvers
//#region resolvers: @alexdima
export interface MessageOptions {
/**
@ -268,7 +267,7 @@ declare module 'vscode' {
//#endregion
//#region editor insets: https://github.com/microsoft/vscode/issues/85682
//#region editorInsets: https://github.com/microsoft/vscode/issues/85682
export interface WebviewEditorInset {
readonly editor: TextEditor;
@ -285,7 +284,7 @@ declare module 'vscode' {
//#endregion
//#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515
//#region fsChunks: https://github.com/microsoft/vscode/issues/84515
export interface FileSystemProvider {
open?(resource: Uri, options: { create: boolean; }): number | Thenable<number>;
@ -296,7 +295,7 @@ declare module 'vscode' {
//#endregion
//#region TextSearchProvider: https://github.com/microsoft/vscode/issues/59921
//#region textSearchProvider: https://github.com/microsoft/vscode/issues/59921
/**
* The parameters of a query for text search.
@ -553,7 +552,7 @@ declare module 'vscode' {
//#endregion
//#region FileSearchProvider: https://github.com/microsoft/vscode/issues/73524
//#region fileSearchProvider: https://github.com/microsoft/vscode/issues/73524
/**
* The parameters of a query for file search.
@ -719,7 +718,7 @@ declare module 'vscode' {
//#endregion
//#region diff command: https://github.com/microsoft/vscode/issues/84899
//#region diffCommand: https://github.com/microsoft/vscode/issues/84899
/**
* The contiguous set of modified lines in a diff.
@ -753,7 +752,7 @@ declare module 'vscode' {
//#endregion
// eslint-disable-next-line vscode-dts-region-comments
//#region @roblourens: new debug session option for simple UI 'managedByParent' (see https://github.com/microsoft/vscode/issues/128588)
//#region notebookDebugOptions: @roblourens: new debug session option for simple UI 'managedByParent' (see https://github.com/microsoft/vscode/issues/128588)
/**
* Options for {@link debug.startDebugging starting a debug session}.
@ -775,29 +774,7 @@ declare module 'vscode' {
//#endregion
// eslint-disable-next-line vscode-dts-region-comments
//#region @weinand: variables view action contributions
/**
* A DebugProtocolVariableContainer is an opaque stand-in type for the intersection of the Scope and Variable types defined in the Debug Adapter Protocol.
* See https://microsoft.github.io/debug-adapter-protocol/specification#Types_Scope and https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable.
*/
export interface DebugProtocolVariableContainer {
// Properties: the intersection of DAP's Scope and Variable types.
}
/**
* A DebugProtocolVariable is an opaque stand-in type for the Variable type defined in the Debug Adapter Protocol.
* See https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable.
*/
export interface DebugProtocolVariable {
// Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_Variable).
}
//#endregion
// eslint-disable-next-line vscode-dts-region-comments
//#region @joaomoreno: SCM validation
// #region scmValidation: @joaomoreno
/**
* Represents the validation type of the Source Control input.
@ -852,8 +829,7 @@ declare module 'vscode' {
//#endregion
// eslint-disable-next-line vscode-dts-region-comments
//#region @joaomoreno: SCM selected provider
//#region scmSelectedProvider: @joaomoreno
export interface SourceControl {
@ -870,7 +846,7 @@ declare module 'vscode' {
//#endregion
//#region Terminal data write event https://github.com/microsoft/vscode/issues/78502
//#region terminalDataWriteEvent: https://github.com/microsoft/vscode/issues/78502
export interface TerminalDataWriteEvent {
/**
@ -894,7 +870,7 @@ declare module 'vscode' {
//#endregion
//#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718
//#region terminalDimensions: https://github.com/microsoft/vscode/issues/55718
/**
* An {@link Event} which fires when a {@link Terminal}'s dimensions change.
@ -928,7 +904,7 @@ declare module 'vscode' {
//#endregion
//#region Terminal location https://github.com/microsoft/vscode/issues/45407
//#region terminalLocation: https://github.com/microsoft/vscode/issues/45407
export interface TerminalOptions {
location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
@ -968,7 +944,7 @@ declare module 'vscode' {
//#endregion
//#region Terminal name change event https://github.com/microsoft/vscode/issues/114898
//#region terminalNameChangeEvent: https://github.com/microsoft/vscode/issues/114898
export interface Pseudoterminal {
/**
@ -992,8 +968,7 @@ declare module 'vscode' {
//#endregion
// eslint-disable-next-line vscode-dts-region-comments
//#region @jrieken -> exclusive document filters
//#region exclusiveDocumentFilters: @jrieken
export interface DocumentFilter {
readonly exclusive?: boolean;
@ -1001,13 +976,13 @@ declare module 'vscode' {
//#endregion
//#region Tree View: https://github.com/microsoft/vscode/issues/61313 @alexr00
//#region treeViewReveal: https://github.com/microsoft/vscode/issues/61313 @alexr00
export interface TreeView<T> extends Disposable {
reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable<void>;
}
//#endregion
//#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592
//#region treeViewDragAndDrop: https://github.com/microsoft/vscode/issues/32592
/**
* A data provider that provides tree data
*/
@ -1071,7 +1046,7 @@ declare module 'vscode' {
}
//#endregion
//#region Task presentation group: https://github.com/microsoft/vscode/issues/47265
//#region taskPresentationGroup: https://github.com/microsoft/vscode/issues/47265
export interface TaskPresentationOptions {
/**
* Controls whether the task is executed in a specific terminal group using split panes.
@ -1085,7 +1060,7 @@ declare module 'vscode' {
}
//#endregion
//#region Custom editor move https://github.com/microsoft/vscode/issues/86146
//#region customEditorMove: https://github.com/microsoft/vscode/issues/86146
// TODO: Also for custom editor
@ -1109,7 +1084,7 @@ declare module 'vscode' {
//#endregion
//#region allow QuickPicks to skip sorting: https://github.com/microsoft/vscode/issues/73904
//#region quickPickSortByLabel: https://github.com/microsoft/vscode/issues/73904
export interface QuickPick<T extends QuickPickItem> extends QuickInput {
/**
@ -1120,7 +1095,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/124970, Cell Execution State
//#region notebookCellExecutionState: https://github.com/microsoft/vscode/issues/124970
/**
* The execution state of a notebook cell.
@ -1167,7 +1142,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, Notebook, deprecated & misc
//#region notebookDeprecated: https://github.com/microsoft/vscode/issues/106744
export interface NotebookCellOutput {
id: string;
@ -1175,7 +1150,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookEditor
//#region notebookEditor: https://github.com/microsoft/vscode/issues/106744
/**
* Represents a notebook editor that is attached to a {@link NotebookDocument notebook}.
@ -1340,7 +1315,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorEdit
//#region notebookEditorEdit: https://github.com/microsoft/vscode/issues/106744
// todo@API add NotebookEdit-type which handles all these cases?
// export class NotebookEdit {
@ -1388,7 +1363,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorDecorationType
//#region notebookEditorDecorationType: https://github.com/microsoft/vscode/issues/106744
export interface NotebookEditor {
setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookRange): void;
@ -1411,7 +1386,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookConcatTextDocument
//#region notebookConcatTextDocument: https://github.com/microsoft/vscode/issues/106744
export namespace notebooks {
/**
@ -1446,8 +1421,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookContentProvider
//#region notebookContentProvider: https://github.com/microsoft/vscode/issues/106744
interface NotebookDocumentBackup {
/**
@ -1507,7 +1481,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/106744, LiveShare
//#region notebookLiveShare: https://github.com/microsoft/vscode/issues/106744
export interface NotebookRegistrationData {
displayName: string;
@ -1524,7 +1498,7 @@ declare module 'vscode' {
//#endregion
//#region @https://github.com/microsoft/vscode/issues/123601, notebook messaging
//#region notebookMessaging: https://github.com/microsoft/vscode/issues/123601
/**
* Represents a script that is loaded into the notebook renderer before rendering output. This allows
@ -1591,7 +1565,7 @@ declare module 'vscode' {
//#endregion
//#region @eamodio - timeline: https://github.com/microsoft/vscode/issues/84297
//#region timeline: https://github.com/microsoft/vscode/issues/84297
export class TimelineItem {
/**
@ -1749,7 +1723,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/91555
//#region tokenInformation: https://github.com/microsoft/vscode/issues/91555
export enum StandardTokenType {
Other = 0,
@ -1769,7 +1743,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/16221
//#region inlayHints: https://github.com/microsoft/vscode/issues/16221
// todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range)
// todo@API add "mini-markdown" for links and styles
@ -1852,7 +1826,7 @@ declare module 'vscode' {
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/104436
//#region extensionRuntime: https://github.com/microsoft/vscode/issues/104436
export enum ExtensionRuntime {
/**
@ -1871,7 +1845,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/102091
//#region textDocumentNotebook: https://github.com/microsoft/vscode/issues/102091
export interface TextDocument {
@ -1883,7 +1857,7 @@ declare module 'vscode' {
}
//#endregion
//#region proposed test APIs https://github.com/microsoft/vscode/issues/107467
//#region testObserver: https://github.com/microsoft/vscode/issues/107467
export namespace tests {
/**
* Requests that tests be run by their controller.
@ -2079,7 +2053,7 @@ declare module 'vscode' {
//#endregion
//#region Opener service (https://github.com/microsoft/vscode/issues/109277)
//#region externalUriOpener: https://github.com/microsoft/vscode/issues/109277
/**
* Details if an `ExternalUriOpener` can open a uri.
@ -2237,7 +2211,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/Microsoft/vscode/issues/15178
//#region tabs: https://github.com/Microsoft/vscode/issues/15178
/**
* Represents a tab within the window
@ -2332,7 +2306,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/120173
//#region workspaceTrust: https://github.com/microsoft/vscode/issues/120173
/**
* The object describing the properties of the workspace trust request
*/
@ -2356,7 +2330,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/115616 @alexr00
//#region portAttributesProvider: https://github.com/microsoft/vscode/issues/115616 @alexr00
export enum PortAutoForwardAction {
Notify = 1,
OpenBrowser = 2,
@ -2411,19 +2385,7 @@ declare module 'vscode' {
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/119904 @eamodio
export interface SourceControlInputBox {
/**
* Sets focus to the input.
*/
focus(): void;
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima
//#region inlineCompletionProvider: https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima
export namespace languages {
/**
@ -2545,7 +2507,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/126280 @mjbvz
//#region notebookMime: https://github.com/microsoft/vscode/issues/126280 @mjbvz
export interface NotebookCellData {
/**
@ -2573,7 +2535,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/123713 @connor4312
//#region testCoverage: https://github.com/microsoft/vscode/issues/123713 @connor4312
export interface TestRun {
/**
* Test coverage provider for this result. An extension can defer setting
@ -2764,7 +2726,7 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/129037
//#region languageStatus: https://github.com/microsoft/vscode/issues/129037
enum LanguageStatusSeverity {
Information = 0,
@ -2791,7 +2753,7 @@ declare module 'vscode' {
//#endregion
//#region @eamodio https://github.com/microsoft/vscode/issues/133935
//#region scmActionButton: https://github.com/microsoft/vscode/issues/133935
export interface SourceControl {
actionButton?: Command;

View file

@ -10,7 +10,7 @@ import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser';
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 { ExtHostContext, ExtHostEditorInsetsShape, IExtHostContext, IWebviewContentOptions, MainContext, MainThreadEditorInsetsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IWebviewService, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { extHostNamedCustomer } from '../common/extHostCustomers';
@ -70,7 +70,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
this._disposables.dispose();
}
async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> {
async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewContentOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> {
let editor: IActiveCodeEditor | undefined;
id = id.substr(0, id.indexOf(',')); //todo@jrieken HACK
@ -121,7 +121,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
inset.webview.html = value;
}
$setOptions(handle: number, options: IWebviewOptions): void {
$setOptions(handle: number, options: IWebviewContentOptions): void {
const inset = this.getInset(handle);
inset.webview.contentOptions = reviveWebviewContentOptions(options);
}

View file

@ -431,15 +431,6 @@ export class MainThreadSCM implements MainThreadSCMShape {
repository.input.visible = visible;
}
$setInputBoxFocus(sourceControlHandle: number): void {
const repository = this._repositories.get(sourceControlHandle);
if (!repository) {
return;
}
repository.input.setFocus();
}
$showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType) {
const repository = this._repositories.get(sourceControlHandle);
if (!repository) {

View file

@ -153,7 +153,9 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
return (await this.tunnelService.tunnels).map(tunnel => {
return {
remoteAddress: { port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost },
localAddress: tunnel.localAddress
localAddress: tunnel.localAddress,
privacy: tunnel.privacy,
protocol: tunnel.protocol
};
});
}

View file

@ -5,17 +5,17 @@
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { MainThreadWebviews, reviveWebviewContentOptions, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { EditorGroupColumn, columnToEditorGroup, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager';
import { ICreateWebViewShowOptions, IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
import { columnToEditorGroup, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -151,13 +151,8 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
extensionData: extHostProtocol.WebviewExtensionDescription,
handle: extHostProtocol.WebviewHandle,
viewType: string,
initData: {
title: string;
webviewOptions: extHostProtocol.IWebviewOptions;
panelOptions: extHostProtocol.IWebviewPanelOptions;
serializeBuffersForPostMessage: boolean;
},
showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; },
initData: extHostProtocol.IWebviewInitData,
showOptions: extHostProtocol.WebviewPanelShowOptions,
): void {
const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null);
if (showOptions) {
@ -192,7 +187,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
webview.setName(value);
}
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void {
public $setIconPath(handle: extHostProtocol.WebviewHandle, value: extHostProtocol.IWebviewIconPath | undefined): void {
const webview = this.getWebviewInput(handle);
webview.iconPath = reviveWebviewIcon(value);
}
@ -316,12 +311,14 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
}
}
function reviveWebviewIcon(
value: { light: UriComponents, dark: UriComponents; } | undefined
): WebviewIcons | undefined {
return value
? { light: URI.revive(value.light), dark: URI.revive(value.dark) }
: undefined;
function reviveWebviewIcon(value: extHostProtocol.IWebviewIconPath | undefined): WebviewIcons | undefined {
if (!value) {
return undefined;
}
return {
light: URI.revive(value.light),
dark: URI.revive(value.dark),
};
}
function reviveWebviewOptions(panelOptions: extHostProtocol.IWebviewPanelOptions): WebviewOptions {

View file

@ -55,7 +55,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
webview.html = value;
}
public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewOptions): void {
public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewContentOptions): void {
const webview = this.getWebview(handle);
webview.contentOptions = reviveWebviewContentOptions(options);
}
@ -123,10 +123,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
export function reviveWebviewExtension(extensionData: extHostProtocol.WebviewExtensionDescription): WebviewExtensionDescription {
return { id: extensionData.id, location: URI.revive(extensionData.location) };
return {
id: extensionData.id,
location: URI.revive(extensionData.location),
};
}
export function reviveWebviewContentOptions(webviewOptions: extHostProtocol.IWebviewOptions): WebviewContentOptions {
export function reviveWebviewContentOptions(webviewOptions: extHostProtocol.IWebviewContentOptions): WebviewContentOptions {
return {
allowScripts: webviewOptions.enableScripts,
allowForms: webviewOptions.enableForms,

View file

@ -618,11 +618,11 @@ export interface MainThreadTelemetryShape extends IDisposable {
}
export interface MainThreadEditorInsetsShape extends IDisposable {
$createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>;
$createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewContentOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>;
$disposeEditorInset(handle: number): void;
$setHtml(handle: number, value: string): void;
$setOptions(handle: number, options: IWebviewOptions): void;
$setOptions(handle: number, options: IWebviewContentOptions): void;
$postMessage(handle: number, value: any): Promise<boolean>;
}
@ -681,12 +681,12 @@ export interface IWebviewPortMapping {
readonly extensionHostPort: number;
}
export interface IWebviewOptions {
export interface IWebviewContentOptions {
readonly enableScripts?: boolean;
readonly enableForms?: boolean;
readonly enableCommandUris?: boolean;
readonly localResourceRoots?: ReadonlyArray<UriComponents>;
readonly portMapping?: ReadonlyArray<IWebviewPortMapping>;
readonly localResourceRoots?: readonly UriComponents[];
readonly portMapping?: readonly IWebviewPortMapping[];
}
export interface IWebviewPanelOptions {
@ -729,27 +729,34 @@ export interface WebviewMessageArrayBufferReference {
export interface MainThreadWebviewsShape extends IDisposable {
$setHtml(handle: WebviewHandle, value: string): void;
$setOptions(handle: WebviewHandle, options: IWebviewOptions): void;
$setOptions(handle: WebviewHandle, options: IWebviewContentOptions): void;
$postMessage(handle: WebviewHandle, value: string, ...buffers: VSBuffer[]): Promise<boolean>
}
export interface IWebviewIconPath {
readonly light: UriComponents;
readonly dark: UriComponents;
}
export interface IWebviewInitData {
readonly title: string;
readonly webviewOptions: IWebviewContentOptions;
readonly panelOptions: IWebviewPanelOptions;
readonly serializeBuffersForPostMessage: boolean;
}
export interface MainThreadWebviewPanelsShape extends IDisposable {
$createWebviewPanel(
extension: WebviewExtensionDescription,
handle: WebviewHandle,
viewType: string,
initData: {
title: string;
webviewOptions: IWebviewOptions;
panelOptions: IWebviewPanelOptions;
serializeBuffersForPostMessage: boolean;
},
initData: IWebviewInitData,
showOptions: WebviewPanelShowOptions,
): void;
$disposeWebview(handle: WebviewHandle): void;
$reveal(handle: WebviewHandle, showOptions: WebviewPanelShowOptions): void;
$setTitle(handle: WebviewHandle, value: string): void;
$setIconPath(handle: WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void;
$setIconPath(handle: WebviewHandle, value: IWebviewIconPath | undefined): void;
$registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void;
$unregisterSerializer(viewType: string): void;
@ -796,7 +803,7 @@ export interface ExtHostWebviewPanelsShape {
initData: {
title: string;
state: any;
webviewOptions: IWebviewOptions;
webviewOptions: IWebviewContentOptions;
panelOptions: IWebviewPanelOptions;
},
position: EditorGroupColumn,
@ -810,7 +817,7 @@ export interface ExtHostCustomEditorsShape {
viewType: string,
initData: {
title: string;
webviewOptions: IWebviewOptions;
webviewOptions: IWebviewContentOptions;
panelOptions: IWebviewPanelOptions;
},
position: EditorGroupColumn,
@ -1096,7 +1103,6 @@ export interface MainThreadSCMShape extends IDisposable {
$setInputBoxValue(sourceControlHandle: number, value: string): void;
$setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void;
$setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void;
$setInputBoxFocus(sourceControlHandle: number): void;
$showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void;
$setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void;
}

View file

@ -253,7 +253,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor
viewType: string,
initData: {
title: string;
webviewOptions: extHostProtocol.IWebviewOptions;
webviewOptions: extHostProtocol.IWebviewContentOptions;
panelOptions: extHostProtocol.IWebviewPanelOptions;
},
position: EditorGroupColumn,

View file

@ -5,6 +5,7 @@
import { ILoggerService, LogService } from 'vs/platform/log/common/log';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
export class ExtHostLogService extends LogService {
@ -14,7 +15,7 @@ export class ExtHostLogService extends LogService {
@ILoggerService loggerService: ILoggerService,
@IExtHostInitDataService initData: IExtHostInitDataService,
) {
super(loggerService.createLogger(initData.logFile));
super(loggerService.createLogger(initData.logFile, { name: ExtensionHostLogFileName }));
}
}

View file

@ -266,16 +266,6 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
// noop
}
focus(): void {
checkProposedApiEnabled(this._extension);
if (!this._visible) {
this.visible = true;
}
this._proxy.$setInputBoxFocus(this._sourceControlHandle);
}
showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) {
checkProposedApiEnabled(this._extension);

View file

@ -1776,7 +1776,7 @@ export enum TaskPanelKind {
@es5ClassCompat
export class TaskGroup implements vscode.TaskGroup {
isDefault?: boolean;
isDefault: boolean | undefined;
private _id: string;
public static Clean: TaskGroup = new TaskGroup('clean', 'Clean');

View file

@ -176,7 +176,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`);
}
public createNewWebview(handle: string, options: extHostProtocol.IWebviewOptions, extension: IExtensionDescription): ExtHostWebview {
public createNewWebview(handle: string, options: extHostProtocol.IWebviewContentOptions, extension: IExtensionDescription): ExtHostWebview {
const webview = new ExtHostWebview(handle, this._webviewProxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService);
this._webviews.set(handle, webview);
@ -202,7 +202,7 @@ export function serializeWebviewOptions(
extension: IExtensionDescription,
workspace: IExtHostWorkspace | undefined,
options: vscode.WebviewOptions,
): extHostProtocol.IWebviewOptions {
): extHostProtocol.IWebviewContentOptions {
return {
enableCommandUris: options.enableCommandUris,
enableScripts: options.enableScripts,
@ -212,7 +212,7 @@ export function serializeWebviewOptions(
};
}
export function reviveOptions(options: extHostProtocol.IWebviewOptions): vscode.WebviewOptions {
export function reviveOptions(options: extHostProtocol.IWebviewContentOptions): vscode.WebviewOptions {
return {
enableCommandUris: options.enableCommandUris,
enableScripts: options.enableScripts,

View file

@ -281,7 +281,7 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel
initData: {
title: string;
state: any;
webviewOptions: extHostProtocol.IWebviewOptions;
webviewOptions: extHostProtocol.IWebviewContentOptions;
panelOptions: extHostProtocol.IWebviewPanelOptions;
},
position: EditorGroupColumn

View file

@ -14,11 +14,7 @@ export class ExtHostLoggerService extends BaseExtHostLoggerService {
protected override doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger {
if (resource.scheme === Schemas.file) {
const logger = new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, logLevel);
if (options?.donotUseFormatters) {
(<SpdLogLogger>logger).clearFormatters();
}
return logger;
return new SpdLogLogger(options?.name || generateUuid(), resource.fsPath, !options?.donotRotate, !!options?.donotUseFormatters, logLevel);
}
return super.doCreateLogger(resource, logLevel, options);
}

View file

@ -59,11 +59,15 @@ export class ToggleActivityBarVisibilityAction extends Action2 {
category: CATEGORIES.View,
f1: true,
toggled: ContextKeyExpr.equals('config.workbench.activityBar.visible', true),
menu: {
menu: [{
id: MenuId.MenubarAppearanceMenu,
group: '2_workbench_layout',
order: 4
}
}, {
id: MenuId.LayoutControlMenu,
group: '0_workbench_layout',
order: 3
}]
});
}
@ -226,11 +230,15 @@ registerAction2(class extends Action2 {
category: CATEGORIES.View,
f1: true,
toggled: EditorAreaVisibleContext,
menu: {
menu: [{
id: MenuId.MenubarAppearanceMenu,
group: '2_workbench_layout',
order: 5
}
}, {
id: MenuId.LayoutControlMenu,
group: '0_workbench_layout',
order: 5
}]
});
}
@ -274,40 +282,53 @@ class ToggleSidebarVisibilityAction extends Action2 {
registerAction2(ToggleSidebarVisibilityAction);
MenuRegistry.appendMenuItems([{
id: MenuId.ViewContainerTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
MenuRegistry.appendMenuItems([
{
id: MenuId.ViewContainerTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
}
}, {
id: MenuId.MenubarAppearanceMenu,
item: {
group: '2_workbench_layout',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
toggled: SideBarVisibleContext
},
order: 1
}
}, {
id: MenuId.LayoutControlMenu,
item: {
group: '0_workbench_layout',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize('miShowSidebarNoMnnemonic', "Show Side Bar"),
toggled: SideBarVisibleContext
},
order: 0
}
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
}
}, {
id: MenuId.MenubarAppearanceMenu,
item: {
group: '2_workbench_layout',
command: {
id: ToggleSidebarVisibilityAction.ID,
title: localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
toggled: SideBarVisibleContext
},
order: 1
}
}]);
]);
// --- Toggle Statusbar Visibility
@ -328,11 +349,15 @@ export class ToggleStatusbarVisibilityAction extends Action2 {
category: CATEGORIES.View,
f1: true,
toggled: ContextKeyExpr.equals('config.workbench.statusBar.visible', true),
menu: {
menu: [{
id: MenuId.MenubarAppearanceMenu,
group: '2_workbench_layout',
order: 3
}
}, {
id: MenuId.LayoutControlMenu,
group: '0_workbench_layout',
order: 1
}]
});
}

View file

@ -9,7 +9,7 @@ import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { CATEGORIES, Extensions as WorkbenchExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { ActiveAuxiliaryContext, AuxiliaryBarVisibleContext } from 'vs/workbench/common/auxiliarybar';
import { AuxiliaryBarVisibleContext } from 'vs/workbench/common/auxiliarybar';
import { ViewContainerLocation, ViewContainerLocationToString } from 'vs/workbench/common/views';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
@ -62,6 +62,19 @@ class FocusAuxiliaryBarAction extends Action {
}
MenuRegistry.appendMenuItems([
{
id: MenuId.LayoutControlMenu,
item: {
group: '0_workbench_layout',
command: {
id: ToggleAuxiliaryBarAction.ID,
title: localize({ key: 'miShowAuxiliaryBar', comment: ['&& denotes a mnemonic'] }, "Show Si&&de Panel"),
toggled: AuxiliaryBarVisibleContext
},
when: ContextKeyExpr.equals('config.workbench.experimental.sidePanel.enabled', true),
order: 4
}
},
{
id: MenuId.MenubarAppearanceMenu,
item: {
@ -69,7 +82,7 @@ MenuRegistry.appendMenuItems([
command: {
id: ToggleAuxiliaryBarAction.ID,
title: localize({ key: 'miShowAuxiliaryBar', comment: ['&& denotes a mnemonic'] }, "Show Si&&de Panel"),
toggled: ActiveAuxiliaryContext
toggled: AuxiliaryBarVisibleContext
},
when: ContextKeyExpr.equals('config.workbench.experimental.sidePanel.enabled', true),
order: 5

View file

@ -12,7 +12,7 @@ import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorProgressService, IOperation, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Emitter } from 'vs/base/common/event';
import { assertIsDefined } from 'vs/base/common/types';
@ -146,20 +146,8 @@ export class EditorPanes extends Disposable {
// Editor pane
const pane = this.doShowEditorPane(descriptor);
// Show progress while setting input after a certain timeout.
// If the workbench is opening be more relaxed about progress
// showing by increasing the delay a little bit to reduce flicker.
const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200);
// Apply input to pane
let changed: boolean;
let cancelled: boolean;
try {
changed = await this.doSetInput(pane, operation, editor, options, context);
cancelled = !operation.isCurrent();
} finally {
operation.stop();
}
const { changed, cancelled } = await this.doSetInput(pane, editor, options, context);
// Focus unless cancelled
if (!cancelled) {
@ -263,22 +251,36 @@ export class EditorPanes extends Disposable {
this._onDidChangeSizeConstraints.fire(undefined);
}
private async doSetInput(editorPane: EditorPane, operation: IOperation, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<boolean> {
const forceReload = options?.forceReload;
private async doSetInput(editorPane: EditorPane, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<{ changed: boolean, cancelled: boolean }> {
// If the input did not change, return early and only
// apply the options unless the options instruct us to
// force open it even if it is the same
const inputMatches = editorPane.input?.matches(editor);
// If the input did not change, return early and only apply the options
// unless the options instruct us to force open it even if it is the same
if (inputMatches && !forceReload) {
if (inputMatches && !options?.forceReload) {
editorPane.setOptions(options);
return { changed: false, cancelled: false };
}
// Otherwise set the input to the editor pane
else {
// Start a new editor input operation to report progress
// and to support cancellation. Any new operation that is
// started will cancel the previous one.
const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200);
// Set the input to the editor pane
let cancelled = false;
try {
await editorPane.setInput(editor, options, context, operation.token);
if (!operation.isCurrent()) {
cancelled = true;
}
} finally {
operation.stop();
}
return !inputMatches;
return { changed: !inputMatches, cancelled };
}
private doHideActiveEditorPane(): void {

View file

@ -18,8 +18,9 @@ import { ViewContainerLocation, ViewContainerLocations } from 'vs/workbench/comm
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { IDisposable } from 'vs/workbench/workbench.web.api';
import { IView } from 'vs/base/browser/ui/grid/grid';
export interface IPaneCompositePart {
export interface IPaneCompositePart extends IView {
readonly onDidPaneCompositeOpen: Event<IPaneComposite>;
readonly onDidPaneCompositeClose: Event<IPaneComposite>;

View file

@ -298,6 +298,17 @@ MenuRegistry.appendMenuItems([
},
order: 5
}
}, {
id: MenuId.LayoutControlMenu,
item: {
group: '0_workbench_layout',
command: {
id: TogglePanelAction.ID,
title: localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
toggled: ActivePanelContext
},
order: 4
}
}, {
id: MenuId.ViewTitleContext,
item: {

View file

@ -132,11 +132,34 @@
margin-left: auto;
}
.monaco-workbench.mac:not(web) .part.titlebar > .window-controls-container {
position: absolute;
right: 0px;
width: 28px;
display: none;
}
.monaco-workbench.mac:not(web) .part.titlebar > .window-controls-container.show-layout-control {
display: flex;
}
.monaco-workbench.fullscreen .part.titlebar > .window-controls-container {
display: none;
background-color: transparent;
}
.monaco-workbench .part.titlebar > .window-controls-container.show-layout-control {
width: 160px;
}
.monaco-workbench .part.titlebar > .window-controls-container > .layout-dropdown-container {
display: none;
}
.monaco-workbench .part.titlebar > .window-controls-container.show-layout-control > .layout-dropdown-container {
display: inline-block;
}
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon {
display: inline-block;
line-height: 30px;

View file

@ -8,7 +8,7 @@ import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action
import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions';
import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform';
@ -38,6 +38,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IsMacNativeContext, IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export type IOpenRecentAction = IAction & { uri: URI, remoteAuthority?: string };
@ -374,6 +375,7 @@ export class CustomMenubarControl extends MenubarControl {
private alwaysOnMnemonics: boolean = false;
private focusInsideMenubar: boolean = false;
private visible: boolean = true;
private actionRunner: IActionRunner;
private readonly webNavigationMenu = this._register(this.menuService.createMenu(MenuId.MenubarHomeMenu, this.contextKeyService));
private readonly _onVisibilityChange: Emitter<boolean>;
@ -394,6 +396,7 @@ export class CustomMenubarControl extends MenubarControl {
@IAccessibilityService accessibilityService: IAccessibilityService,
@IThemeService private readonly themeService: IThemeService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IHostService hostService: IHostService,
@ICommandService commandService: ICommandService
) {
@ -402,6 +405,11 @@ export class CustomMenubarControl extends MenubarControl {
this._onVisibilityChange = this._register(new Emitter<boolean>());
this._onFocusStateChange = this._register(new Emitter<boolean>());
this.actionRunner = this._register(new ActionRunner());
this.actionRunner.onDidRun(e => {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'menu' });
});
this.workspacesService.getRecentlyOpened().then((recentlyOpened) => {
this.recentlyOpened = recentlyOpened;
});
@ -811,6 +819,7 @@ export class CustomMenubarControl extends MenubarControl {
enableMnemonics: this.currentEnableMenuBarMnemonics,
disableAltFocus: this.currentDisableMenuBarAltFocus,
visibility: this.currentMenubarVisibility,
actionRunner: this.actionRunner,
getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),
alwaysOnMnemonics: this.alwaysOnMnemonics,
compactMode: this.currentCompactMenuMode,

View file

@ -12,7 +12,7 @@ import { getZoomFactor } from 'vs/base/browser/browser';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAction } from 'vs/base/common/actions';
import { IAction, SubmenuAction } from 'vs/base/common/actions';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
@ -28,14 +28,14 @@ import { trim } from 'vs/base/common/strings';
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend } from 'vs/base/browser/dom';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { template } from 'vs/base/common/labels';
import { mnemonicButtonLabel, template } from 'vs/base/common/labels';
import { ILabelService } from 'vs/platform/label/common/label';
import { Emitter } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { IMenuService, IMenu, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IProductService } from 'vs/platform/product/common/productService';
@ -43,6 +43,10 @@ import { Schemas } from 'vs/base/common/network';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
import { getVirtualWorkspaceLocation } from 'vs/platform/remote/common/remoteHosts';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class TitlebarPart extends Part implements ITitleService {
@ -66,10 +70,13 @@ export class TitlebarPart extends Part implements ITitleService {
declare readonly _serviceBrand: undefined;
protected title!: HTMLElement;
protected customMenubar: CustomMenubarControl | undefined;
protected appIcon: HTMLElement | undefined;
private appIconBadge: HTMLElement | undefined;
protected menubar?: HTMLElement;
protected windowControls: HTMLElement | undefined;
private layoutToolbar: ActionBar | undefined;
protected lastLayoutDimensions: Dimension | undefined;
private titleBarStyle: 'native' | 'custom';
@ -91,12 +98,13 @@ export class TitlebarPart extends Part implements ITitleService {
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IThemeService themeService: IThemeService,
@ILabelService private readonly labelService: ILabelService,
@IStorageService storageService: IStorageService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IHostService private readonly hostService: IHostService,
@IProductService private readonly productService: IProductService,
) {
@ -143,6 +151,10 @@ export class TitlebarPart extends Part implements ITitleService {
}
}
}
if (this.titleBarStyle !== 'native' && this.windowControls && event.affectsConfiguration('workbench.experimental.layoutControl.enabled')) {
this.windowControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
}
}
protected onMenubarVisibilityChanged(visible: boolean): void {
@ -395,6 +407,53 @@ export class TitlebarPart extends Part implements ITitleService {
this.titleUpdater.schedule();
}
if (this.titleBarStyle !== 'native') {
this.windowControls = append(this.element, $('div.window-controls-container'));
this.windowControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
const layoutDropdownContainer = append(this.windowControls, $('div.layout-dropdown-container'));
this.layoutToolbar = new ActionBar(layoutDropdownContainer,
{
ariaLabel: localize('layoutMenu', "Configure Layout"),
actionViewItemProvider: action => {
if (action instanceof SubmenuAction) {
return new DropdownMenuActionViewItem(action, action.actions, this.contextMenuService, {
classNames: Codicon.editorLayout.classNamesArray,
anchorAlignmentProvider: () => AnchorAlignment.RIGHT,
keybindingProvider: action => this.keybindingService.lookupKeybinding(action.id)
});
}
return undefined;
}
});
const menu = this._register(this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService));
const updateLayoutMenu = () => {
if (!this.layoutToolbar) {
return;
}
const actions: IAction[] = [];
const toDispose = createAndFillInContextMenuActions(menu, undefined, { primary: [], secondary: actions });
this.layoutToolbar.clear();
this.layoutToolbar.push(new SubmenuAction('stenir', localize('layoutMenu', "Configure Layout"), actions.map(action => {
if (action instanceof MenuItemAction) {
(action as IAction).label = mnemonicButtonLabel(typeof action.item.title === 'string'
? action.item.title
: action.item.title.mnemonicTitle ?? action.item.title.value, true);
}
return action;
})));
toDispose.dispose();
};
menu.onDidChange(updateLayoutMenu);
updateLayoutMenu();
}
// Context menu on title
[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
this._register(addDisposableListener(this.title, event, e => {
@ -413,6 +472,10 @@ export class TitlebarPart extends Part implements ITitleService {
return;
}
if (e.target && this.layoutToolbar && isAncestor(e.target as HTMLElement, this.layoutToolbar.getContainer())) {
return;
}
const active = document.activeElement;
setTimeout(() => {
if (active instanceof HTMLElement) {
@ -507,6 +570,10 @@ export class TitlebarPart extends Part implements ITitleService {
return getMenuBarVisibility(this.configurationService);
}
private get layoutControlEnabled(): boolean {
return this.configurationService.getValue<boolean>('workbench.experimental.layoutControl.enabled');
}
updateLayout(dimension: Dimension): void {
this.lastLayoutDimensions = dimension;

View file

@ -356,6 +356,11 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
// On Mac, the delay is 1500.
'default': isMacintosh ? 1500 : 500
},
'workbench.experimental.layoutControl.enabled': {
'type': 'boolean',
'default': product.quality !== 'stable',
'description': localize('layoutControlEnabled', "Controls whether the layout control button in the custom title bar is enabled."),
},
'workbench.experimental.sidePanel.enabled': {
'type': 'boolean',
'default': false,

View file

@ -5,20 +5,20 @@
import { localize } from 'vs/nls';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
class InspectKeyMap extends EditorAction {
class InspectKeyMap extends Action2 {
constructor() {
super({
id: 'workbench.action.inspectKeyMappings',
label: localize('workbench.action.inspectKeyMap', "Developer: Inspect Key Mappings"),
alias: 'Developer: Inspect Key Mappings',
precondition: undefined
title: { value: localize('workbench.action.inspectKeyMap', "Inspect Key Mappings"), original: 'Inspect Key Mappings' },
category: CATEGORIES.Developer,
f1: true
});
}
@ -30,7 +30,7 @@ class InspectKeyMap extends EditorAction {
}
}
registerEditorAction(InspectKeyMap);
registerAction2(InspectKeyMap);
class InspectKeyMapJSON extends Action2 {

View file

@ -727,7 +727,7 @@ registerThemingParticipant((theme, collector) => {
if (debugIconBreakpointCurrentStackframeForegroundColor) {
collector.addRule(`
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.debugStackframe)},
.monaco-editor .debug-top-stack-frame-column::before {
.monaco-editor .debug-top-stack-frame-column {
color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important;
}
`);

View file

@ -17,7 +17,6 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { distinct } from 'vs/base/common/arrays';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { noBreakWhitespace } from 'vs/base/common/strings';
export const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
export const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
@ -82,7 +81,7 @@ export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocuse
options: {
description: 'top-stack-frame-inline-decoration',
before: {
content: noBreakWhitespace,
content: '\uEB8B',
inlineClassName: noCharactersBefore ? 'debug-top-stack-frame-column start-of-line' : 'debug-top-stack-frame-column',
inlineClassNameAffectsLetterSpacing: true
},

View file

@ -574,13 +574,16 @@ class Launch extends AbstractLaunch implements ILaunch {
} catch {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
content = await this.getInitialConfigurationContent(this.workspace.uri, type, token);
if (content) {
created = true; // pin only if config file is created #8727
try {
await this.textFileService.write(resource, content);
} catch (error) {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
}
if (!content) {
// Cancelled
return { editor: null, created: false };
}
created = true; // pin only if config file is created #8727
try {
await this.textFileService.write(resource, content);
} catch (error) {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
}
}

View file

@ -20,12 +20,6 @@
opacity: 0.7;
}
.codicon-debug-breakpoint.codicon-debug-stackframe-focused::after,
.codicon-debug-breakpoint.codicon-debug-stackframe::after {
content: '\eb8a';
position: absolute;
}
.monaco-editor .inline-breakpoint-widget.line-start {
left: -8px !important;
}
@ -38,10 +32,17 @@
}
.monaco-editor .debug-top-stack-frame-column {
font: normal normal normal 16px/1 codicon;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin-left: 0;
margin-right: 4px;
margin-top: -1px; /* TODO @misolori: figure out a way to not use negative margin for alignment */
align-items: center;
width: 0.9em;
display: inline-flex;
vertical-align: middle;
margin-top: -1px; /* TODO @misolori: figure out a way to not use negative margin for alignment */
}
/* Do not push text with inline decoration when decoration on start of line */
@ -49,21 +50,7 @@
position: absolute;
top: 50%;
transform: translate(-17px, -50%);
}
.monaco-editor .debug-top-stack-frame-column {
display: inline-flex;
vertical-align: middle;
}
.monaco-editor .debug-top-stack-frame-column::before {
content: '\eb8b';
font: normal normal normal 16px/1 codicon;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin-left: 0;
margin-right: 4px;
margin-top: 0px; /* TODO @misolori: figure out a way to not use negative margin for alignment */
}
.monaco-editor .inline-breakpoint-widget {

View file

@ -227,12 +227,13 @@ export class ExperimentService extends Disposable implements IExperimentService
}
protected async getExperiments(): Promise<IRawExperiment[] | null> {
if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
const experimentsUrl = this.configurationService.getValue<string>('_workbench.experimentsUrl') || this.productService.experimentsUrl;
if (!experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
return [];
}
try {
const context = await this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None);
const context = await this.requestService.request({ type: 'GET', url: experimentsUrl }, CancellationToken.None);
if (context.res.statusCode !== 200) {
return null;
}

View file

@ -396,6 +396,12 @@
cursor: pointer;
}
.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.extension-editor > .body > .content > .details > .additional-details-container .resources-container > .resources > .resource:hover {
text-decoration: underline;
}

View file

@ -19,8 +19,6 @@ interface OpenersMemento {
[id: string]: RegisteredExternalOpener;
}
/**
*/
export class ContributedExternalUriOpenersStore extends Disposable {
private static readonly STORAGE_ID = 'externalUriOpeners';
@ -37,8 +35,8 @@ export class ContributedExternalUriOpenersStore extends Disposable {
this._memento = new Memento(ContributedExternalUriOpenersStore.STORAGE_ID, storageService);
this._mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
for (const id of Object.keys(this._mementoObject || {})) {
this.add(id, this._mementoObject[id].extensionId, { isCurrentlyRegistered: false });
for (const [id, value] of Object.entries(this._mementoObject || {})) {
this.add(id, value.extensionId, { isCurrentlyRegistered: false });
}
this.invalidateOpenersOnExtensionsChanged();

View file

@ -258,7 +258,12 @@ export class InteractiveEditor extends EditorPane {
cellExecuteToolbar: MenuId.InteractiveCellExecute,
cellExecutePrimary: undefined
},
cellEditorContributions: [],
cellEditorContributions: EditorExtensionsRegistry.getSomeEditorContributions([
SelectionClipboardContributionID,
ContextMenuController.ID,
ModesHoverController.ID,
MarkerController.ID
]),
options: this.#notebookOptions
});
@ -270,6 +275,9 @@ export class InteractiveEditor extends EditorPane {
top: INPUT_EDITOR_PADDING,
bottom: INPUT_EDITOR_PADDING
},
hover: {
enabled: true
}
}
}, {
...{

View file

@ -446,7 +446,7 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction {
constructor() {
super({
id: EXECUTE_CELL_INSERT_BELOW,
precondition: executeThisCellCondition,
precondition: ContextKeyExpr.or(executeThisCellCondition, NOTEBOOK_CELL_TYPE.isEqualTo('markup')),
title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"),
keybinding: {
when: NOTEBOOK_CELL_LIST_FOCUSED,
@ -460,14 +460,17 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction {
const idx = context.notebookEditor.getCellIndex(context.cell);
const modeService = accessor.get(IModeService);
const newFocusMode = context.cell.focusMode === CellFocusMode.Editor ? 'editor' : 'container';
const executionP = runCell(accessor, context);
const newCell = insertCell(modeService, context.notebookEditor, idx, CellKind.Code, 'below');
const newCell = insertCell(modeService, context.notebookEditor, idx, context.cell.cellKind, 'below');
if (newCell) {
context.notebookEditor.focusNotebookCell(newCell, newFocusMode);
}
return executionP;
if (context.cell.cellKind === CellKind.Markup) {
context.cell.updateEditState(CellEditState.Preview, EXECUTE_CELL_INSERT_BELOW);
} else {
runCell(accessor, context);
}
}
});

Some files were not shown because too many files have changed in this diff Show more