diff --git a/.github/classifier.json b/.github/classifier.json
new file mode 100644
index 0000000000..9607d5f137
--- /dev/null
+++ b/.github/classifier.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/master/classifier/apply/apply-labels/classifier-config.schema.json",
+ "assignees": {
+ "JacksonKearl": {
+ "assign": true
+ }
+ },
+ "labels": {
+ "search-editor": {
+ "applyLabel": true,
+ "assign": [
+ "JacksonKearl"
+ ]
+ }
+ }
+}
diff --git a/.github/classifier.yml b/.github/classifier.yml
deleted file mode 100644
index 754726f1a1..0000000000
--- a/.github/classifier.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- perform: true,
- alwaysRequireAssignee: false,
- labelsRequiringAssignee: [],
- defaultLabel: 'Triage: Needed',
- defaultAssignee: '',
- autoAssignees: {
- Area - Acquisition: [],
- Area - Azure: [],
- Area - Backup\Restore: [],
- Area - Charting\Insights: [],
- Area - Connection: [ charles-gagnon ],
- Area - DacFX: [],
- Area - Dashboard: [],
- Area - Data Explorer: [],
- Area - Edit Data: [],
- Area - Extensibility: [],
- Area - External Table: [],
- Area - Fundamentals: [],
- Area - Language Service: [ charles-gagnon ],
- Area - Localization: [],
- Area - Notebooks: [ chlafreniere ],
- Area - Performance: [],
- Area - Query Editor: [ anthonydresser ],
- Area - Query Plan: [],
- Area - Reliability: [],
- Area - Resource Deployment: [],
- Area - Schema Compare: [],
- Area - Shell: [],
- Area - SQL Agent: [],
- Area - SQL Import: [],
- Area - SQL Profiler: [],
- Area - SQL 2019: [],
- Area - SSMS Integration: []
- }
-}
diff --git a/.github/workflows/classifier-apply.yml b/.github/workflows/classifier-apply.yml
new file mode 100644
index 0000000000..80b61b5d31
--- /dev/null
+++ b/.github/workflows/classifier-apply.yml
@@ -0,0 +1,43 @@
+name: "Classifier: Apply"
+on:
+ schedule:
+ - cron: 0,30 * * * *
+
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Actions
+ uses: actions/checkout@v2
+ with:
+ repository: 'microsoft/vscode-github-triage-actions'
+ ref: v15
+ path: ./actions
+ - name: Install Actions
+ run: npm install --production --prefix ./actions
+ - name: Install Additional Dependencies
+ # Pulls in a bunch of other packages that arent needed for the rest of the actions
+ run: npm install @azure/storage-blob@12
+ - name: "Run Classifier: Scraper"
+ uses: ./actions/classifier/apply/fetch-issues
+ with:
+ # slightly overlapping to protect against issues slipping through the cracks if a run is delayed
+ from: 45
+ until: 5
+ blobContainerName: classifier-models
+ blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}}
+ - name: Set up Python 3.7
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.7
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install --upgrade numpy scipy scikit-learn joblib nltk
+ - name: "Run Classifier: Generator"
+ run: python ./actions/classifier/apply/generate-labels/main.py
+ - name: "Run Classifier: Labeler"
+ uses: ./actions/classifier/apply/apply-labels
+ with:
+ config-path: classifier
+ token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index a631078f52..1b823d9c03 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -145,6 +145,25 @@
"order": 2
}
},
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Main Process",
+ "runtimeExecutable": "${workspaceFolder}/scripts/code.sh",
+ "windows": {
+ "runtimeExecutable": "${workspaceFolder}/scripts/code.bat",
+ },
+ "runtimeArgs": [
+ "--no-cached-data"
+ ],
+ "outFiles": [
+ "${workspaceFolder}/out/**/*.js"
+ ],
+ "presentation": {
+ "group": "1_vscode",
+ "order": 1
+ }
+ },
{
"type": "chrome",
"request": "launch",
diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml
index 221ad7c199..553e683255 100644
--- a/build/azure-pipelines/darwin/continuous-build-darwin.yml
+++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml
@@ -2,54 +2,76 @@ steps:
- task: NodeTool@0
inputs:
versionSpec: "12.13.0"
+
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version
inputs:
versionSpec: "1.x"
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
+
- script: |
CHILD_CONCURRENCY=1 yarn --frozen-lockfile
displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- script: |
yarn electron x64
displayName: Download Electron
+
- script: |
yarn gulp hygiene
displayName: Run Hygiene Checks
+
- script: | # {{SQL CARBON EDIT}} add step
yarn strict-vscode
displayName: Run Strict Null Check.
+
# - script: | {{SQL CARBON EDIT}} remove step
# yarn monaco-compile-check
# displayName: Run Monaco Editor Checks
+
- script: |
yarn valid-layers-check
displayName: Run Valid Layers Checks
+
- script: |
yarn compile
displayName: Compile Sources
+
# - script: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions
# displayName: Download Built-in Extensions
+
- script: |
./scripts/test.sh --tfs "Unit Tests"
displayName: Run Unit Tests (Electron)
+
# - script: | {{SQL CARBON EDIT}} disable
# yarn test-browser --browser chromium --browser webkit --browser firefox
# displayName: Run Unit Tests (Browser)
+
# - script: | {{SQL CARBON EDIT}} disable
# ./scripts/test-integration.sh --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron)
+
+- task: PublishPipelineArtifact@0
+ inputs:
+ artifactName: crash-dump-macos
+ targetPath: .build/crashes
+ displayName: 'Publish Crash Reports'
+ condition: succeededOrFailed()
+
- task: PublishTestResults@2
displayName: Publish Tests Results
inputs:
diff --git a/build/azure-pipelines/darwin/entitlements.plist b/build/azure-pipelines/darwin/entitlements.plist
index be8b7163da..6631ffa6f2 100644
--- a/build/azure-pipelines/darwin/entitlements.plist
+++ b/build/azure-pipelines/darwin/entitlements.plist
@@ -2,13 +2,5 @@
- com.apple.security.cs.allow-jit
-
- com.apple.security.cs.allow-unsigned-executable-memory
-
- com.apple.security.cs.disable-library-validation
-
- com.apple.security.cs.allow-dyld-environment-variables
-
diff --git a/build/azure-pipelines/darwin/helper-gpu-entitlements.plist b/build/azure-pipelines/darwin/helper-gpu-entitlements.plist
new file mode 100644
index 0000000000..4efe1ce508
--- /dev/null
+++ b/build/azure-pipelines/darwin/helper-gpu-entitlements.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+
+
diff --git a/build/azure-pipelines/darwin/helper-plugin-entitlements.plist b/build/azure-pipelines/darwin/helper-plugin-entitlements.plist
new file mode 100644
index 0000000000..7cd9df032b
--- /dev/null
+++ b/build/azure-pipelines/darwin/helper-plugin-entitlements.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+
+
diff --git a/build/azure-pipelines/darwin/helper-renderer-entitlements.plist b/build/azure-pipelines/darwin/helper-renderer-entitlements.plist
new file mode 100644
index 0000000000..be8b7163da
--- /dev/null
+++ b/build/azure-pipelines/darwin/helper-renderer-entitlements.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+
+
diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml
index e354e28dc3..175ec6c111 100644
--- a/build/azure-pipelines/darwin/product-build-darwin.yml
+++ b/build/azure-pipelines/darwin/product-build-darwin.yml
@@ -152,15 +152,29 @@ steps:
displayName: Run smoke tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
+- task: PublishPipelineArtifact@0
+ inputs:
+ artifactName: crash-dump-macos
+ targetPath: .build/crashes
+ displayName: 'Publish Crash Reports'
+ condition: succeededOrFailed()
+
- script: |
set -e
+ APP_ROOT=$(agent.builddirectory)/VSCode-darwin
+ APP_NAME="`ls $APP_ROOT | head -n 1`"
+ HELPER_APP_NAME="`echo $APP_NAME | sed -e 's/^Visual Studio //;s/\.app$//'`"
+ APP_FRAMEWORK_PATH="$APP_ROOT/$APP_NAME/Contents/Frameworks"
security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
security default-keychain -s $(agent.tempdirectory)/buildagent.keychain
security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12
security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain
- codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist $(agent.builddirectory)/VSCode-darwin/*.app
+ codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist "$APP_ROOT"/*.app
+ codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-gpu-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (GPU).app"
+ codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-plugin-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Plugin).app"
+ codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-renderer-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Renderer).app"
displayName: Set Hardened Entitlements
- script: |
diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml
index 2e898ac73e..0a692dffdb 100644
--- a/build/azure-pipelines/linux/continuous-build-linux.yml
+++ b/build/azure-pipelines/linux/continuous-build-linux.yml
@@ -7,57 +7,80 @@ steps:
sudo chmod +x /etc/init.d/xvfb
sudo update-rc.d xvfb defaults
sudo service xvfb start
+
- task: NodeTool@0
inputs:
versionSpec: "12.13.0"
+
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
inputs:
versionSpec: "1.x"
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
+
- script: |
CHILD_CONCURRENCY=1 yarn --frozen-lockfile
displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- script: |
yarn electron x64
displayName: Download Electron
+
- script: |
yarn gulp hygiene
displayName: Run Hygiene Checks
+
- script: | # {{SQL CARBON EDIT}} add strict null check
yarn strict-vscode
+
displayName: Run Strict Null Check
# - script: | {{SQL CARBON EDIT}} remove monaco editor checks
# yarn monaco-compile-check
# displayName: Run Monaco Editor Checks
+
- script: |
yarn valid-layers-check
displayName: Run Valid Layers Checks
+
- script: |
yarn compile
displayName: Compile Sources
+
# - script: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions
# displayName: Download Built-in Extensions
+
- script: |
DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests"
displayName: Run Unit Tests (Electron)
+
# - script: | {{SQL CARBON EDIT}} disable
# DISPLAY=:10 yarn test-browser --browser chromium
# displayName: Run Unit Tests (Browser)
+
# - script: | {{SQL CARBON EDIT}} disable
# DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron)
+
+- task: PublishPipelineArtifact@0
+ inputs:
+ artifactName: crash-dump-linux
+ targetPath: .build/crashes
+ displayName: 'Publish Crash Reports'
+ condition: succeededOrFailed()
+
- task: PublishTestResults@2
displayName: Publish Tests Results
inputs:
diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml
index cbe3bf051e..76428b860f 100644
--- a/build/azure-pipelines/linux/product-build-linux.yml
+++ b/build/azure-pipelines/linux/product-build-linux.yml
@@ -140,6 +140,13 @@ steps:
displayName: Run integration tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
+- task: PublishPipelineArtifact@0
+ inputs:
+ artifactName: crash-dump-linux
+ targetPath: .build/crashes
+ displayName: 'Publish Crash Reports'
+ condition: succeededOrFailed()
+
- script: |
set -e
yarn gulp "vscode-linux-x64-build-deb"
diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml
index d0f7037962..27826fdd1f 100644
--- a/build/azure-pipelines/win32/continuous-build-win32.yml
+++ b/build/azure-pipelines/win32/continuous-build-win32.yml
@@ -2,59 +2,83 @@ steps:
- task: NodeTool@0
inputs:
versionSpec: "12.13.0"
+
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version
inputs:
versionSpec: "1.x"
+
- task: UsePythonVersion@0
inputs:
versionSpec: '2.x'
addToPath: true
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
+
- powershell: |
yarn --frozen-lockfile
env:
CHILD_CONCURRENCY: "1"
displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
+
- powershell: |
yarn electron
+ displayName: Download Electron
+
- script: |
yarn gulp hygiene
displayName: Run Hygiene Checks
+
- script: | # {{SQL CARBON EDIT}} add step
yarn strict-vscode
displayName: Run Strict Null Check
+
# - powershell: | {{SQL CARBON EDIT}} remove step
# yarn monaco-compile-check
# displayName: Run Monaco Editor Checks
+
- script: |
yarn valid-layers-check
displayName: Run Valid Layers Checks
+
- powershell: |
yarn compile
displayName: Compile Sources
+
# - powershell: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions
# displayName: Download Built-in Extensions
+
- powershell: |
.\scripts\test.bat --tfs "Unit Tests"
displayName: Run Unit Tests (Electron)
+
# - powershell: | {{SQL CARBON EDIT}} disable
# yarn test-browser --browser chromium --browser firefox
# displayName: Run Unit Tests (Browser)
+
# - powershell: | {{SQL CARBON EDIT}} disable
# .\scripts\test-integration.bat --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron)
+
+- task: PublishPipelineArtifact@0
+ displayName: 'Publish Crash Reports'
+ inputs:
+ artifactName: crash-dump-windows
+ targetPath: .build\crashes
+ condition: succeededOrFailed()
+
- task: PublishTestResults@2
displayName: Publish Tests Results
inputs:
diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml
index c428642454..75dc54e359 100644
--- a/build/azure-pipelines/win32/product-build-win32.yml
+++ b/build/azure-pipelines/win32/product-build-win32.yml
@@ -149,6 +149,13 @@ steps:
displayName: Run integration tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
+- task: PublishPipelineArtifact@0
+ inputs:
+ artifactName: crash-dump-windows-$(VSCODE_ARCH)
+ targetPath: .build\crashes
+ displayName: 'Publish Crash Reports'
+ condition: succeededOrFailed()
+
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
inputs:
ConnectedServiceName: 'ESRP CodeSign'
diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js
index 0c8b5dea26..34b517f76c 100644
--- a/build/lib/treeshaking.js
+++ b/build/lib/treeshaking.js
@@ -430,6 +430,7 @@ function markNodes(languageService, options) {
|| ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]'
+ || memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON'
|| memberName === 'toString'
|| memberName === 'dispose' // TODO: keeping all `dispose` methods
diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts
index b2959ee1eb..80822c4c87 100644
--- a/build/lib/treeshaking.ts
+++ b/build/lib/treeshaking.ts
@@ -548,6 +548,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt
|| ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]'
+ || memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON'
|| memberName === 'toString'
|| memberName === 'dispose'// TODO: keeping all `dispose` methods
@@ -795,8 +796,8 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol |
let symbol = (
ts.isShorthandPropertyAssignment(node)
- ? checker.getShorthandAssignmentValueSymbol(node)
- : checker.getSymbolAtLocation(node)
+ ? checker.getShorthandAssignmentValueSymbol(node)
+ : checker.getSymbolAtLocation(node)
);
let importNode: ts.Declaration | null = null;
diff --git a/extensions/git/package.json b/extensions/git/package.json
index 7c28c7e4b0..ea99da5140 100644
--- a/extensions/git/package.json
+++ b/extensions/git/package.json
@@ -409,6 +409,11 @@
"command": "git.timeline.copyCommitMessage",
"title": "%command.timelineCopyCommitMessage%",
"category": "Git"
+ },
+ {
+ "command": "git.rebaseAbort",
+ "title": "%command.rebaseAbort%",
+ "category": "Git"
}
],
"keybindings": [
diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json
index 3f5269dd51..a735b58aa7 100644
--- a/extensions/git/package.nls.json
+++ b/extensions/git/package.nls.json
@@ -63,6 +63,7 @@
"command.showOutput": "Show Git Output",
"command.ignore": "Add to .gitignore",
"command.revealInExplorer": "Reveal in Side Bar",
+ "command.rebaseAbort": "Abort Rebase",
"command.stashIncludeUntracked": "Stash (Include Untracked)",
"command.stash": "Stash",
"command.stashPop": "Pop Stash...",
diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts
index e31124e15d..4a2ecfa7ab 100644
--- a/extensions/git/src/commands.ts
+++ b/extensions/git/src/commands.ts
@@ -2494,6 +2494,10 @@ export class CommandCenter {
env.clipboard.writeText(item.message);
}
+ @command('git.rebaseAbort', { repository: true })
+ async rebaseAbort(repository: Repository): Promise {
+ await repository.rebaseAbort();
+ }
private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts
index 0dabbcf951..ed8b89ea98 100644
--- a/extensions/git/src/git.ts
+++ b/extensions/git/src/git.ts
@@ -1333,6 +1333,10 @@ export class Repository {
}
}
+ async rebaseAbort(): Promise {
+ await this.run(['rebase', '--abort']);
+ }
+
async rebaseContinue(): Promise {
const args = ['rebase', '--continue'];
diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts
index 3d1f49f78c..ad2cf89802 100644
--- a/extensions/git/src/repository.ts
+++ b/extensions/git/src/repository.ts
@@ -303,6 +303,7 @@ export const enum Operation {
CheckIgnore = 'CheckIgnore',
GetObjectDetails = 'GetObjectDetails',
SubmoduleUpdate = 'SubmoduleUpdate',
+ RebaseAbort = 'RebaseAbort',
RebaseContinue = 'RebaseContinue',
FindTrackingBranches = 'GetTracking',
Apply = 'Apply',
@@ -1331,6 +1332,10 @@ export class Repository implements Disposable {
});
}
+ async rebaseAbort(): Promise {
+ await this.run(Operation.RebaseAbort, async () => await this.repository.rebaseAbort());
+ }
+
checkIgnore(filePaths: string[]): Promise> {
return this.run(Operation.CheckIgnore, () => {
return new Promise>((resolve, reject) => {
diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts
index b7a874b2d9..ebdbf7e661 100644
--- a/extensions/json-language-features/client/src/jsonMain.ts
+++ b/extensions/json-language-features/client/src/jsonMain.ts
@@ -77,6 +77,12 @@ interface JSONSchemaSettings {
schema?: any;
}
+namespace SettingIds {
+ export const enableFormatter = 'json.format.enable';
+ export const enableSchemaDownload = 'json.schemaDownload.enable';
+ export const maxItemsComputed = 'json.maxItemsComputed';
+}
+
let telemetryReporter: TelemetryReporter | undefined;
export function activate(context: ExtensionContext) {
@@ -107,10 +113,8 @@ export function activate(context: ExtensionContext) {
id: 'status.json.resolveError',
name: localize('json.resolveError', "JSON: Schema Resolution Error"),
alignment: StatusBarAlignment.Right,
- priority: 0
+ priority: 0,
});
- schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
- schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema.') + ' ' + localize('json.clickToRetry', 'Click to retry.');
schemaResolutionErrorStatusBarItem.text = '$(alert)';
toDispose.push(schemaResolutionErrorStatusBarItem);
@@ -200,6 +204,7 @@ export function activate(context: ExtensionContext) {
toDispose.push(disposable);
client.onReady().then(() => {
const schemaDocuments: { [uri: string]: boolean } = {};
+ let schemaDownloadEnabled = true;
// handle content request
client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
@@ -208,12 +213,16 @@ export function activate(context: ExtensionContext) {
return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString())));
}
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
- return workspace.openTextDocument(uri).then(doc => {
- schemaDocuments[uri.toString()] = true;
- return doc.getText();
- }, error => {
- return Promise.reject(error);
- });
+ if (schemaDownloadEnabled) {
+ return workspace.openTextDocument(uri).then(doc => {
+ schemaDocuments[uri.toString()] = true;
+ return doc.getText();
+ }, error => {
+ return Promise.reject(error);
+ });
+ } else {
+ return Promise.reject(localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload));
+ }
} else {
if (telemetryReporter && uri.authority === 'schema.management.azure.com') {
/* __GDPR__
@@ -294,16 +303,61 @@ export function activate(context: ExtensionContext) {
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
});
- // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652.
+ // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652.
updateFormatterRegistration();
toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });
- toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration()));
+ updateSchemaDownloadSetting();
+
+ toDispose.push(workspace.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration(SettingIds.enableFormatter)) {
+ updateFormatterRegistration();
+ } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) {
+ updateSchemaDownloadSetting();
+ }
+ }));
client.onNotification(ResultLimitReachedNotification.type, message => {
- window.showInformationMessage(`${message}\nUse setting 'json.maxItemsComputed' to configure the limit.`);
+ window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`);
});
+ function updateFormatterRegistration() {
+ const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter);
+ if (!formatEnabled && rangeFormatting) {
+ rangeFormatting.dispose();
+ rangeFormatting = undefined;
+ } else if (formatEnabled && !rangeFormatting) {
+ rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
+ provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult {
+ const params: DocumentRangeFormattingParams = {
+ textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
+ range: client.code2ProtocolConverter.asRange(range),
+ options: client.code2ProtocolConverter.asFormattingOptions(options)
+ };
+ return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
+ client.protocol2CodeConverter.asTextEdits,
+ (error) => {
+ client.logFailedRequest(DocumentRangeFormattingRequest.type, error);
+ return Promise.resolve([]);
+ }
+ );
+ }
+ });
+ }
+ }
+
+ function updateSchemaDownloadSetting() {
+ schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false;
+ if (schemaDownloadEnabled) {
+ schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.');
+ schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
+ handleRetryResolveSchemaCommand();
+ } else {
+ schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.');
+ schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' };
+ }
+ }
+
});
const languageConfiguration: LanguageConfiguration = {
@@ -316,30 +370,6 @@ export function activate(context: ExtensionContext) {
languages.setLanguageConfiguration('json', languageConfiguration);
languages.setLanguageConfiguration('jsonc', languageConfiguration);
- function updateFormatterRegistration() {
- const formatEnabled = workspace.getConfiguration().get('json.format.enable');
- if (!formatEnabled && rangeFormatting) {
- rangeFormatting.dispose();
- rangeFormatting = undefined;
- } else if (formatEnabled && !rangeFormatting) {
- rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
- provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult {
- const params: DocumentRangeFormattingParams = {
- textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
- range: client.code2ProtocolConverter.asRange(range),
- options: client.code2ProtocolConverter.asFormattingOptions(options)
- };
- return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
- client.protocol2CodeConverter.asTextEdits,
- (error) => {
- client.logFailedRequest(DocumentRangeFormattingRequest.type, error);
- return Promise.resolve([]);
- }
- );
- }
- });
- }
- }
}
@@ -386,7 +416,7 @@ function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[]
function getSettings(): Settings {
const httpSettings = workspace.getConfiguration('http');
- const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000;
+ const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
const settings: Settings = {
http: {
diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json
index d2edb7d54a..2868c0c01a 100644
--- a/extensions/json-language-features/package.json
+++ b/extensions/json-language-features/package.json
@@ -95,7 +95,13 @@
"type": "number",
"default": 5000,
"description": "%json.maxItemsComputed.desc%"
- }
+ },
+ "json.schemaDownload.enable": {
+ "type": "boolean",
+ "default": true,
+ "description": "%json.enableSchemaDownload.desc%",
+ "tags": ["usesOnlineServices"]
+ }
}
},
"configurationDefaults": {
diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json
index 35cf05e2d0..69a8c0eea8 100644
--- a/extensions/json-language-features/package.nls.json
+++ b/extensions/json-language-features/package.nls.json
@@ -12,5 +12,6 @@
"json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.",
"json.schemaResolutionErrorMessage": "Unable to resolve schema.",
"json.clickToRetry": "Click to retry.",
- "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons)."
+ "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).",
+ "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations."
}
diff --git a/extensions/machine-learning/src/test/mainController.test.ts b/extensions/machine-learning/src/test/mainController.test.ts
index 6b7f2b9132..88abbe7cfd 100644
--- a/extensions/machine-learning/src/test/mainController.test.ts
+++ b/extensions/machine-learning/src/test/mainController.test.ts
@@ -90,7 +90,8 @@ function createContext(): TestContext {
storagePath: '',
globalStoragePath: '',
logPath: '',
- extensionUri: vscode.Uri.parse('')
+ extensionUri: vscode.Uri.parse(''),
+ environmentVariableCollection: { } as any
},
outputChannel: {
name: '',
diff --git a/extensions/notebook/src/test/common/stubs.ts b/extensions/notebook/src/test/common/stubs.ts
index 8f577d7f2d..f541416276 100644
--- a/extensions/notebook/src/test/common/stubs.ts
+++ b/extensions/notebook/src/test/common/stubs.ts
@@ -22,6 +22,7 @@ export class MockExtensionContext implements vscode.ExtensionContext {
constructor() {
this.subscriptions = [];
}
+ environmentVariableCollection: vscode.EnvironmentVariableCollection;
}
export class MockOutputChannel implements vscode.OutputChannel {
diff --git a/extensions/sql-database-projects/src/test/testContext.ts b/extensions/sql-database-projects/src/test/testContext.ts
index fd637eef4f..5936784e8d 100644
--- a/extensions/sql-database-projects/src/test/testContext.ts
+++ b/extensions/sql-database-projects/src/test/testContext.ts
@@ -33,7 +33,8 @@ export function createContext(): TestContext {
storagePath: '',
globalStoragePath: '',
logPath: '',
- extensionUri: vscode.Uri.parse('')
+ extensionUri: vscode.Uri.parse(''),
+ environmentVariableCollection: undefined as any
},
};
}
diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js
index 4bd77f37f3..67e33fb9a6 100644
--- a/extensions/theme-seti/build/update-icon-theme.js
+++ b/extensions/theme-seti/build/update-icon-theme.js
@@ -10,8 +10,8 @@ let fs = require('fs');
let https = require('https');
let url = require('url');
-// list of languagesIs not shipped with VSCode. The information is used to associate an icon with a language association
-let nonBuiltInLanguages = { // { fileNames, extensions }
+// list of languagesId not shipped with VSCode. The information is used to associate an icon with a language association
+let nonBuiltInLanguages = { // { fileNames, extensions }
"r": { extensions: ['r', 'rhistory', 'rprofile', 'rt'] },
"argdown": { extensions: ['ad', 'adown', 'argdown', 'argdn'] },
"elm": { extensions: ['elm'] },
@@ -32,10 +32,16 @@ let nonBuiltInLanguages = { // { fileNames, extensions }
"haml": { extensions: ['haml'] },
"stylus": { extensions: ['styl'] },
"vala": { extensions: ['vala'] },
- "todo": { fileNames: ['todo'] },
- "jsonc": { extensions: ['json'] }
+ "todo": { fileNames: ['todo'] }
};
+// list of languagesId that inherit the icon from another language
+let inheritIconFromLanguage = {
+ "jsonc": 'json',
+ "postcss": 'css',
+ "django-html": 'html'
+}
+
let FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo
let font, fontMappingsFile, fileAssociationFile, colorsFile;
@@ -358,6 +364,16 @@ exports.update = function () {
}
}
}
+ for (let lang in inheritIconFromLanguage) {
+ let superLang = inheritIconFromLanguage[lang];
+ let def = lang2Def[superLang];
+ if (def) {
+ lang2Def[lang] = def;
+ } else {
+ console.log('skipping icon def for ' + lang + ': no icon for ' + superLang + ' defined');
+ }
+
+ }
return download(colorsFile).then(function (content) {
diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json
index 5ee7c0f5d5..a3df8c6b32 100644
--- a/extensions/theme-seti/cgmanifest.json
+++ b/extensions/theme-seti/cgmanifest.json
@@ -6,7 +6,7 @@
"git": {
"name": "seti-ui",
"repositoryUrl": "https://github.com/jesseweed/seti-ui",
- "commitHash": "4b3e0a3d0ca8999430bc3aa9f2c8324e6922b3de"
+ "commitHash": "8f22764c37feb7f706465f5186132111a2401b6b"
}
},
"version": "0.1.0"
diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff
index 85f6ee76a5..b4b8366f16 100644
Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ
diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json
index eed503fcc0..16de3aceb1 100644
--- a/extensions/theme-seti/icons/vs-seti-icon-theme.json
+++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json
@@ -966,399 +966,413 @@
"fontCharacter": "\\E06C",
"fontColor": "#519aba"
},
- "_pug_light": {
+ "_prolog_light": {
"fontCharacter": "\\E06E",
+ "fontColor": "#cc6d2e"
+ },
+ "_prolog": {
+ "fontCharacter": "\\E06E",
+ "fontColor": "#e37933"
+ },
+ "_pug_light": {
+ "fontCharacter": "\\E06F",
"fontColor": "#b8383d"
},
"_pug": {
- "fontCharacter": "\\E06E",
+ "fontCharacter": "\\E06F",
"fontColor": "#cc3e44"
},
"_puppet_light": {
- "fontCharacter": "\\E06F",
+ "fontCharacter": "\\E070",
"fontColor": "#b7b73b"
},
"_puppet": {
- "fontCharacter": "\\E06F",
+ "fontCharacter": "\\E070",
"fontColor": "#cbcb41"
},
"_python_light": {
- "fontCharacter": "\\E070",
+ "fontCharacter": "\\E071",
"fontColor": "#498ba7"
},
"_python": {
- "fontCharacter": "\\E070",
+ "fontCharacter": "\\E071",
"fontColor": "#519aba"
},
"_react_light": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#498ba7"
},
"_react": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#519aba"
},
"_react_1_light": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#cc6d2e"
},
"_react_1": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#e37933"
},
"_react_2_light": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#b7b73b"
},
"_react_2": {
- "fontCharacter": "\\E072",
+ "fontCharacter": "\\E073",
"fontColor": "#cbcb41"
},
"_reasonml_light": {
- "fontCharacter": "\\E073",
+ "fontCharacter": "\\E074",
"fontColor": "#b8383d"
},
"_reasonml": {
- "fontCharacter": "\\E073",
+ "fontCharacter": "\\E074",
"fontColor": "#cc3e44"
},
+ "_rmd_light": {
+ "fontColor": "#498ba7"
+ },
+ "_rmd": {
+ "fontColor": "#519aba"
+ },
"_rollup_light": {
- "fontCharacter": "\\E074",
+ "fontCharacter": "\\E075",
"fontColor": "#b8383d"
},
"_rollup": {
- "fontCharacter": "\\E074",
+ "fontCharacter": "\\E075",
"fontColor": "#cc3e44"
},
"_ruby_light": {
- "fontCharacter": "\\E075",
+ "fontCharacter": "\\E076",
"fontColor": "#b8383d"
},
"_ruby": {
- "fontCharacter": "\\E075",
+ "fontCharacter": "\\E076",
"fontColor": "#cc3e44"
},
"_rust_light": {
- "fontCharacter": "\\E076",
+ "fontCharacter": "\\E077",
"fontColor": "#627379"
},
"_rust": {
- "fontCharacter": "\\E076",
+ "fontCharacter": "\\E077",
"fontColor": "#6d8086"
},
"_salesforce_light": {
- "fontCharacter": "\\E077",
+ "fontCharacter": "\\E078",
"fontColor": "#498ba7"
},
"_salesforce": {
- "fontCharacter": "\\E077",
+ "fontCharacter": "\\E078",
"fontColor": "#519aba"
},
"_sass_light": {
- "fontCharacter": "\\E078",
+ "fontCharacter": "\\E079",
"fontColor": "#dd4b78"
},
"_sass": {
- "fontCharacter": "\\E078",
+ "fontCharacter": "\\E079",
"fontColor": "#f55385"
},
"_sbt_light": {
- "fontCharacter": "\\E079",
+ "fontCharacter": "\\E07A",
"fontColor": "#498ba7"
},
"_sbt": {
- "fontCharacter": "\\E079",
+ "fontCharacter": "\\E07A",
"fontColor": "#519aba"
},
"_scala_light": {
- "fontCharacter": "\\E07A",
+ "fontCharacter": "\\E07B",
"fontColor": "#b8383d"
},
"_scala": {
- "fontCharacter": "\\E07A",
+ "fontCharacter": "\\E07B",
"fontColor": "#cc3e44"
},
"_shell_light": {
- "fontCharacter": "\\E07D",
+ "fontCharacter": "\\E07E",
"fontColor": "#455155"
},
"_shell": {
- "fontCharacter": "\\E07D",
+ "fontCharacter": "\\E07E",
"fontColor": "#4d5a5e"
},
"_slim_light": {
- "fontCharacter": "\\E07E",
+ "fontCharacter": "\\E07F",
"fontColor": "#cc6d2e"
},
"_slim": {
- "fontCharacter": "\\E07E",
+ "fontCharacter": "\\E07F",
"fontColor": "#e37933"
},
"_smarty_light": {
- "fontCharacter": "\\E07F",
+ "fontCharacter": "\\E080",
"fontColor": "#b7b73b"
},
"_smarty": {
- "fontCharacter": "\\E07F",
+ "fontCharacter": "\\E080",
"fontColor": "#cbcb41"
},
"_spring_light": {
- "fontCharacter": "\\E080",
+ "fontCharacter": "\\E081",
"fontColor": "#7fae42"
},
"_spring": {
- "fontCharacter": "\\E080",
+ "fontCharacter": "\\E081",
"fontColor": "#8dc149"
},
"_stylelint_light": {
- "fontCharacter": "\\E081",
+ "fontCharacter": "\\E082",
"fontColor": "#bfc2c1"
},
"_stylelint": {
- "fontCharacter": "\\E081",
+ "fontCharacter": "\\E082",
"fontColor": "#d4d7d6"
},
"_stylelint_1_light": {
- "fontCharacter": "\\E081",
+ "fontCharacter": "\\E082",
"fontColor": "#455155"
},
"_stylelint_1": {
- "fontCharacter": "\\E081",
+ "fontCharacter": "\\E082",
"fontColor": "#4d5a5e"
},
"_stylus_light": {
- "fontCharacter": "\\E082",
+ "fontCharacter": "\\E083",
"fontColor": "#7fae42"
},
"_stylus": {
- "fontCharacter": "\\E082",
+ "fontCharacter": "\\E083",
"fontColor": "#8dc149"
},
"_sublime_light": {
- "fontCharacter": "\\E083",
+ "fontCharacter": "\\E084",
"fontColor": "#cc6d2e"
},
"_sublime": {
- "fontCharacter": "\\E083",
+ "fontCharacter": "\\E084",
"fontColor": "#e37933"
},
"_svg_light": {
- "fontCharacter": "\\E084",
+ "fontCharacter": "\\E085",
"fontColor": "#9068b0"
},
"_svg": {
- "fontCharacter": "\\E084",
+ "fontCharacter": "\\E085",
"fontColor": "#a074c4"
},
"_svg_1_light": {
- "fontCharacter": "\\E084",
+ "fontCharacter": "\\E085",
"fontColor": "#498ba7"
},
"_svg_1": {
- "fontCharacter": "\\E084",
+ "fontCharacter": "\\E085",
"fontColor": "#519aba"
},
"_swift_light": {
- "fontCharacter": "\\E085",
+ "fontCharacter": "\\E086",
"fontColor": "#cc6d2e"
},
"_swift": {
- "fontCharacter": "\\E085",
+ "fontCharacter": "\\E086",
"fontColor": "#e37933"
},
"_terraform_light": {
- "fontCharacter": "\\E086",
+ "fontCharacter": "\\E087",
"fontColor": "#9068b0"
},
"_terraform": {
- "fontCharacter": "\\E086",
+ "fontCharacter": "\\E087",
"fontColor": "#a074c4"
},
"_tex_light": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#498ba7"
},
"_tex": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#519aba"
},
"_tex_1_light": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#b7b73b"
},
"_tex_1": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#cbcb41"
},
"_tex_2_light": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#cc6d2e"
},
"_tex_2": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#e37933"
},
"_tex_3_light": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#bfc2c1"
},
"_tex_3": {
- "fontCharacter": "\\E087",
+ "fontCharacter": "\\E088",
"fontColor": "#d4d7d6"
},
"_todo": {
- "fontCharacter": "\\E089"
+ "fontCharacter": "\\E08A"
},
"_tsconfig_light": {
- "fontCharacter": "\\E08A",
+ "fontCharacter": "\\E08B",
"fontColor": "#498ba7"
},
"_tsconfig": {
- "fontCharacter": "\\E08A",
+ "fontCharacter": "\\E08B",
"fontColor": "#519aba"
},
"_twig_light": {
- "fontCharacter": "\\E08B",
+ "fontCharacter": "\\E08C",
"fontColor": "#7fae42"
},
"_twig": {
- "fontCharacter": "\\E08B",
+ "fontCharacter": "\\E08C",
"fontColor": "#8dc149"
},
"_typescript_light": {
- "fontCharacter": "\\E08C",
+ "fontCharacter": "\\E08D",
"fontColor": "#498ba7"
},
"_typescript": {
- "fontCharacter": "\\E08C",
+ "fontCharacter": "\\E08D",
"fontColor": "#519aba"
},
"_typescript_1_light": {
- "fontCharacter": "\\E08C",
+ "fontCharacter": "\\E08D",
"fontColor": "#b7b73b"
},
"_typescript_1": {
- "fontCharacter": "\\E08C",
+ "fontCharacter": "\\E08D",
"fontColor": "#cbcb41"
},
"_vala_light": {
- "fontCharacter": "\\E08D",
+ "fontCharacter": "\\E08E",
"fontColor": "#627379"
},
"_vala": {
- "fontCharacter": "\\E08D",
+ "fontCharacter": "\\E08E",
"fontColor": "#6d8086"
},
"_video_light": {
- "fontCharacter": "\\E08E",
+ "fontCharacter": "\\E08F",
"fontColor": "#dd4b78"
},
"_video": {
- "fontCharacter": "\\E08E",
+ "fontCharacter": "\\E08F",
"fontColor": "#f55385"
},
"_vue_light": {
- "fontCharacter": "\\E08F",
+ "fontCharacter": "\\E090",
"fontColor": "#7fae42"
},
"_vue": {
- "fontCharacter": "\\E08F",
+ "fontCharacter": "\\E090",
"fontColor": "#8dc149"
},
"_wasm_light": {
- "fontCharacter": "\\E090",
+ "fontCharacter": "\\E091",
"fontColor": "#9068b0"
},
"_wasm": {
- "fontCharacter": "\\E090",
+ "fontCharacter": "\\E091",
"fontColor": "#a074c4"
},
"_wat_light": {
- "fontCharacter": "\\E091",
+ "fontCharacter": "\\E092",
"fontColor": "#9068b0"
},
"_wat": {
- "fontCharacter": "\\E091",
+ "fontCharacter": "\\E092",
"fontColor": "#a074c4"
},
"_webpack_light": {
- "fontCharacter": "\\E092",
+ "fontCharacter": "\\E093",
"fontColor": "#498ba7"
},
"_webpack": {
- "fontCharacter": "\\E092",
+ "fontCharacter": "\\E093",
"fontColor": "#519aba"
},
"_wgt_light": {
- "fontCharacter": "\\E093",
+ "fontCharacter": "\\E094",
"fontColor": "#498ba7"
},
"_wgt": {
- "fontCharacter": "\\E093",
+ "fontCharacter": "\\E094",
"fontColor": "#519aba"
},
"_windows_light": {
- "fontCharacter": "\\E094",
+ "fontCharacter": "\\E095",
"fontColor": "#498ba7"
},
"_windows": {
- "fontCharacter": "\\E094",
+ "fontCharacter": "\\E095",
"fontColor": "#519aba"
},
"_word_light": {
- "fontCharacter": "\\E095",
+ "fontCharacter": "\\E096",
"fontColor": "#498ba7"
},
"_word": {
- "fontCharacter": "\\E095",
+ "fontCharacter": "\\E096",
"fontColor": "#519aba"
},
"_xls_light": {
- "fontCharacter": "\\E096",
+ "fontCharacter": "\\E097",
"fontColor": "#7fae42"
},
"_xls": {
- "fontCharacter": "\\E096",
+ "fontCharacter": "\\E097",
"fontColor": "#8dc149"
},
"_xml_light": {
- "fontCharacter": "\\E097",
+ "fontCharacter": "\\E098",
"fontColor": "#cc6d2e"
},
"_xml": {
- "fontCharacter": "\\E097",
+ "fontCharacter": "\\E098",
"fontColor": "#e37933"
},
"_yarn_light": {
- "fontCharacter": "\\E098",
+ "fontCharacter": "\\E099",
"fontColor": "#498ba7"
},
"_yarn": {
- "fontCharacter": "\\E098",
+ "fontCharacter": "\\E099",
"fontColor": "#519aba"
},
"_yml_light": {
- "fontCharacter": "\\E099",
+ "fontCharacter": "\\E09A",
"fontColor": "#9068b0"
},
"_yml": {
- "fontCharacter": "\\E099",
+ "fontCharacter": "\\E09A",
"fontColor": "#a074c4"
},
"_zip_light": {
- "fontCharacter": "\\E09A",
+ "fontCharacter": "\\E09B",
"fontColor": "#b8383d"
},
"_zip": {
- "fontCharacter": "\\E09A",
+ "fontCharacter": "\\E09B",
"fontColor": "#cc3e44"
},
"_zip_1_light": {
- "fontCharacter": "\\E09A",
+ "fontCharacter": "\\E09B",
"fontColor": "#627379"
},
"_zip_1": {
- "fontCharacter": "\\E09A",
+ "fontCharacter": "\\E09B",
"fontColor": "#6d8086"
},
// {{SQL CARBON EDIT}}
@@ -1392,6 +1406,7 @@
"edn": "_clojure_1",
"cfc": "_coldfusion",
"cfm": "_coldfusion",
+ "litcoffee": "_coffee",
"config": "_config",
"cfg": "_config",
"conf": "_config",
@@ -1434,6 +1449,7 @@
"hxml": "_haxe_3",
"class": "_java",
"classpath": "_java",
+ "properties": "_java",
"js.map": "_javascript",
"spec.js": "_javascript_1",
"test.js": "_javascript_1",
@@ -1480,6 +1496,7 @@
"test.tsx": "_react_2",
"re": "_reasonml",
"r": "_R",
+ "rmd": "_rmd",
"erb": "_html_erb",
"erb.html": "_html_erb",
"html.erb": "_html_erb",
@@ -1507,6 +1524,7 @@
"vue": "_vue",
"wasm": "_wasm",
"wat": "_wat",
+ "pro": "_prolog",
"jar": "_zip",
"zip": "_zip_1",
"wgt": "_wgt",
@@ -1545,6 +1563,8 @@
"obj": "_svg_1",
"dae": "_svg_1",
"babelrc": "_babel",
+ "babelrc.js": "_babel",
+ "babelrc.cjs": "_babel",
"bowerrc": "_bower",
"dockerignore": "_docker_1",
"codeclimate.yml": "_code-climate",
@@ -1588,12 +1608,14 @@
"version.md": "_clock",
"version": "_clock",
"mvnw": "_maven",
- "tsconfig.json": "_tsconfig",
"swagger.json": "_json_1",
"swagger.yml": "_json_1",
"swagger.yaml": "_json_1",
"mime.types": "_config",
"jenkinsfile": "_jenkins",
+ "babel.config.js": "_babel",
+ "babel.config.json": "_babel",
+ "babel.config.cjs": "_babel",
"bower.json": "_bower",
"docker-healthcheck": "_docker_2",
"docker-compose.yml": "_docker_3",
@@ -1606,6 +1628,7 @@
"gruntfile.babel.js": "_grunt",
"gruntfile.coffee": "_grunt",
"gulpfile": "_gulp",
+ "gulpfile.js": "_gulp",
"ionic.config.json": "_ionic",
"ionic.project": "_ionic",
"platformio.ini": "_platformio",
@@ -1649,7 +1672,6 @@
"groovy": "_grails",
"handlebars": "_mustache",
"html": "_html_3",
- "properties": "_java",
"java": "_java",
"javascriptreact": "_react",
"javascript": "_javascript",
@@ -1699,7 +1721,9 @@
"todo": "_todo",
// {{SQL CARBON EDIT}}
"notebook": "notebook_dark",
- "scmp": "scmp_dark"
+ "scmp": "scmp_dark",
+ "postcss": "_css",
+ "django-html": "_html_3"
},
"light": {
"file": "_default_light",
@@ -1719,6 +1743,7 @@
"edn": "_clojure_1_light",
"cfc": "_coldfusion_light",
"cfm": "_coldfusion_light",
+ "litcoffee": "_coffee_light",
"config": "_config_light",
"cfg": "_config_light",
"conf": "_config_light",
@@ -1761,6 +1786,7 @@
"hxml": "_haxe_3_light",
"class": "_java_light",
"classpath": "_java_light",
+ "properties": "_java_light",
"js.map": "_javascript_light",
"spec.js": "_javascript_1_light",
"test.js": "_javascript_1_light",
@@ -1807,6 +1833,7 @@
"test.tsx": "_react_2_light",
"re": "_reasonml_light",
"r": "_R_light",
+ "rmd": "_rmd_light",
"erb": "_html_erb_light",
"erb.html": "_html_erb_light",
"html.erb": "_html_erb_light",
@@ -1834,6 +1861,7 @@
"vue": "_vue_light",
"wasm": "_wasm_light",
"wat": "_wat_light",
+ "pro": "_prolog_light",
"jar": "_zip_light",
"zip": "_zip_1_light",
"wgt": "_wgt_light",
@@ -1872,6 +1900,8 @@
"obj": "_svg_1_light",
"dae": "_svg_1_light",
"babelrc": "_babel_light",
+ "babelrc.js": "_babel_light",
+ "babelrc.cjs": "_babel_light",
"bowerrc": "_bower_light",
"dockerignore": "_docker_1_light",
"codeclimate.yml": "_code-climate_light",
@@ -1919,7 +1949,6 @@
"groovy": "_grails_light",
"handlebars": "_mustache_light",
"html": "_html_3_light",
- "properties": "_java_light",
"java": "_java_light",
"javascriptreact": "_react_light",
"javascript": "_javascript_light",
@@ -1968,7 +1997,9 @@
"vala": "_vala_light",
// {{SQL CARBON EDIT}}
"notebook": "notebook",
- "scmp": "scmp"
+ "scmp": "scmp",
+ "postcss": "_css_light",
+ "django-html": "_html_3_light"
},
"fileNames": {
"mix": "_hex_light",
@@ -1981,12 +2012,14 @@
"version.md": "_clock_light",
"version": "_clock_light",
"mvnw": "_maven_light",
- "tsconfig.json": "_tsconfig_light",
"swagger.json": "_json_1_light",
"swagger.yml": "_json_1_light",
"swagger.yaml": "_json_1_light",
"mime.types": "_config_light",
"jenkinsfile": "_jenkins_light",
+ "babel.config.js": "_babel_light",
+ "babel.config.json": "_babel_light",
+ "babel.config.cjs": "_babel_light",
"bower.json": "_bower_light",
"docker-healthcheck": "_docker_2_light",
"docker-compose.yml": "_docker_3_light",
@@ -1999,6 +2032,7 @@
"gruntfile.babel.js": "_grunt_light",
"gruntfile.coffee": "_grunt_light",
"gulpfile": "_gulp_light",
+ "gulpfile.js": "_gulp_light",
"ionic.config.json": "_ionic_light",
"ionic.project": "_ionic_light",
"platformio.ini": "_platformio_light",
@@ -2027,5 +2061,5 @@
"Schema Compare": "scmp"
}
},
- "version": "https://github.com/jesseweed/seti-ui/commit/4b3e0a3d0ca8999430bc3aa9f2c8324e6922b3de"
-}
\ No newline at end of file
+ "version": "https://github.com/jesseweed/seti-ui/commit/8f22764c37feb7f706465f5186132111a2401b6b"
+}
diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts
index 623c4926d8..c5b4046a97 100644
--- a/extensions/vscode-account/src/AADHelper.ts
+++ b/extensions/vscode-account/src/AADHelper.ts
@@ -317,6 +317,10 @@ export class AzureActiveDirectoryService {
}
private getCallbackEnvironment(callbackUri: vscode.Uri): string {
+ if (callbackUri.authority.endsWith('.workspaces.github.com')) {
+ return `${callbackUri.authority},`;
+ }
+
switch (callbackUri.authority) {
case 'online.visualstudio.com':
return 'vso,';
diff --git a/package.json b/package.json
index 9668eff395..4a00aef3d6 100644
--- a/package.json
+++ b/package.json
@@ -76,15 +76,15 @@
"sudo-prompt": "9.1.1",
"v8-inspect-profiler": "^0.0.20",
"vscode-nsfw": "1.2.8",
- "vscode-oniguruma": "1.3.0",
+ "vscode-oniguruma": "1.3.1",
"vscode-proxy-agent": "^0.5.2",
"vscode-ripgrep": "^1.5.8",
"vscode-sqlite3": "4.0.10",
"vscode-textmate": "5.1.1",
- "xterm": "4.6.0-beta.38",
+ "xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
- "xterm-addon-web-links": "0.4.0-beta.5",
+ "xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10",
"yauzl": "^2.9.2",
"yazl": "^2.4.3",
diff --git a/remote/package.json b/remote/package.json
index 26faf6ed76..3e8943349a 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -34,14 +34,14 @@
"slickgrid": "github:anthonydresser/SlickGrid#2.3.33",
"spdlog": "^0.11.1",
"vscode-nsfw": "1.2.8",
- "vscode-oniguruma": "1.3.0",
+ "vscode-oniguruma": "1.3.1",
"vscode-proxy-agent": "^0.5.2",
"vscode-ripgrep": "^1.5.8",
"vscode-textmate": "5.1.1",
- "xterm": "4.6.0-beta.38",
+ "xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
- "xterm-addon-web-links": "0.4.0-beta.5",
+ "xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10",
"yauzl": "^2.9.2",
"yazl": "^2.4.3",
diff --git a/remote/web/package.json b/remote/web/package.json
index 0f2da3630b..c5ab0ad2d0 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -21,12 +21,12 @@
"sanitize-html": "^1.19.1",
"semver-umd": "^5.5.6",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.33",
- "vscode-oniguruma": "1.3.0",
+ "vscode-oniguruma": "1.3.1",
"vscode-textmate": "5.1.1",
- "xterm": "4.6.0-beta.38",
+ "xterm": "4.6.0-beta.44",
"xterm-addon-search": "0.7.0-beta.2",
"xterm-addon-unicode11": "0.2.0-beta.5",
- "xterm-addon-web-links": "0.4.0-beta.5",
+ "xterm-addon-web-links": "0.4.0-beta.6",
"xterm-addon-webgl": "0.7.0-beta.10",
"zone.js": "^0.8.4"
}
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index d11c030bb4..ba75b1f9cd 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -338,10 +338,10 @@ util-deprecate@^1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-vscode-oniguruma@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.0.tgz#6788a9db2f8b0781243b4eb8c7a1dd25f6c0e2c8"
- integrity sha512-m4Br19v6XD4MRbVrgsLNSZgQrBzk1BCMCleL8+GrcoGxKEJJd62zOFcTaoQR3hCrSlLqoxWmJ7Cc0VieVV3iTQ==
+vscode-oniguruma@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f"
+ integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ==
vscode-textmate@5.1.1:
version "5.1.1"
@@ -363,20 +363,20 @@ xterm-addon-unicode11@0.2.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298"
integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q==
-xterm-addon-web-links@0.4.0-beta.5:
- version "0.4.0-beta.5"
- resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8"
- integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ==
+xterm-addon-web-links@0.4.0-beta.6:
+ version "0.4.0-beta.6"
+ resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.6.tgz#d159d4542eb9a02d57977fe7eb5f42f8ef2f27fa"
+ integrity sha512-dsQVD/EyVq8PtAYGh2PGQTCt009UipIfX6Q2SBDlz+W9x7IkXjhRxRaryMmLsBCca20qeVKwmbQ+ANhLi+nTaQ==
xterm-addon-webgl@0.7.0-beta.10:
version "0.7.0-beta.10"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4"
integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg==
-xterm@4.6.0-beta.38:
- version "4.6.0-beta.38"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7"
- integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg==
+xterm@4.6.0-beta.44:
+ version "4.6.0-beta.44"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.44.tgz#76b2a6b8e147595ab44aa752c0e721d935464615"
+ integrity sha512-vYtfz4spFcSKLEUpC6anH7TwDams71+k2wAtUzCJ47dNL2IrwYafcFsvGPm46QLTtq4M2Bp9rQo3R3V746yxNg==
zone.js@^0.8.4:
version "0.8.29"
diff --git a/remote/yarn.lock b/remote/yarn.lock
index 0d63dcfbbc..f5da6262f7 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -698,10 +698,10 @@ vscode-nsfw@1.2.8:
lodash.isundefined "^3.0.1"
nan "^2.10.0"
-vscode-oniguruma@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.0.tgz#6788a9db2f8b0781243b4eb8c7a1dd25f6c0e2c8"
- integrity sha512-m4Br19v6XD4MRbVrgsLNSZgQrBzk1BCMCleL8+GrcoGxKEJJd62zOFcTaoQR3hCrSlLqoxWmJ7Cc0VieVV3iTQ==
+vscode-oniguruma@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f"
+ integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ==
vscode-proxy-agent@^0.5.2:
version "0.5.2"
@@ -750,20 +750,20 @@ xterm-addon-unicode11@0.2.0-beta.5:
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0-beta.5.tgz#5961850162df20b5e966166423cd6957ac2db298"
integrity sha512-IjnbBcyfS5JgJDXPO0W2nk/VBtGwx6GWE2snMC676z4DmAABUqPXfTzJKfUoWqoT6UcbxB0oIjDzykCfoRJp6Q==
-xterm-addon-web-links@0.4.0-beta.5:
- version "0.4.0-beta.5"
- resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.5.tgz#523fd0a1c5668370d73e05019ed16eaf596894c8"
- integrity sha512-Qe0idPpSokCNvGrthSBjdrOZrsgXwnLYbzuv0JoEec/A9HVcxKmZ+ktw7fOA2gT/zbcwtrA5FWrir3GlRHglCQ==
+xterm-addon-web-links@0.4.0-beta.6:
+ version "0.4.0-beta.6"
+ resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0-beta.6.tgz#d159d4542eb9a02d57977fe7eb5f42f8ef2f27fa"
+ integrity sha512-dsQVD/EyVq8PtAYGh2PGQTCt009UipIfX6Q2SBDlz+W9x7IkXjhRxRaryMmLsBCca20qeVKwmbQ+ANhLi+nTaQ==
xterm-addon-webgl@0.7.0-beta.10:
version "0.7.0-beta.10"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.10.tgz#39fdb96351e97a1bf15f4c4c8944ba3d05cacee4"
integrity sha512-nQl/ASk+ck11aSrBZXb2a0tu+SNDnm89owBk/sAZeZzi5MHNo6bB8y2VTKNNC6D3i3aFouTz4VorYB25LUgNFg==
-xterm@4.6.0-beta.38:
- version "4.6.0-beta.38"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.38.tgz#8472b168941500c3071aba482c2b5c6040951ec7"
- integrity sha512-Q+nOalMD1MDGOqXdtkGZmOQqbSBU+71vhlX2RBwQoSpJa1QBrKDAhSlN/J+/XvouvVEtCiEFDeacF4EufMEIMg==
+xterm@4.6.0-beta.44:
+ version "4.6.0-beta.44"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.44.tgz#76b2a6b8e147595ab44aa752c0e721d935464615"
+ integrity sha512-vYtfz4spFcSKLEUpC6anH7TwDams71+k2wAtUzCJ47dNL2IrwYafcFsvGPm46QLTtq4M2Bp9rQo3R3V746yxNg==
yauzl@^2.9.2:
version "2.10.0"
diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh
index 96aa59e3d8..91e4d79d27 100755
--- a/resources/linux/bin/code.sh
+++ b/resources/linux/bin/code.sh
@@ -9,6 +9,7 @@ if grep -qi Microsoft /proc/version && [ -z "$DONT_PROMPT_WSL_INSTALL" ]; then
read -e -p "Do you want to continue anyways ? [y/N] " YN
[[ $YN == "n" || $YN == "N" || $YN == "" ]] && exit 1
+ echo "To no longer see this prompt, start @@PRODNAME@@ with the environment variable DONT_PROMPT_WSL_INSTALL defined."
fi
diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat
index ae9d1c08d3..c07b93569c 100755
--- a/scripts/test-integration.bat
+++ b/scripts/test-integration.bat
@@ -4,14 +4,16 @@ setlocal
pushd %~dp0\..
set VSCODEUSERDATADIR=%TEMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,2%
+set VSCODECRASHDIR=%~dp0\..\.build\crashes
:: Figure out which Electron to use for running tests
if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
- :: Run out of sources: no need to compile as code.sh takes care of it
+ :: Run out of sources: no need to compile as code.bat takes care of it
chcp 65001
set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat
set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1
+ echo Storing crash reports into '%VSCODECRASHDIR%'.
echo Running integration tests out of sources.
) else (
:: Run from a built: need to compile all test extensions
@@ -29,8 +31,8 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
:: Configuration for more verbose output
set VSCODE_CLI=1
set ELECTRON_ENABLE_LOGGING=1
- set ELECTRON_ENABLE_STACK_DUMPING=1
+ echo Storing crash reports into '%VSCODECRASHDIR%'.
echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build.
)
@@ -41,19 +43,19 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
:: Tests in the extension host
-REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
+REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
REM if %errorlevel% neq 0 exit /b %errorlevel%
-REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
+REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
REM if %errorlevel% neq 0 exit /b %errorlevel%
-REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
+REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
REM if %errorlevel% neq 0 exit /b %errorlevel%
-REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\markdown-language-features\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% .
+REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\markdown-language-features\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% .
REM if %errorlevel% neq 0 exit /b %errorlevel%
-REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% .
+REM call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\out\test\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% .
REM if %errorlevel% neq 0 exit /b %errorlevel%
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
@@ -62,7 +64,7 @@ if %errorlevel% neq 0 exit /b %errorlevel%
for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i
set GITWORKSPACE=%TEMPDIR%\git-%RANDOM%
mkdir %GITWORKSPACE%
-call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
+call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --enable-proposed-api=vscode.git --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
if %errorlevel% neq 0 exit /b %errorlevel%
:: Tests in commonJS (HTML, CSS, JSON language server tests...)
diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh
index 2a1b62a28a..20e9d20d2c 100755
--- a/scripts/test-integration.sh
+++ b/scripts/test-integration.sh
@@ -10,6 +10,7 @@ else
fi
VSCODEUSERDATADIR=`mktemp -d 2>/dev/null`
+VSCODECRASHDIR=$ROOT/.build/crashes
cd $ROOT
# Figure out which Electron to use for running tests
@@ -18,6 +19,7 @@ then
# Run out of sources: no need to compile as code.sh takes care of it
INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh"
+ echo "Storing crash reports into '$VSCODECRASHDIR'."
echo "Running integration tests out of sources."
else
# Run from a built: need to compile all test extensions
@@ -38,6 +40,7 @@ else
export ELECTRON_ENABLE_STACK_DUMPING=1
export ELECTRON_ENABLE_LOGGING=1
+ echo "Storing crash reports into '$VSCODECRASHDIR'."
echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build."
fi
@@ -45,13 +48,13 @@ fi
./scripts/test.sh --runGlob **/*.integrationTest.js "$@"
# Tests in the extension host
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
-# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
+# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR
diff --git a/src/main.js b/src/main.js
index f02b10901e..4f31e22afb 100644
--- a/src/main.js
+++ b/src/main.js
@@ -39,6 +39,25 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}}
const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);
+// Set temp directory based on crash-reporter-directory CLI argument
+// The crash reporter will store crashes in temp folder so we need
+// to change that location accordingly.
+let crashReporterDirectory = args['crash-reporter-directory'];
+if (crashReporterDirectory) {
+ crashReporterDirectory = path.normalize(crashReporterDirectory);
+
+ if (!fs.existsSync(crashReporterDirectory)) {
+ try {
+ fs.mkdirSync(crashReporterDirectory);
+ } catch (error) {
+ console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`);
+ app.exit(1);
+ }
+ }
+ console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`);
+ app.setPath('temp', crashReporterDirectory);
+}
+
// Set logs path before app 'ready' event if running portable
// to ensure that no 'logs' folder is created on disk at a
// location outside of the portable directory
@@ -336,7 +355,8 @@ function parseCLIArgs() {
'user-data-dir',
'locale',
'js-flags',
- 'max-memory'
+ 'max-memory',
+ 'crash-reporter-directory'
]
});
}
diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts
index 38412c1358..da7b37b0e0 100644
--- a/src/vs/base/browser/ui/actionbar/actionbar.ts
+++ b/src/vs/base/browser/ui/actionbar/actionbar.ts
@@ -477,27 +477,27 @@ export class ActionBar extends Disposable implements IActionRunner {
DOM.addClass(this.domNode, 'animated');
}
- let previousKey: KeyCode;
- let nextKey: KeyCode;
+ let previousKeys: KeyCode[];
+ let nextKeys: KeyCode[];
switch (this.options.orientation) {
case ActionsOrientation.HORIZONTAL:
- previousKey = KeyCode.LeftArrow;
- nextKey = KeyCode.RightArrow;
+ previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
+ nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
break;
case ActionsOrientation.HORIZONTAL_REVERSE:
- previousKey = KeyCode.RightArrow;
- nextKey = KeyCode.LeftArrow;
+ previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
+ nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
this.domNode.className += ' reverse';
break;
case ActionsOrientation.VERTICAL:
- previousKey = KeyCode.UpArrow;
- nextKey = KeyCode.DownArrow;
+ previousKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
+ nextKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
this.domNode.className += ' vertical';
break;
case ActionsOrientation.VERTICAL_REVERSE:
- previousKey = KeyCode.DownArrow;
- nextKey = KeyCode.UpArrow;
+ previousKeys = [KeyCode.RightArrow, KeyCode.DownArrow];
+ nextKeys = [KeyCode.LeftArrow, KeyCode.UpArrow];
this.domNode.className += ' vertical reverse';
break;
}
@@ -506,9 +506,9 @@ export class ActionBar extends Disposable implements IActionRunner {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
- if (event.equals(previousKey)) {
+ if (previousKeys && (event.equals(previousKeys[0]) || event.equals(previousKeys[1]))) {
this.focusPrevious();
- } else if (event.equals(nextKey)) {
+ } else if (nextKeys && (event.equals(nextKeys[0]) || event.equals(nextKeys[1]))) {
this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
this._onDidCancel.fire();
diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css
index f7bab5b08e..3d3915b604 100644
--- a/src/vs/base/browser/ui/countBadge/countBadge.css
+++ b/src/vs/base/browser/ui/countBadge/countBadge.css
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
.monaco-count-badge {
- padding: 3px 5px;
+ padding: 3px 6px;
border-radius: 11px;
font-size: 11px;
min-width: 18px;
@@ -14,4 +14,9 @@
text-align: center;
display: inline-block;
box-sizing: border-box;
-}
\ No newline at end of file
+}
+
+.monaco-count-badge.long {
+ padding: 2px 3px;
+ border-radius: 2px;
+}
diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts
index d901efae8b..2af4512869 100644
--- a/src/vs/base/browser/ui/tree/indexTreeModel.ts
+++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts
@@ -18,6 +18,7 @@ export interface IIndexTreeNode extends ITreeNode, TFilterData = voi
collapsible: false,
collapsed: false,
renderNodeCount: 0,
+ visibility: TreeVisibility.Visible,
visible: true,
filterData: undefined
};
@@ -185,6 +187,17 @@ export class IndexTreeModel, TFilterData = voi
}
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
+
+ let node: IIndexTreeNode | undefined = parentNode;
+
+ while (node) {
+ if (node.visibility === TreeVisibility.Recurse) {
+ this.refilter();
+ break;
+ }
+
+ node = node.parent;
+ }
}
rerender(location: number[]): void {
@@ -194,7 +207,7 @@ export class IndexTreeModel, TFilterData = voi
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
- if (revealed) {
+ if (node.visible && revealed) {
this.list.splice(listIndex, 1, [node]);
}
}
@@ -355,11 +368,13 @@ export class IndexTreeModel, TFilterData = voi
collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'),
collapsed: typeof treeElement.collapsed === 'undefined' ? this.collapseByDefault : treeElement.collapsed,
renderNodeCount: 1,
+ visibility: TreeVisibility.Visible,
visible: true,
filterData: undefined
};
const visibility = this._filterNode(node, parentVisibility);
+ node.visibility = visibility;
if (revealed) {
treeListElements.push(node);
diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts
index 95a64ade15..9c7c88cd86 100644
--- a/src/vs/base/common/codicons.ts
+++ b/src/vs/base/common/codicons.ts
@@ -43,12 +43,12 @@ const _registry = new Registry();
export const iconRegistry: IIconRegistry = _registry;
-export function registerIcon(id: string, def: Codicon) {
+export function registerIcon(id: string, def: Codicon, description?: string) {
return new Codicon(id, def);
}
export class Codicon {
- constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition) {
+ constructor(public readonly id: string, public readonly definition: Codicon | IconDefinition, public description?: string) {
_registry.add(this);
}
public get classNames() { return 'codicon codicon-' + this.id; }
diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts
index 9f2c8519b8..8834dc3807 100644
--- a/src/vs/base/common/jsonSchema.ts
+++ b/src/vs/base/common/jsonSchema.ts
@@ -63,6 +63,7 @@ export interface IJSONSchema {
markdownEnumDescriptions?: string[];
markdownDescription?: string;
doNotSuggest?: boolean;
+ suggestSortText?: string;
allowComments?: boolean;
allowTrailingCommas?: boolean;
}
diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts
index e5813e996b..eb56c470b0 100644
--- a/src/vs/base/common/map.ts
+++ b/src/vs/base/common/map.ts
@@ -580,9 +580,7 @@ export const enum Touch {
AsNew = 2
}
-export class LinkedMap implements Map{
-
- readonly [Symbol.toStringTag] = 'LinkedMap';
+export class LinkedMap {
private _map: Map>;
private _head: Item | undefined;
@@ -634,7 +632,7 @@ export class LinkedMap implements Map{
return item.value;
}
- set(key: K, value: V, touch: Touch = Touch.None): this {
+ set(key: K, value: V, touch: Touch = Touch.None): void {
let item = this._map.get(key);
if (item) {
item.value = value;
@@ -660,7 +658,6 @@ export class LinkedMap implements Map{
this._map.set(key, item);
this._size++;
}
- return this;
}
delete(key: K): boolean {
@@ -704,13 +701,34 @@ export class LinkedMap implements Map{
}
}
- keys(): IterableIterator {
+ values(): V[] {
+ const result: V[] = [];
let current = this._head;
+ while (current) {
+ result.push(current.value);
+ current = current.next;
+ }
+ return result;
+ }
+
+ keys(): K[] {
+ const result: K[] = [];
+ let current = this._head;
+ while (current) {
+ result.push(current.key);
+ current = current.next;
+ }
+ return result;
+ }
+
+ /* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
+ keys(): IterableIterator {
+ const current = this._head;
const iterator: IterableIterator = {
[Symbol.iterator]() {
return iterator;
},
- next(): IteratorResult {
+ next():IteratorResult {
if (current) {
const result = { value: current.key, done: false };
current = current.next;
@@ -724,12 +742,12 @@ export class LinkedMap implements Map{
}
values(): IterableIterator {
- let current = this._head;
+ const current = this._head;
const iterator: IterableIterator = {
[Symbol.iterator]() {
return iterator;
},
- next(): IteratorResult {
+ next():IteratorResult {
if (current) {
const result = { value: current.value, done: false };
current = current.next;
@@ -741,29 +759,7 @@ export class LinkedMap implements Map{
};
return iterator;
}
-
- entries(): IterableIterator<[K, V]> {
- let current = this._head;
- const iterator: IterableIterator<[K, V]> = {
- [Symbol.iterator]() {
- return iterator;
- },
- next(): IteratorResult<[K, V]> {
- if (current) {
- const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
- current = current.next;
- return result;
- } else {
- return { value: undefined, done: true };
- }
- }
- };
- return iterator;
- }
-
- [Symbol.iterator](): IterableIterator<[K, V]> {
- return this.entries();
- }
+ */
protected trimOld(newSize: number) {
if (newSize >= this.size) {
@@ -965,10 +961,9 @@ export class LRUCache extends LinkedMap {
return super.get(key, Touch.None);
}
- set(key: K, value: V): this {
+ set(key: K, value: V): void {
super.set(key, value, Touch.AsNew);
this.checkTrim();
- return this;
}
private checkTrim() {
diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css
index aa2cb9758d..5a1e959ec4 100644
--- a/src/vs/base/parts/quickinput/browser/media/quickInput.css
+++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css
@@ -97,10 +97,14 @@
align-self: center;
position: absolute;
right: 4px;
+ display: flex;
+ align-items: center;
}
.quick-input-count .monaco-count-badge {
vertical-align: middle;
+ padding: 2px 4px;
+ border-radius: 2px;
}
.quick-input-action {
@@ -108,9 +112,8 @@
}
.quick-input-action .monaco-text-button {
- font-size: 85%;
+ font-size: 11px;
padding: 0 6px;
- line-height: initial;
display: flex;
height: 100%;
align-items: center;
diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts
index cd6c9fc389..f099f53d07 100644
--- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts
+++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts
@@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
-import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
+import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
function toSpliceable(arr: T[]): ISpliceable {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
+ // console.log(`splice (${start}, ${deleteCount}, ${elements.length} [${elements.join(', ')}] )`); // debugging
arr.splice(start, deleteCount, ...elements);
}
};
@@ -240,4 +241,35 @@ suite('ObjectTreeModel', function () {
model.expandTo(1000);
assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]);
});
+
+ test('issue #95641', () => {
+ const list: ITreeNode[] = [];
+ let fn = (_: string) => true;
+ const filter = new class implements ITreeFilter {
+ filter(element: string, parentVisibility: TreeVisibility): TreeVisibility {
+ if (element === 'file') {
+ return TreeVisibility.Recurse;
+ }
+
+ return fn(element) ? TreeVisibility.Visible : parentVisibility;
+ }
+ };
+ const model = new ObjectTreeModel('test', toSpliceable(list), { filter });
+
+ model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]);
+ assert.deepEqual(toArray(list), ['file', 'hello']);
+
+ fn = (el: string) => el === 'world';
+ model.refilter();
+ assert.deepEqual(toArray(list), []);
+
+ model.setChildren('file', [{ element: 'world' }]);
+ assert.deepEqual(toArray(list), ['file', 'world']);
+
+ model.setChildren('file', [{ element: 'hello' }]);
+ assert.deepEqual(toArray(list), []);
+
+ model.setChildren('file', [{ element: 'world' }]);
+ assert.deepEqual(toArray(list), ['file', 'world']);
+ });
});
diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts
index 2ca1eb2aa7..3650bff666 100644
--- a/src/vs/base/test/common/map.test.ts
+++ b/src/vs/base/test/common/map.test.ts
@@ -13,8 +13,8 @@ suite('Map', () => {
let map = new LinkedMap();
map.set('ak', 'av');
map.set('bk', 'bv');
- assert.deepStrictEqual([...map.keys()], ['ak', 'bk']);
- assert.deepStrictEqual([...map.values()], ['av', 'bv']);
+ assert.deepStrictEqual(map.keys(), ['ak', 'bk']);
+ assert.deepStrictEqual(map.values(), ['av', 'bv']);
assert.equal(map.first, 'av');
assert.equal(map.last, 'bv');
});
@@ -23,16 +23,16 @@ suite('Map', () => {
let map = new LinkedMap();
map.set('ak', 'av');
map.set('ak', 'av', Touch.AsOld);
- assert.deepStrictEqual([...map.keys()], ['ak']);
- assert.deepStrictEqual([...map.values()], ['av']);
+ assert.deepStrictEqual(map.keys(), ['ak']);
+ assert.deepStrictEqual(map.values(), ['av']);
});
test('LinkedMap - Touch New one', () => {
let map = new LinkedMap();
map.set('ak', 'av');
map.set('ak', 'av', Touch.AsNew);
- assert.deepStrictEqual([...map.keys()], ['ak']);
- assert.deepStrictEqual([...map.values()], ['av']);
+ assert.deepStrictEqual(map.keys(), ['ak']);
+ assert.deepStrictEqual(map.values(), ['av']);
});
test('LinkedMap - Touch Old two', () => {
@@ -40,8 +40,8 @@ suite('Map', () => {
map.set('ak', 'av');
map.set('bk', 'bv');
map.set('bk', 'bv', Touch.AsOld);
- assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
- assert.deepStrictEqual([...map.values()], ['bv', 'av']);
+ assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
+ assert.deepStrictEqual(map.values(), ['bv', 'av']);
});
test('LinkedMap - Touch New two', () => {
@@ -49,8 +49,8 @@ suite('Map', () => {
map.set('ak', 'av');
map.set('bk', 'bv');
map.set('ak', 'av', Touch.AsNew);
- assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
- assert.deepStrictEqual([...map.values()], ['bv', 'av']);
+ assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
+ assert.deepStrictEqual(map.values(), ['bv', 'av']);
});
test('LinkedMap - Touch Old from middle', () => {
@@ -59,8 +59,8 @@ suite('Map', () => {
map.set('bk', 'bv');
map.set('ck', 'cv');
map.set('bk', 'bv', Touch.AsOld);
- assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']);
- assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']);
+ assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']);
+ assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']);
});
test('LinkedMap - Touch New from middle', () => {
@@ -69,8 +69,8 @@ suite('Map', () => {
map.set('bk', 'bv');
map.set('ck', 'cv');
map.set('bk', 'bv', Touch.AsNew);
- assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']);
- assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']);
+ assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']);
+ assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']);
});
test('LinkedMap - basics', function () {
@@ -136,15 +136,13 @@ suite('Map', () => {
assert.strictEqual(cache.size, 5);
cache.set(6, 6);
assert.strictEqual(cache.size, 5);
- assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]);
+ assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]);
cache.set(7, 7);
assert.strictEqual(cache.size, 5);
- assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]);
+ assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]);
let values: number[] = [];
[3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [3, 4, 5, 6, 7]);
-
- assert.deepEqual([...cache.entries()], [[3, 3], [4, 4], [5, 5], [6, 6], [7, 7]]);
});
test('LinkedMap - LRU Cache get', () => {
@@ -152,11 +150,11 @@ suite('Map', () => {
[1, 2, 3, 4, 5].forEach(value => cache.set(value, value));
assert.strictEqual(cache.size, 5);
- assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]);
+ assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]);
cache.get(3);
- assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
+ assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
cache.peek(4);
- assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
+ assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
let values: number[] = [];
[1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [1, 2, 3, 4, 5]);
@@ -171,7 +169,7 @@ suite('Map', () => {
assert.strictEqual(cache.size, 10);
cache.limit = 5;
assert.strictEqual(cache.size, 5);
- assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]);
+ assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]);
cache.limit = 20;
assert.strictEqual(cache.size, 5);
for (let i = 11; i <= 20; i++) {
@@ -183,7 +181,7 @@ suite('Map', () => {
values.push(cache.get(i)!);
assert.strictEqual(cache.get(i), i);
}
- assert.deepStrictEqual([...cache.values()], values);
+ assert.deepStrictEqual(cache.values(), values);
});
test('LinkedMap - LRU Cache limit with ratio', () => {
@@ -195,11 +193,11 @@ suite('Map', () => {
assert.strictEqual(cache.size, 10);
cache.set(11, 11);
assert.strictEqual(cache.size, 5);
- assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]);
+ assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]);
let values: number[] = [];
- [...cache.keys()].forEach(key => values.push(cache.get(key)!));
+ cache.keys().forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [7, 8, 9, 10, 11]);
- assert.deepStrictEqual([...cache.values()], values);
+ assert.deepStrictEqual(cache.values(), values);
});
test('LinkedMap - toJSON / fromJSON', () => {
@@ -239,7 +237,7 @@ suite('Map', () => {
map.delete('1');
assert.equal(map.get('1'), undefined);
assert.equal(map.size, 0);
- assert.equal([...map.keys()].length, 0);
+ assert.equal(map.keys().length, 0);
});
test('LinkedMap - delete Head', function () {
@@ -253,8 +251,8 @@ suite('Map', () => {
map.delete('1');
assert.equal(map.get('2'), 2);
assert.equal(map.size, 1);
- assert.equal([...map.keys()].length, 1);
- assert.equal([...map.keys()][0], 2);
+ assert.equal(map.keys().length, 1);
+ assert.equal(map.keys()[0], 2);
});
test('LinkedMap - delete Tail', function () {
@@ -268,8 +266,8 @@ suite('Map', () => {
map.delete('2');
assert.equal(map.get('1'), 1);
assert.equal(map.size, 1);
- assert.equal([...map.keys()].length, 1);
- assert.equal([...map.keys()][0], 1);
+ assert.equal(map.keys().length, 1);
+ assert.equal(map.keys()[0], 1);
});
diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts
index 9686283e1a..4b438f6e1a 100644
--- a/src/vs/editor/browser/controller/pointerHandler.ts
+++ b/src/vs/editor/browser/controller/pointerHandler.ts
@@ -31,84 +31,6 @@ function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, curr
return r;
}
-/**
- * Basically IE10 and IE11
- */
-class MsPointerHandler extends MouseHandler implements IDisposable {
-
- private _lastPointerType: string;
- private _installGestureHandlerTimeout: number;
-
- constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
- super(context, viewController, viewHelper);
-
- (this.viewHelper.linesContentDomNode.style as any).msTouchAction = 'none';
- (this.viewHelper.linesContentDomNode.style as any).msContentZooming = 'none';
-
- // TODO@Alex -> this expects that the view is added in 100 ms, might not be the case
- // This handler should be added when the dom node is in the dom tree
- this._installGestureHandlerTimeout = window.setTimeout(() => {
- this._installGestureHandlerTimeout = -1;
- if ((window).MSGesture) {
- const touchGesture = new MSGesture();
- const penGesture = new MSGesture();
- touchGesture.target = this.viewHelper.linesContentDomNode;
- penGesture.target = this.viewHelper.linesContentDomNode;
- this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => {
- // Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions
- const pointerType = e.pointerType;
- if (pointerType === ((e).MSPOINTER_TYPE_MOUSE || 'mouse')) {
- this._lastPointerType = 'mouse';
- return;
- } else if (pointerType === ((e).MSPOINTER_TYPE_TOUCH || 'touch')) {
- this._lastPointerType = 'touch';
- touchGesture.addPointer(e.pointerId);
- } else {
- this._lastPointerType = 'pen';
- penGesture.addPointer(e.pointerId);
- }
- });
- this._register(dom.addDisposableThrottledListener(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
- this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
- }
- }, 100);
- this._lastPointerType = 'mouse';
- }
-
- public _onMouseDown(e: EditorMouseEvent): void {
- if (this._lastPointerType === 'mouse') {
- super._onMouseDown(e);
- }
- }
-
- private _onCaptureGestureTap(rawEvent: MSGestureEvent): void {
- const e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode);
- const t = this._createMouseTarget(e, false);
- if (t.position) {
- this.viewController.moveTo(t.position);
- }
- // IE does not want to focus when coming in from the browser's address bar
- if ((e.browserEvent).fromElement) {
- e.preventDefault();
- this.viewHelper.focusTextArea();
- } else {
- // TODO@Alex -> cancel this is focus is lost
- setTimeout(() => {
- this.viewHelper.focusTextArea();
- });
- }
- }
-
- private _onGestureChange(e: IThrottledGestureEvent): void {
- this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
- }
-
- public dispose(): void {
- window.clearTimeout(this._installGestureHandlerTimeout);
- super.dispose();
- }
-}
-
/**
* Basically Edge but should be modified to handle any pointerEnabled, even without support of MSGesture
*/
@@ -302,9 +224,7 @@ export class PointerHandler extends Disposable {
constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
super();
- if (window.navigator.msPointerEnabled) {
- this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
- } else if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
+ if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
} else if ((window).TouchEvent) {
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts
index 356d93a11a..261e63e8b9 100644
--- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts
+++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts
@@ -111,7 +111,7 @@ export class EditorScrollbar extends ViewPart {
const minimap = options.get(EditorOption.minimap);
const side = minimap.side;
if (side === 'right') {
- this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimapWidth);
+ this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimap.minimapWidth);
} else {
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth);
}
diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts
index 00fb54efae..bc5aebe196 100644
--- a/src/vs/editor/browser/viewParts/minimap/minimap.ts
+++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts
@@ -104,30 +104,31 @@ class MinimapOptions {
const options = configuration.options;
const pixelRatio = options.get(EditorOption.pixelRatio);
const layoutInfo = options.get(EditorOption.layoutInfo);
+ const minimapLayout = layoutInfo.minimap;
const fontInfo = options.get(EditorOption.fontInfo);
const minimapOpts = options.get(EditorOption.minimap);
- this.renderMinimap = layoutInfo.renderMinimap | 0;
+ this.renderMinimap = minimapLayout.renderMinimap;
this.size = minimapOpts.size;
- this.minimapHeightIsEditorHeight = layoutInfo.minimapHeightIsEditorHeight;
+ this.minimapHeightIsEditorHeight = minimapLayout.minimapHeightIsEditorHeight;
this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine);
this.showSlider = minimapOpts.showSlider;
this.pixelRatio = pixelRatio;
this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this.lineHeight = options.get(EditorOption.lineHeight);
- this.minimapLeft = layoutInfo.minimapLeft;
- this.minimapWidth = layoutInfo.minimapWidth;
+ this.minimapLeft = minimapLayout.minimapLeft;
+ this.minimapWidth = minimapLayout.minimapWidth;
this.minimapHeight = layoutInfo.height;
- this.canvasInnerWidth = layoutInfo.minimapCanvasInnerWidth;
- this.canvasInnerHeight = layoutInfo.minimapCanvasInnerHeight;
- this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth;
- this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight;
+ this.canvasInnerWidth = minimapLayout.minimapCanvasInnerWidth;
+ this.canvasInnerHeight = minimapLayout.minimapCanvasInnerHeight;
+ this.canvasOuterWidth = minimapLayout.minimapCanvasOuterWidth;
+ this.canvasOuterHeight = minimapLayout.minimapCanvasOuterHeight;
- this.isSampling = layoutInfo.minimapIsSampling;
+ this.isSampling = minimapLayout.minimapIsSampling;
this.editorHeight = layoutInfo.height;
- this.fontScale = layoutInfo.minimapScale;
- this.minimapLineHeight = layoutInfo.minimapLineHeight;
+ this.fontScale = minimapLayout.minimapScale;
+ this.minimapLineHeight = minimapLayout.minimapLineHeight;
this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale;
this.charRenderer = once(() => MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily));
@@ -262,7 +263,8 @@ class MinimapLayout {
const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight);
const sliderTop = (scrollTop * computedSliderRatio);
const sliderNeeded = (maxMinimapSliderTop > 0);
- return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, lineCount);
+ const maxLinesFitting = Math.floor(options.canvasInnerHeight / options.minimapLineHeight);
+ return new MinimapLayout(scrollTop, scrollHeight, sliderNeeded, computedSliderRatio, sliderTop, sliderHeight, 1, Math.min(lineCount, maxLinesFitting));
}
// The visible line count in a viewport can change due to a number of reasons:
diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts
index 241763520f..ced91db145 100644
--- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts
+++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts
@@ -42,7 +42,7 @@ export class ViewOverlayWidgets extends ViewPart {
this._widgets = {};
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
- this._minimapWidth = layoutInfo.minimapWidth;
+ this._minimapWidth = layoutInfo.minimap.minimapWidth;
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
this._editorHeight = layoutInfo.height;
this._editorWidth = layoutInfo.width;
@@ -68,7 +68,7 @@ export class ViewOverlayWidgets extends ViewPart {
const layoutInfo = options.get(EditorOption.layoutInfo);
this._verticalScrollbarWidth = layoutInfo.verticalScrollbarWidth;
- this._minimapWidth = layoutInfo.minimapWidth;
+ this._minimapWidth = layoutInfo.minimap.minimapWidth;
this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
this._editorHeight = layoutInfo.height;
this._editorWidth = layoutInfo.width;
diff --git a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts
index 3fbcda8b19..04d47c779a 100644
--- a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts
+++ b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts
@@ -58,10 +58,10 @@ export class ScrollDecorationViewPart extends ViewPart {
const options = this._context.configuration.options;
const layoutInfo = options.get(EditorOption.layoutInfo);
- if (layoutInfo.renderMinimap === 0 || (layoutInfo.minimapWidth > 0 && layoutInfo.minimapLeft === 0)) {
+ if (layoutInfo.minimap.renderMinimap === 0 || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) {
this._width = layoutInfo.width;
} else {
- this._width = layoutInfo.width - layoutInfo.minimapWidth - layoutInfo.verticalScrollbarWidth;
+ this._width = layoutInfo.width - layoutInfo.minimap.minimapWidth - layoutInfo.verticalScrollbarWidth;
}
}
diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts
index aaf2a0e3ff..f652d90aa5 100644
--- a/src/vs/editor/common/config/commonEditorConfig.ts
+++ b/src/vs/editor/common/config/commonEditorConfig.ts
@@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import * as arrays from 'vs/base/common/arrays';
-import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions';
+import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById, ComputeOptionsMemory } from 'vs/editor/common/config/editorOptions';
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
import { IConfiguration, IDimension } from 'vs/editor/common/editorCommon';
@@ -284,6 +284,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
public readonly onDidChange: Event = this._onDidChange.event;
public readonly isSimpleWidget: boolean;
+ private _computeOptionsMemory: ComputeOptionsMemory;
public options!: ComputedEditorOptions;
private _isDominatedByLongLines: boolean;
@@ -299,6 +300,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
this.isSimpleWidget = isSimpleWidget;
this._isDominatedByLongLines = false;
+ this._computeOptionsMemory = new ComputeOptionsMemory();
this._viewLineCount = 1;
this._lineNumbersDigitCount = 1;
@@ -344,6 +346,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
const partialEnv = this._getEnvConfiguration();
const bareFontInfo = BareFontInfo.createFromValidatedSettings(this._validatedOptions, partialEnv.zoomLevel, this.isSimpleWidget);
const env: IEnvironmentalOptions = {
+ memory: this._computeOptionsMemory,
outerWidth: partialEnv.outerWidth,
outerHeight: partialEnv.outerHeight,
fontInfo: this.readConfiguration(bareFontInfo),
diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts
index e479cd603e..4713d29f70 100644
--- a/src/vs/editor/common/config/editorOptions.ts
+++ b/src/vs/editor/common/config/editorOptions.ts
@@ -693,6 +693,7 @@ export interface IComputedEditorOptions {
* @internal
*/
export interface IEnvironmentalOptions {
+ readonly memory: ComputeOptionsMemory | null;
readonly outerWidth: number;
readonly outerHeight: number;
readonly fontInfo: FontInfo;
@@ -706,6 +707,22 @@ export interface IEnvironmentalOptions {
readonly accessibilitySupport: AccessibilitySupport;
}
+/**
+ * @internal
+ */
+export class ComputeOptionsMemory {
+
+ public stableMinimapLayoutInput: IMinimapLayoutInput | null;
+ public stableFitMaxMinimapScale: number;
+ public stableFitRemainingWidth: number;
+
+ constructor() {
+ this.stableMinimapLayoutInput = null;
+ this.stableFitMaxMinimapScale = 0;
+ this.stableFitRemainingWidth = 0;
+ }
+}
+
export interface IEditorOption {
readonly id: K1;
readonly name: string;
@@ -1717,32 +1734,19 @@ export interface EditorLayoutInfo {
readonly contentWidth: number;
/**
- * The position for the minimap
+ * Layout information for the minimap
*/
- readonly minimapLeft: number;
- /**
- * The width of the minimap
- */
- readonly minimapWidth: number;
- readonly minimapHeightIsEditorHeight: boolean;
- readonly minimapIsSampling: boolean;
- readonly minimapScale: number;
- readonly minimapLineHeight: number;
- readonly minimapCanvasInnerWidth: number;
- readonly minimapCanvasInnerHeight: number;
- readonly minimapCanvasOuterWidth: number;
- readonly minimapCanvasOuterHeight: number;
-
- /**
- * Minimap render type
- */
- readonly renderMinimap: RenderMinimap;
+ readonly minimap: EditorMinimapLayoutInfo;
/**
* The number of columns (of typical characters) fitting on a viewport line.
*/
readonly viewportColumn: number;
+ readonly isWordWrapMinified: boolean;
+ readonly isViewportWrapping: boolean;
+ readonly wrappingColumn: number;
+
/**
* The width of the vertical scrollbar.
*/
@@ -1758,18 +1762,80 @@ export interface EditorLayoutInfo {
readonly overviewRuler: OverviewRulerPosition;
}
+/**
+ * The internal layout details of the editor.
+ */
+export interface EditorMinimapLayoutInfo {
+ readonly renderMinimap: RenderMinimap;
+ readonly minimapLeft: number;
+ readonly minimapWidth: number;
+ readonly minimapHeightIsEditorHeight: boolean;
+ readonly minimapIsSampling: boolean;
+ readonly minimapScale: number;
+ readonly minimapLineHeight: number;
+ readonly minimapCanvasInnerWidth: number;
+ readonly minimapCanvasInnerHeight: number;
+ readonly minimapCanvasOuterWidth: number;
+ readonly minimapCanvasOuterHeight: number;
+}
+
/**
* @internal
*/
export interface EditorLayoutInfoComputerEnv {
- outerWidth: number;
- outerHeight: number;
- lineHeight: number;
- viewLineCount: number;
- lineNumbersDigitCount: number;
- typicalHalfwidthCharacterWidth: number;
- maxDigitWidth: number;
- pixelRatio: number;
+ readonly memory: ComputeOptionsMemory | null;
+ readonly outerWidth: number;
+ readonly outerHeight: number;
+ readonly isDominatedByLongLines: boolean
+ readonly lineHeight: number;
+ readonly viewLineCount: number;
+ readonly lineNumbersDigitCount: number;
+ readonly typicalHalfwidthCharacterWidth: number;
+ readonly maxDigitWidth: number;
+ readonly pixelRatio: number;
+}
+
+/**
+ * @internal
+ */
+export interface IEditorLayoutComputerInput {
+ readonly outerWidth: number;
+ readonly outerHeight: number;
+ readonly isDominatedByLongLines: boolean
+ readonly lineHeight: number;
+ readonly lineNumbersDigitCount: number;
+ readonly typicalHalfwidthCharacterWidth: number;
+ readonly maxDigitWidth: number;
+ readonly pixelRatio: number;
+ readonly glyphMargin: boolean;
+ readonly lineDecorationsWidth: string | number;
+ readonly folding: boolean;
+ readonly minimap: Readonly>;
+ readonly scrollbar: InternalEditorScrollbarOptions;
+ readonly lineNumbers: InternalEditorRenderLineNumbersOptions;
+ readonly lineNumbersMinChars: number;
+ readonly scrollBeyondLastLine: boolean;
+ readonly wordWrap: 'wordWrapColumn' | 'on' | 'off' | 'bounded';
+ readonly wordWrapColumn: number;
+ readonly wordWrapMinified: boolean;
+ readonly accessibilitySupport: AccessibilitySupport;
+}
+
+/**
+ * @internal
+ */
+export interface IMinimapLayoutInput {
+ readonly outerWidth: number;
+ readonly outerHeight: number;
+ readonly lineHeight: number;
+ readonly typicalHalfwidthCharacterWidth: number;
+ readonly pixelRatio: number;
+ readonly scrollBeyondLastLine: boolean;
+ readonly minimap: Readonly>;
+ readonly verticalScrollbarWidth: number;
+ readonly viewLineCount: number;
+ readonly remainingWidth: number;
+ readonly isViewportWrapping: boolean;
}
/**
@@ -1780,14 +1846,22 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(input.minimap.scale * 2) : input.minimap.scale);
+ const minimapMaxColumn = input.minimap.maxColumn;
+ const minimapSize = input.minimap.size;
+ const minimapSide = input.minimap.side;
+ const verticalScrollbarWidth = input.verticalScrollbarWidth;
+ const viewLineCount = input.viewLineCount;
+ const remainingWidth = input.remainingWidth;
+ const isViewportWrapping = input.isViewportWrapping;
+
+ const baseCharHeight = minimapRenderCharacters ? 2 : 3;
+ let minimapCanvasInnerHeight = Math.floor(pixelRatio * outerHeight);
+ const minimapCanvasOuterHeight = minimapCanvasInnerHeight / pixelRatio;
+ let minimapHeightIsEditorHeight = false;
+ let minimapIsSampling = false;
+ let minimapLineHeight = baseCharHeight * minimapScale;
+ let minimapCharWidth = minimapScale / pixelRatio;
+ let minimapWidthMultiplier: number = 1;
+
+ if (minimapSize === 'fill' || minimapSize === 'fit') {
+ const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
+ viewLineCount: viewLineCount,
+ scrollBeyondLastLine: scrollBeyondLastLine,
+ height: outerHeight,
+ lineHeight: lineHeight,
+ pixelRatio: pixelRatio
+ });
+ // ratio is intentionally not part of the layout to avoid the layout changing all the time
+ // when doing sampling
+ const ratio = viewLineCount / minimapLineCount;
+
+ if (ratio > 1) {
+ minimapHeightIsEditorHeight = true;
+ minimapIsSampling = true;
+ minimapScale = 1;
+ minimapLineHeight = 1;
+ minimapCharWidth = minimapScale / pixelRatio;
+ } else {
+ let fitBecomesFill = false;
+ let maxMinimapScale = minimapScale + 1;
+
+ if (minimapSize === 'fit') {
+ const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
+ if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) {
+ // There is a loop when using `fit` and viewport wrapping:
+ // - view line count impacts minimap layout
+ // - minimap layout impacts viewport width
+ // - viewport width impacts view line count
+ // To break the loop, once we go to a smaller minimap scale, we try to stick with it.
+ fitBecomesFill = true;
+ maxMinimapScale = memory.stableFitMaxMinimapScale;
+ } else {
+ fitBecomesFill = (effectiveMinimapHeight > minimapCanvasInnerHeight);
+ if (isViewportWrapping && fitBecomesFill) {
+ // remember for next time
+ memory.stableMinimapLayoutInput = input;
+ memory.stableFitRemainingWidth = remainingWidth;
+ } else {
+ memory.stableMinimapLayoutInput = null;
+ memory.stableFitRemainingWidth = 0;
+ }
+ }
+ }
+
+ if (minimapSize === 'fill' || fitBecomesFill) {
+ minimapHeightIsEditorHeight = true;
+ const configuredMinimapScale = minimapScale;
+ minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
+ minimapScale = Math.min(maxMinimapScale, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight)));
+ if (minimapScale > configuredMinimapScale) {
+ minimapWidthMultiplier = Math.min(2, minimapScale / configuredMinimapScale);
+ }
+ minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
+ minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
+ if (isViewportWrapping && fitBecomesFill) {
+ memory.stableFitMaxMinimapScale = minimapScale;
+ }
+ }
+ }
+ }
+
+ // Given:
+ // (leaving 2px for the cursor to have space after the last character)
+ // viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
+ // minimapWidth = viewportColumn * minimapCharWidth
+ // contentWidth = remainingWidth - minimapWidth
+ // What are good values for contentWidth and minimapWidth ?
+
+ // minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
+ // typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
+ // typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
+ // (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
+ // minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
+
+ const minimapMaxWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
+ const minimapWidth = Math.min(minimapMaxWidth, Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH);
+
+ let minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth);
+ const minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio;
+ minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier);
+
+ const renderMinimap = (minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks);
+ const minimapLeft = (minimapSide === 'left' ? 0 : (outerWidth - minimapWidth - verticalScrollbarWidth));
+
+ return {
+ renderMinimap,
+ minimapLeft,
+ minimapWidth,
+ minimapHeightIsEditorHeight,
+ minimapIsSampling,
+ minimapScale,
+ minimapLineHeight,
+ minimapCanvasInnerWidth,
+ minimapCanvasInnerHeight,
+ minimapCanvasOuterWidth,
+ minimapCanvasOuterHeight,
+ };
+ }
+
public static computeLayout(options: IComputedEditorOptions, env: EditorLayoutInfoComputerEnv): EditorLayoutInfo {
const outerWidth = env.outerWidth | 0;
const outerHeight = env.outerHeight | 0;
@@ -1819,24 +2059,25 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale);
- const minimapMaxColumn = minimap.maxColumn | 0;
- const minimapSize = minimap.size;
const scrollbar = options.get(EditorOption.scrollbar);
- const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0;
+ const verticalScrollbarWidth = scrollbar.verticalScrollbarSize;
const verticalScrollbarHasArrows = scrollbar.verticalHasArrows;
- const scrollbarArrowSize = scrollbar.arrowSize | 0;
- const horizontalScrollbarHeight = scrollbar.horizontalScrollbarSize | 0;
+ const scrollbarArrowSize = scrollbar.arrowSize;
+ const horizontalScrollbarHeight = scrollbar.horizontalScrollbarSize;
const rawLineDecorationsWidth = options.get(EditorOption.lineDecorationsWidth);
const folding = options.get(EditorOption.folding);
@@ -1870,107 +2111,62 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption 1) {
- minimapHeightIsEditorHeight = true;
- minimapIsSampling = true;
- minimapScale = 1;
- minimapLineHeight = 1;
- minimapCharWidth = minimapScale / pixelRatio;
- } else {
- const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
- if (minimapSize === 'fill' || effectiveMinimapHeight > minimapCanvasInnerHeight) {
- minimapHeightIsEditorHeight = true;
- const configuredFontScale = minimapScale;
- minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
- minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight)));
- if (minimapScale > configuredFontScale) {
- minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale);
- }
- minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
- minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
- }
- }
+ if (accessibilitySupport !== AccessibilitySupport.Enabled) {
+ // See https://github.com/Microsoft/vscode/issues/27766
+ // Never enable wrapping when a screen reader is attached
+ // because arrow down etc. will not move the cursor in the way
+ // a screen reader expects.
+ if (wordWrapMinified && isDominatedByLongLines) {
+ // Force viewport width wrapping if model is dominated by long lines
+ isWordWrapMinified = true;
+ isViewportWrapping = true;
+ } else if (wordWrap === 'on' || wordWrap === 'bounded') {
+ isViewportWrapping = true;
+ } else if (wordWrap === 'wordWrapColumn') {
+ wrappingColumn = wordWrapColumn;
}
-
- renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks;
-
- // Given:
- // (leaving 2px for the cursor to have space after the last character)
- // viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
- // minimapWidth = viewportColumn * minimapCharWidth
- // contentWidth = remainingWidth - minimapWidth
- // What are good values for contentWidth and minimapWidth ?
-
- // minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
- // typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
- // typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
- // (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
- // minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
-
- minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH;
- let minimapColumns = minimapWidth / minimapCharWidth;
- if (minimapColumns > minimapMaxColumn) {
- minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
- }
- contentWidth = remainingWidth - minimapWidth;
-
- if (minimapSide === 'left') {
- minimapLeft = 0;
- glyphMarginLeft += minimapWidth;
- lineNumbersLeft += minimapWidth;
- decorationsLeft += minimapWidth;
- contentLeft += minimapWidth;
- } else {
- minimapLeft = outerWidth - minimapWidth - verticalScrollbarWidth;
- }
-
- minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth);
- minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio;
- minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier);
}
+ const minimapLayout = EditorLayoutInfoComputer._computeMinimapLayout({
+ outerWidth: outerWidth,
+ outerHeight: outerHeight,
+ lineHeight: lineHeight,
+ typicalHalfwidthCharacterWidth: typicalHalfwidthCharacterWidth,
+ pixelRatio: pixelRatio,
+ scrollBeyondLastLine: scrollBeyondLastLine,
+ minimap: minimap,
+ verticalScrollbarWidth: verticalScrollbarWidth,
+ viewLineCount: viewLineCount,
+ remainingWidth: remainingWidth,
+ isViewportWrapping: isViewportWrapping,
+ }, env.memory || new ComputeOptionsMemory());
+
+ if (minimapLayout.renderMinimap !== RenderMinimap.None && minimapLayout.minimapLeft === 0) {
+ // the minimap is rendered to the left, so move everything to the right
+ glyphMarginLeft += minimapLayout.minimapWidth;
+ lineNumbersLeft += minimapLayout.minimapWidth;
+ decorationsLeft += minimapLayout.minimapWidth;
+ contentLeft += minimapLayout.minimapWidth;
+ }
+ const contentWidth = remainingWidth - minimapLayout.minimapWidth;
+
// (leaving 2px for the cursor to have space after the last character)
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth));
const verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0);
+ if (isViewportWrapping) {
+ // compute the actual wrappingColumn
+ wrappingColumn = Math.max(1, viewportColumn);
+ if (wordWrap === 'bounded') {
+ wrappingColumn = Math.min(wrappingColumn, wordWrapColumn);
+ }
+ }
+
return {
width: outerWidth,
height: outerHeight,
@@ -1987,20 +2183,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption {
constructor() {
- super(EditorOption.wrappingInfo, [EditorOption.wordWrap, EditorOption.wordWrapColumn, EditorOption.wordWrapMinified, EditorOption.layoutInfo, EditorOption.accessibilitySupport]);
+ super(EditorOption.wrappingInfo, [EditorOption.layoutInfo]);
}
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: EditorWrappingInfo): EditorWrappingInfo {
- const wordWrap = options.get(EditorOption.wordWrap);
- const wordWrapColumn = options.get(EditorOption.wordWrapColumn);
- const wordWrapMinified = options.get(EditorOption.wordWrapMinified);
const layoutInfo = options.get(EditorOption.layoutInfo);
- const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
-
- let bareWrappingInfo: { isWordWrapMinified: boolean; isViewportWrapping: boolean; wrappingColumn: number; } | null = null;
- {
- if (accessibilitySupport === AccessibilitySupport.Enabled) {
- // See https://github.com/Microsoft/vscode/issues/27766
- // Never enable wrapping when a screen reader is attached
- // because arrow down etc. will not move the cursor in the way
- // a screen reader expects.
- bareWrappingInfo = {
- isWordWrapMinified: false,
- isViewportWrapping: false,
- wrappingColumn: -1
- };
- } else if (wordWrapMinified && env.isDominatedByLongLines) {
- // Force viewport width wrapping if model is dominated by long lines
- bareWrappingInfo = {
- isWordWrapMinified: true,
- isViewportWrapping: true,
- wrappingColumn: Math.max(1, layoutInfo.viewportColumn)
- };
- } else if (wordWrap === 'on') {
- bareWrappingInfo = {
- isWordWrapMinified: false,
- isViewportWrapping: true,
- wrappingColumn: Math.max(1, layoutInfo.viewportColumn)
- };
- } else if (wordWrap === 'bounded') {
- bareWrappingInfo = {
- isWordWrapMinified: false,
- isViewportWrapping: true,
- wrappingColumn: Math.min(Math.max(1, layoutInfo.viewportColumn), wordWrapColumn)
- };
- } else if (wordWrap === 'wordWrapColumn') {
- bareWrappingInfo = {
- isWordWrapMinified: false,
- isViewportWrapping: false,
- wrappingColumn: wordWrapColumn
- };
- } else {
- bareWrappingInfo = {
- isWordWrapMinified: false,
- isViewportWrapping: false,
- wrappingColumn: -1
- };
- }
- }
return {
isDominatedByLongLines: env.isDominatedByLongLines,
- isWordWrapMinified: bareWrappingInfo.isWordWrapMinified,
- isViewportWrapping: bareWrappingInfo.isViewportWrapping,
- wrappingColumn: bareWrappingInfo.wrappingColumn,
+ isWordWrapMinified: layoutInfo.isWordWrapMinified,
+ isViewportWrapping: layoutInfo.isViewportWrapping,
+ wrappingColumn: layoutInfo.wrappingColumn,
};
}
}
diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts
index 923a489115..c2d31236b0 100644
--- a/src/vs/editor/contrib/find/findWidget.ts
+++ b/src/vs/editor/contrib/find/findWidget.ts
@@ -701,7 +701,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
const editorWidth = layoutInfo.width;
- const minimapWidth = layoutInfo.minimapWidth;
+ const minimapWidth = layoutInfo.minimap.minimapWidth;
let collapsedFindWidget = false;
let reducedFindWidget = false;
let narrowFindWidget = false;
@@ -1218,7 +1218,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// 1. never resized before, double click should maximizes it
// 2. users resized it already but its width is the same as default
const layoutInfo = this._codeEditor.getLayoutInfo();
- width = layoutInfo.width - 28 - layoutInfo.minimapWidth - 15;
+ width = layoutInfo.width - 28 - layoutInfo.minimap.minimapWidth - 15;
this._resized = true;
} else {
/**
diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts
index 6b163949e0..e008b9e311 100644
--- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts
+++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts
@@ -254,13 +254,13 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
}
private _getWidth(info: EditorLayoutInfo): number {
- return info.width - info.minimapWidth - info.verticalScrollbarWidth;
+ return info.width - info.minimap.minimapWidth - info.verticalScrollbarWidth;
}
private _getLeft(info: EditorLayoutInfo): number {
// If minimap is to the left, we move beyond it
- if (info.minimapWidth > 0 && info.minimapLeft === 0) {
- return info.minimapWidth;
+ if (info.minimap.minimapWidth > 0 && info.minimap.minimapLeft === 0) {
+ return info.minimap.minimapWidth;
}
return 0;
}
@@ -526,6 +526,6 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
getHorizontalSashWidth() {
const layoutInfo = this.editor.getLayoutInfo();
- return layoutInfo.width - layoutInfo.minimapWidth;
+ return layoutInfo.width - layoutInfo.minimap.minimapWidth;
}
}
diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts
index cd362822bf..0ced0a4798 100644
--- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts
+++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts
@@ -76,9 +76,16 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
};
options._write(EditorOption.lineNumbers, lineNumbersOptions);
+ options._write(EditorOption.wordWrap, 'off');
+ options._write(EditorOption.wordWrapColumn, 80);
+ options._write(EditorOption.wordWrapMinified, true);
+ options._write(EditorOption.accessibilitySupport, 'auto');
+
const actual = EditorLayoutInfoComputer.computeLayout(options, {
+ memory: null,
outerWidth: input.outerWidth,
outerHeight: input.outerHeight,
+ isDominatedByLongLines: false,
lineHeight: input.lineHeight,
viewLineCount: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
lineNumbersDigitCount: input.lineNumbersDigitCount,
@@ -126,18 +133,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 990,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 800,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 800,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 98,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -188,18 +201,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 990,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 800,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 800,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 97,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 11,
horizontalScrollbarHeight: 12,
@@ -250,18 +269,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 890,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 800,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 800,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 88,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -312,18 +337,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 890,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 88,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -374,18 +405,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 890,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 88,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -436,18 +473,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 60,
contentWidth: 840,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 83,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -498,18 +541,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 60,
contentWidth: 840,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 83,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -560,18 +609,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 70,
contentWidth: 830,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 82,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -622,18 +677,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 40,
contentWidth: 860,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 171,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -684,18 +745,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 40,
contentWidth: 860,
- renderMinimap: RenderMinimap.None,
- minimapLeft: 0,
- minimapWidth: 0,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 0,
- minimapCanvasInnerHeight: 900,
- minimapCanvasOuterWidth: 0,
- minimapCanvasOuterHeight: 900,
+ minimap: {
+ renderMinimap: RenderMinimap.None,
+ minimapLeft: 0,
+ minimapWidth: 0,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 0,
+ minimapCanvasInnerHeight: 900,
+ minimapCanvasOuterWidth: 0,
+ minimapCanvasOuterHeight: 900,
+ },
+
viewportColumn: 169,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -746,18 +813,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 893,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 903,
- minimapWidth: 97,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 1,
- minimapLineHeight: 2,
- minimapCanvasInnerWidth: 97,
- minimapCanvasInnerHeight: 800,
- minimapCanvasOuterWidth: 97,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 903,
+ minimapWidth: 97,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 1,
+ minimapLineHeight: 2,
+ minimapCanvasInnerWidth: 97,
+ minimapCanvasInnerHeight: 800,
+ minimapCanvasOuterWidth: 97,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 89,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -808,18 +881,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 893,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 903,
- minimapWidth: 97,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 2,
- minimapLineHeight: 4,
- minimapCanvasInnerWidth: 194,
- minimapCanvasInnerHeight: 1600,
- minimapCanvasOuterWidth: 97,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 903,
+ minimapWidth: 97,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 2,
+ minimapLineHeight: 4,
+ minimapCanvasInnerWidth: 194,
+ minimapCanvasInnerHeight: 1600,
+ minimapCanvasOuterWidth: 97,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 89,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -870,18 +949,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 935,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 945,
- minimapWidth: 55,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 2,
- minimapLineHeight: 4,
- minimapCanvasInnerWidth: 220,
- minimapCanvasInnerHeight: 3200,
- minimapCanvasOuterWidth: 55,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 945,
+ minimapWidth: 55,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 2,
+ minimapLineHeight: 4,
+ minimapCanvasInnerWidth: 220,
+ minimapCanvasInnerHeight: 3200,
+ minimapCanvasOuterWidth: 55,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 93,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -932,18 +1017,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 65,
contentWidth: 935,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 0,
- minimapWidth: 55,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 2,
- minimapLineHeight: 4,
- minimapCanvasInnerWidth: 220,
- minimapCanvasInnerHeight: 3200,
- minimapCanvasOuterWidth: 55,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 0,
+ minimapWidth: 55,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 2,
+ minimapLineHeight: 4,
+ minimapCanvasInnerWidth: 220,
+ minimapCanvasInnerHeight: 3200,
+ minimapCanvasOuterWidth: 55,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 93,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -996,18 +1087,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 893,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 903,
- minimapWidth: 97,
- minimapHeightIsEditorHeight: true,
- minimapIsSampling: false,
- minimapScale: 3,
- minimapLineHeight: 13,
- minimapCanvasInnerWidth: 291,
- minimapCanvasInnerHeight: 1560,
- minimapCanvasOuterWidth: 97,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 903,
+ minimapWidth: 97,
+ minimapHeightIsEditorHeight: true,
+ minimapIsSampling: false,
+ minimapScale: 3,
+ minimapLineHeight: 13,
+ minimapCanvasInnerWidth: 291,
+ minimapCanvasInnerHeight: 1560,
+ minimapCanvasOuterWidth: 97,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 89,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -1060,18 +1157,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 935,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 945,
- minimapWidth: 55,
- minimapHeightIsEditorHeight: true,
- minimapIsSampling: true,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 110,
- minimapCanvasInnerHeight: 1600,
- minimapCanvasOuterWidth: 55,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 945,
+ minimapWidth: 55,
+ minimapHeightIsEditorHeight: true,
+ minimapIsSampling: true,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 110,
+ minimapCanvasInnerHeight: 1600,
+ minimapCanvasOuterWidth: 55,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 93,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -1124,18 +1227,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 893,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 903,
- minimapWidth: 97,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 2,
- minimapLineHeight: 4,
- minimapCanvasInnerWidth: 194,
- minimapCanvasInnerHeight: 1600,
- minimapCanvasOuterWidth: 97,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 903,
+ minimapWidth: 97,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 2,
+ minimapLineHeight: 4,
+ minimapCanvasInnerWidth: 194,
+ minimapCanvasInnerHeight: 1600,
+ minimapCanvasOuterWidth: 97,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 89,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -1188,18 +1297,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 10,
contentWidth: 935,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 945,
- minimapWidth: 55,
- minimapHeightIsEditorHeight: true,
- minimapIsSampling: true,
- minimapScale: 1,
- minimapLineHeight: 1,
- minimapCanvasInnerWidth: 110,
- minimapCanvasInnerHeight: 1600,
- minimapCanvasOuterWidth: 55,
- minimapCanvasOuterHeight: 800,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 945,
+ minimapWidth: 55,
+ minimapHeightIsEditorHeight: true,
+ minimapIsSampling: true,
+ minimapScale: 1,
+ minimapLineHeight: 1,
+ minimapCanvasInnerWidth: 110,
+ minimapCanvasInnerHeight: 1600,
+ minimapCanvasOuterWidth: 55,
+ minimapCanvasOuterHeight: 800,
+ },
+
viewportColumn: 93,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
@@ -1250,18 +1365,24 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
contentLeft: 92,
contentWidth: 1018,
- renderMinimap: RenderMinimap.Text,
- minimapLeft: 1096,
- minimapWidth: 91,
- minimapHeightIsEditorHeight: false,
- minimapIsSampling: false,
- minimapScale: 2,
- minimapLineHeight: 4,
- minimapCanvasInnerWidth: 182,
- minimapCanvasInnerHeight: 844,
- minimapCanvasOuterWidth: 91,
- minimapCanvasOuterHeight: 422,
+ minimap: {
+ renderMinimap: RenderMinimap.Text,
+ minimapLeft: 1096,
+ minimapWidth: 91,
+ minimapHeightIsEditorHeight: false,
+ minimapIsSampling: false,
+ minimapScale: 2,
+ minimapLineHeight: 4,
+ minimapCanvasInnerWidth: 182,
+ minimapCanvasInnerHeight: 844,
+ minimapCanvasOuterWidth: 91,
+ minimapCanvasOuterHeight: 422,
+ },
+
viewportColumn: 83,
+ isWordWrapMinified: false,
+ isViewportWrapping: false,
+ wrappingColumn: -1,
verticalScrollbarWidth: 14,
horizontalScrollbarHeight: 10,
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index a251a6b99b..f9258fce82 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -3404,29 +3404,16 @@ declare namespace monaco.editor {
*/
readonly contentWidth: number;
/**
- * The position for the minimap
+ * Layout information for the minimap
*/
- readonly minimapLeft: number;
- /**
- * The width of the minimap
- */
- readonly minimapWidth: number;
- readonly minimapHeightIsEditorHeight: boolean;
- readonly minimapIsSampling: boolean;
- readonly minimapScale: number;
- readonly minimapLineHeight: number;
- readonly minimapCanvasInnerWidth: number;
- readonly minimapCanvasInnerHeight: number;
- readonly minimapCanvasOuterWidth: number;
- readonly minimapCanvasOuterHeight: number;
- /**
- * Minimap render type
- */
- readonly renderMinimap: RenderMinimap;
+ readonly minimap: EditorMinimapLayoutInfo;
/**
* The number of columns (of typical characters) fitting on a viewport line.
*/
readonly viewportColumn: number;
+ readonly isWordWrapMinified: boolean;
+ readonly isViewportWrapping: boolean;
+ readonly wrappingColumn: number;
/**
* The width of the vertical scrollbar.
*/
@@ -3441,6 +3428,23 @@ declare namespace monaco.editor {
readonly overviewRuler: OverviewRulerPosition;
}
+ /**
+ * The internal layout details of the editor.
+ */
+ export interface EditorMinimapLayoutInfo {
+ readonly renderMinimap: RenderMinimap;
+ readonly minimapLeft: number;
+ readonly minimapWidth: number;
+ readonly minimapHeightIsEditorHeight: boolean;
+ readonly minimapIsSampling: boolean;
+ readonly minimapScale: number;
+ readonly minimapLineHeight: number;
+ readonly minimapCanvasInnerWidth: number;
+ readonly minimapCanvasInnerHeight: number;
+ readonly minimapCanvasOuterWidth: number;
+ readonly minimapCanvasOuterHeight: number;
+ }
+
/**
* Configuration options for editor lightbulb
*/
diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts
index cbd78aeae0..f7fc33dc05 100644
--- a/src/vs/platform/configuration/common/configurationModels.ts
+++ b/src/vs/platform/configuration/common/configurationModels.ts
@@ -324,7 +324,8 @@ export class ConfigurationModelParser {
result[key] = this.filterByScope(properties[key], configurationProperties, false, scopes);
} else {
const scope = this.getScope(key, configurationProperties);
- if (scopes.indexOf(scope) !== -1) {
+ // Load unregistered configurations always.
+ if (scope === undefined || scopes.indexOf(scope) !== -1) {
result[key] = properties[key];
}
}
@@ -332,9 +333,9 @@ export class ConfigurationModelParser {
return result;
}
- private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope {
+ private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope | undefined {
const propertySchema = configurationProperties[key];
- return propertySchema && typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW;
+ return propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined;
}
}
diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts
index 4e19409def..5e742ed518 100644
--- a/src/vs/platform/diagnostics/node/diagnosticsService.ts
+++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts
@@ -5,7 +5,7 @@
import * as osLib from 'os';
import { virtualMachineHint } from 'vs/base/node/id';
import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics';
-import { readdir, stat, exists, readFile } from 'fs';
+import { readdir, exists, readFile } from 'fs';
import { join, basename } from 'vs/base/common/path';
import { parse, ParseError, getNodeType } from 'vs/base/common/json';
import { listProcesses } from 'vs/base/node/ps';
@@ -17,6 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes';
import { IMainProcessInfo } from 'vs/platform/launch/common/launch';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { Iterable } from 'vs/base/common/iterator';
export const ID = 'diagnosticsService';
export const IDiagnosticsService = createDecorator(ID);
@@ -42,24 +43,31 @@ export interface ProcessInfo {
name: string;
}
-export function collectWorkspaceStats(folder: string, filter: string[]): Promise {
- const configFilePatterns = [
- { 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
- { 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
- { 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
- { 'tag': 'package.json', 'pattern': /^package\.json$/i },
- { 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
- { 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
- { 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
- { 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
- { 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
- { 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
- { 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
- { 'tag': 'project.json', 'pattern': /^project\.json$/i },
- { 'tag': 'makefile', 'pattern': /^makefile$/i },
- { 'tag': 'sln', 'pattern': /^.+\.sln$/i },
- { 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
- { 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
+interface ConfigFilePatterns {
+ tag: string;
+ filePattern: RegExp;
+ relativePathPattern?: RegExp;
+}
+
+export async function collectWorkspaceStats(folder: string, filter: string[]): Promise {
+ const configFilePatterns: ConfigFilePatterns[] = [
+ { tag: 'grunt.js', filePattern: /^gruntfile\.js$/i },
+ { tag: 'gulp.js', filePattern: /^gulpfile\.js$/i },
+ { tag: 'tsconfig.json', filePattern: /^tsconfig\.json$/i },
+ { tag: 'package.json', filePattern: /^package\.json$/i },
+ { tag: 'jsconfig.json', filePattern: /^jsconfig\.json$/i },
+ { tag: 'tslint.json', filePattern: /^tslint\.json$/i },
+ { tag: 'eslint.json', filePattern: /^eslint\.json$/i },
+ { tag: 'tasks.json', filePattern: /^tasks\.json$/i },
+ { tag: 'launch.json', filePattern: /^launch\.json$/i },
+ { tag: 'settings.json', filePattern: /^settings\.json$/i },
+ { tag: 'webpack.config.js', filePattern: /^webpack\.config\.js$/i },
+ { tag: 'project.json', filePattern: /^project\.json$/i },
+ { tag: 'makefile', filePattern: /^makefile$/i },
+ { tag: 'sln', filePattern: /^.+\.sln$/i },
+ { tag: 'csproj', filePattern: /^.+\.csproj$/i },
+ { tag: 'cmake', filePattern: /^.+\.cmake$/i },
+ { tag: 'github-actions', filePattern: /^.+\.yml$/i, relativePathPattern: /^\.github(?:\/|\\)workflows$/i }
];
const fileTypes = new Map();
@@ -67,123 +75,91 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Promise
const MAX_FILES = 20000;
- function walk(dir: string, filter: string[], token: { count: number, maxReached: boolean }, done: (allFiles: string[]) => void): void {
- let results: string[] = [];
- readdir(dir, async (err, files) => {
- // Ignore folders that can't be read
- if (err) {
- return done(results);
- }
+ function collect(root: string, dir: string, filter: string[], token: { count: number, maxReached: boolean }): Promise {
+ const relativePath = dir.substring(root.length + 1);
+
+ return new Promise(resolve => {
+ readdir(dir, { withFileTypes: true }, async (err, files) => {
+ // Ignore folders that can't be read
+ if (err) {
+ resolve();
+ return;
+ }
+
+ if (token.count > MAX_FILES) {
+ token.count += files.length;
+ token.maxReached = true;
+ resolve();
+ return;
+ }
+
+ let pending = files.length;
+ if (pending === 0) {
+ resolve();
+ return;
+ }
+
+ let filesToRead = files;
+ if (token.count + files.length > MAX_FILES) {
+ token.maxReached = true;
+ pending = MAX_FILES - token.count;
+ filesToRead = files.slice(0, pending);
+ }
- if (token.count > MAX_FILES) {
token.count += files.length;
- token.maxReached = true;
- return done(results);
- }
- let pending = files.length;
- if (pending === 0) {
- return done(results);
- }
+ for (const file of filesToRead) {
+ if (file.isDirectory()) {
+ if (!filter.includes(file.name)) {
+ await collect(root, join(dir, file.name), filter, token);
+ }
- let filesToRead = files;
- if (token.count + files.length > MAX_FILES) {
- token.maxReached = true;
- pending = MAX_FILES - token.count;
- filesToRead = files.slice(0, pending);
- }
-
- token.count += files.length;
-
- for (const file of filesToRead) {
- stat(join(dir, file), (err, stats) => {
- // Ignore files that can't be read
- if (err) {
if (--pending === 0) {
- return done(results);
+ resolve();
+ return;
}
} else {
- if (stats.isDirectory()) {
- if (filter.indexOf(file) === -1) {
- walk(join(dir, file), filter, token, (res: string[]) => {
- results = results.concat(res);
-
- if (--pending === 0) {
- return done(results);
- }
- });
- } else {
- if (--pending === 0) {
- done(results);
- }
- }
- } else {
- results.push(file);
-
- if (--pending === 0) {
- done(results);
+ const index = file.name.lastIndexOf('.');
+ if (index >= 0) {
+ const fileType = file.name.substring(index + 1);
+ if (fileType) {
+ fileTypes.set(fileType, (fileTypes.get(fileType) ?? 0) + 1);
}
}
+
+ for (const configFile of configFilePatterns) {
+ if (configFile.relativePathPattern?.test(relativePath) !== false && configFile.filePattern.test(file.name)) {
+ configFiles.set(configFile.tag, (configFiles.get(configFile.tag) ?? 0) + 1);
+ }
+ }
+
+ if (--pending === 0) {
+ resolve();
+ return;
+ }
}
- });
- }
+ }
+ });
});
}
- const addFileType = (fileType: string) => {
- if (fileTypes.has(fileType)) {
- fileTypes.set(fileType, fileTypes.get(fileType)! + 1);
- }
- else {
- fileTypes.set(fileType, 1);
- }
- };
-
- const addConfigFiles = (fileName: string) => {
- for (const each of configFilePatterns) {
- if (each.pattern.test(fileName)) {
- if (configFiles.has(each.tag)) {
- configFiles.set(each.tag, configFiles.get(each.tag)! + 1);
- } else {
- configFiles.set(each.tag, 1);
- }
- }
- }
- };
-
- const acceptFile = (name: string) => {
- if (name.lastIndexOf('.') >= 0) {
- const suffix: string | undefined = name.split('.').pop();
- if (suffix) {
- addFileType(suffix);
- }
- }
- addConfigFiles(name);
- };
-
const token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
- return new Promise((resolve, reject) => {
- walk(folder, filter, token, async (files) => {
- files.forEach(acceptFile);
-
- const launchConfigs = await collectLaunchConfigs(folder);
-
- resolve({
- configFiles: asSortedItems(configFiles),
- fileTypes: asSortedItems(fileTypes),
- fileCount: token.count,
- maxFilesReached: token.maxReached,
- launchConfigFiles: launchConfigs
- });
- });
- });
+ await collect(folder, folder, filter, token);
+ const launchConfigs = await collectLaunchConfigs(folder);
+ return {
+ configFiles: asSortedItems(configFiles),
+ fileTypes: asSortedItems(fileTypes),
+ fileCount: token.count,
+ maxFilesReached: token.maxReached,
+ launchConfigFiles: launchConfigs
+ };
}
-function asSortedItems(map: Map): WorkspaceStatItem[] {
- const a: WorkspaceStatItem[] = [];
- map.forEach((value, index) => a.push({ name: index, count: value }));
- return a.sort((a, b) => b.count - a.count);
+function asSortedItems(items: Map): WorkspaceStatItem[] {
+ return [
+ ...Iterable.map(items.entries(), ([name, count]) => ({ name: name, count: count }))
+ ].sort((a, b) => b.count - a.count);
}
export function getMachineInfo(): IMachineInfo {
@@ -540,58 +516,61 @@ export class DiagnosticsService implements IDiagnosticsService {
}
public async reportWorkspaceStats(workspace: IWorkspaceInformation): Promise {
- workspace.folders.forEach(folder => {
- const folderUri = URI.revive(folder.uri);
- if (folderUri.scheme === 'file') {
- const folder = folderUri.fsPath;
- collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => {
- type WorkspaceStatsClassification = {
- 'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
- rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
- };
- type WorkspaceStatsEvent = {
- 'workspace.id': string | undefined;
- rendererSessionId: string;
- };
- this.telemetryService.publicLog2('workspace.stats', {
- 'workspace.id': workspace.telemetryId,
- rendererSessionId: workspace.rendererSessionId
- });
- type WorkspaceStatsFileClassification = {
- rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
- type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
- count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
- };
- type WorkspaceStatsFileEvent = {
- rendererSessionId: string;
- type: string;
- count: number;
- };
- stats.fileTypes.forEach(e => {
- this.telemetryService.publicLog2('workspace.stats.file', {
- rendererSessionId: workspace.rendererSessionId,
- type: e.name,
- count: e.count
- });
- });
- stats.launchConfigFiles.forEach(e => {
- this.telemetryService.publicLog2('workspace.stats.launchConfigFile', {
- rendererSessionId: workspace.rendererSessionId,
- type: e.name,
- count: e.count
- });
- });
- stats.configFiles.forEach(e => {
- this.telemetryService.publicLog2('workspace.stats.configFiles', {
- rendererSessionId: workspace.rendererSessionId,
- type: e.name,
- count: e.count
- });
- });
- }).catch(_ => {
- // Report nothing if collecting metadata fails.
- });
+ for (const { uri } of workspace.folders) {
+ const folderUri = URI.revive(uri);
+ if (folderUri.scheme !== 'file') {
+ continue;
}
- });
+
+ const folder = folderUri.fsPath;
+ try {
+ const stats = await collectWorkspaceStats(folder, ['node_modules', '.git']);
+ type WorkspaceStatsClassification = {
+ 'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
+ rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
+ };
+ type WorkspaceStatsEvent = {
+ 'workspace.id': string | undefined;
+ rendererSessionId: string;
+ };
+ this.telemetryService.publicLog2('workspace.stats', {
+ 'workspace.id': workspace.telemetryId,
+ rendererSessionId: workspace.rendererSessionId
+ });
+ type WorkspaceStatsFileClassification = {
+ rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
+ type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
+ count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
+ };
+ type WorkspaceStatsFileEvent = {
+ rendererSessionId: string;
+ type: string;
+ count: number;
+ };
+ stats.fileTypes.forEach(e => {
+ this.telemetryService.publicLog2('workspace.stats.file', {
+ rendererSessionId: workspace.rendererSessionId,
+ type: e.name,
+ count: e.count
+ });
+ });
+ stats.launchConfigFiles.forEach(e => {
+ this.telemetryService.publicLog2('workspace.stats.launchConfigFile', {
+ rendererSessionId: workspace.rendererSessionId,
+ type: e.name,
+ count: e.count
+ });
+ });
+ stats.configFiles.forEach(e => {
+ this.telemetryService.publicLog2('workspace.stats.configFiles', {
+ rendererSessionId: workspace.rendererSessionId,
+ type: e.name,
+ count: e.count
+ });
+ });
+ } catch {
+ // Report nothing if collecting metadata fails.
+ }
+ }
}
}
diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts
index 389aad6482..5fc94a7d6c 100644
--- a/src/vs/platform/editor/common/editor.ts
+++ b/src/vs/platform/editor/common/editor.ts
@@ -170,6 +170,12 @@ export interface IEditorOptions {
*/
readonly pinned?: boolean;
+ /**
+ * An editor that is sticky moves to the beginning of the editors list within the group and will remain
+ * there unless explicitly closed. Operations such as "Close All" will not close sticky editors.
+ */
+ readonly sticky?: boolean;
+
/**
* The index in the document stack where to insert the editor into when opening.
*/
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index 4f9f165b9a..56e6d3306e 100644
--- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts
@@ -62,6 +62,7 @@ export interface ParsedArgs {
'install-source'?: string;
'disable-updates'?: boolean;
'disable-crash-reporter'?: boolean;
+ 'crash-reporter-directory'?: string;
'skip-add-to-recently-opened'?: boolean;
'max-memory'?: string;
'file-write'?: boolean;
@@ -186,6 +187,7 @@ export const OPTIONS: OptionDescriptions> = {
'disable-telemetry': { type: 'boolean' },
'disable-updates': { type: 'boolean' },
'disable-crash-reporter': { type: 'boolean' },
+ 'crash-reporter-directory': { type: 'string' },
'disable-user-env-probe': { type: 'boolean' },
'skip-add-to-recently-opened': { type: 'boolean' },
'unity-launch': { type: 'boolean' },
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index 0a28995113..f3bd1b75b9 100644
--- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts
@@ -253,6 +253,7 @@ export class EnvironmentService implements INativeEnvironmentService {
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
+ get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; }
get driverHandle(): string | undefined { return this._args['driver']; }
get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts
index 34e5f00b0a..df39033372 100644
--- a/src/vs/vscode.d.ts
+++ b/src/vs/vscode.d.ts
@@ -5452,6 +5452,12 @@ declare module 'vscode' {
*/
readonly extensionPath: string;
+ /**
+ * Gets the extension's environment variable collection for this workspace, enabling changes
+ * to be applied to terminal environment variables.
+ */
+ readonly environmentVariableCollection: EnvironmentVariableCollection;
+
/**
* Get the absolute path of a resource contained in the extension.
*
@@ -8257,6 +8263,113 @@ declare module 'vscode' {
readonly code: number | undefined;
}
+ /**
+ * A type of mutation that can be applied to an environment variable.
+ */
+ export enum EnvironmentVariableMutatorType {
+ /**
+ * Replace the variable's existing value.
+ */
+ Replace = 1,
+ /**
+ * Append to the end of the variable's existing value.
+ */
+ Append = 2,
+ /**
+ * Prepend to the start of the variable's existing value.
+ */
+ Prepend = 3
+ }
+
+ /**
+ * A type of mutation and its value to be applied to an environment variable.
+ */
+ export interface EnvironmentVariableMutator {
+ /**
+ * The type of mutation that will occur to the variable.
+ */
+ readonly type: EnvironmentVariableMutatorType;
+
+ /**
+ * The value to use for the variable.
+ */
+ readonly value: string;
+ }
+
+ /**
+ * A collection of mutations that an extension can apply to a process environment.
+ */
+ export interface EnvironmentVariableCollection {
+ /**
+ * Whether the collection should be cached for the workspace and applied to the terminal
+ * across window reloads. When true the collection will be active immediately such when the
+ * window reloads. Additionally, this API will return the cached version if it exists. The
+ * collection will be invalidated when the extension is uninstalled or when the collection
+ * is cleared. Defaults to true.
+ */
+ persistent: boolean;
+
+ /**
+ * Replace an environment variable with a value.
+ *
+ * Note that an extension can only make a single change to any one variable, so this will
+ * overwrite any previous calls to replace, append or prepend.
+ *
+ * @param variable The variable to replace.
+ * @param value The value to replace the variable with.
+ */
+ replace(variable: string, value: string): void;
+
+ /**
+ * Append a value to an environment variable.
+ *
+ * Note that an extension can only make a single change to any one variable, so this will
+ * overwrite any previous calls to replace, append or prepend.
+ *
+ * @param variable The variable to append to.
+ * @param value The value to append to the variable.
+ */
+ append(variable: string, value: string): void;
+
+ /**
+ * Prepend a value to an environment variable.
+ *
+ * Note that an extension can only make a single change to any one variable, so this will
+ * overwrite any previous calls to replace, append or prepend.
+ *
+ * @param variable The variable to prepend.
+ * @param value The value to prepend to the variable.
+ */
+ prepend(variable: string, value: string): void;
+
+ /**
+ * Gets the mutator that this collection applies to a variable, if any.
+ *
+ * @param variable The variable to get the mutator for.
+ */
+ get(variable: string): EnvironmentVariableMutator | undefined;
+
+ /**
+ * Iterate over each mutator in this collection.
+ *
+ * @param callback Function to execute for each entry.
+ * @param thisArg The `this` context used when invoking the handler function.
+ */
+ forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void;
+
+ /**
+ * Deletes this collection's mutator for a variable.
+ *
+ * @param variable The variable to delete the mutator for.
+ */
+ delete(variable: string): void;
+
+ /**
+ * Clears all mutators from this collection.
+ */
+ clear(): void;
+ }
+
/**
* A location in the editor at which progress information can be shown. It depends on the
* location how progress is visually represented.
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index df34dc8da7..ff2e947766 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -930,103 +930,6 @@ declare module 'vscode' {
//#endregion
- //#region Contribute to terminal environment https://github.com/microsoft/vscode/issues/46696
-
- export enum EnvironmentVariableMutatorType {
- /**
- * Replace the variable's existing value.
- */
- Replace = 1,
- /**
- * Append to the end of the variable's existing value.
- */
- Append = 2,
- /**
- * Prepend to the start of the variable's existing value.
- */
- Prepend = 3
- }
-
- export interface EnvironmentVariableMutator {
- /**
- * The type of mutation that will occur to the variable.
- */
- readonly type: EnvironmentVariableMutatorType;
-
- /**
- * The value to use for the variable.
- */
- readonly value: string;
- }
-
- /**
- * A collection of mutations that an extension can apply to a process environment.
- */
- export interface EnvironmentVariableCollection {
- /**
- * Whether the collection should be cached for the workspace and applied to the terminal
- * across window reloads. When true the collection will be active immediately such when the
- * window reloads. Additionally, this API will return the cached version if it exists. The
- * collection will be invalidated when the extension is uninstalled or when the collection
- * is cleared. Defaults to true.
- */
- persistent: boolean;
-
- /**
- * Replace an environment variable with a value.
- *
- * Note that an extension can only make a single change to any one variable, so this will
- * overwrite any previous calls to replace, append or prepend.
- */
- replace(variable: string, value: string): void;
-
- /**
- * Append a value to an environment variable.
- *
- * Note that an extension can only make a single change to any one variable, so this will
- * overwrite any previous calls to replace, append or prepend.
- */
- append(variable: string, value: string): void;
-
- /**
- * Prepend a value to an environment variable.
- *
- * Note that an extension can only make a single change to any one variable, so this will
- * overwrite any previous calls to replace, append or prepend.
- */
- prepend(variable: string, value: string): void;
-
- /**
- * Gets the mutator that this collection applies to a variable, if any.
- */
- get(variable: string): EnvironmentVariableMutator | undefined;
-
- /**
- * Iterate over each mutator in this collection.
- */
- forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void;
-
- /**
- * Deletes this collection's mutator for a variable.
- */
- delete(variable: string): void;
-
- /**
- * Clears all mutators from this collection.
- */
- clear(): void;
- }
-
- export interface ExtensionContext {
- /**
- * Gets the extension's environment variable collection for this workspace, enabling changes
- * to be applied to terminal environment variables.
- */
- readonly environmentVariableCollection: EnvironmentVariableCollection;
- }
-
- //#endregion
-
//#region @jrieken -> exclusive document filters
export interface DocumentFilter {
@@ -1737,12 +1640,6 @@ declare module 'vscode' {
edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable;
}
- export interface NotebookProvider {
- resolveNotebook(editor: NotebookEditor): Promise;
- executeCell(document: NotebookDocument, cell: NotebookCell | undefined, token: CancellationToken): Promise;
- save(document: NotebookDocument): Promise;
- }
-
export interface NotebookOutputSelector {
type: string;
subTypes?: string[];
@@ -1805,11 +1702,6 @@ declare module 'vscode' {
provider: NotebookContentProvider
): Disposable;
- export function registerNotebookProvider(
- notebookType: string,
- provider: NotebookProvider
- ): Disposable;
-
export function registerNotebookOutputRenderer(
type: string,
outputSelector: NotebookOutputSelector,
@@ -1839,6 +1731,43 @@ declare module 'vscode' {
//#endregion
+ //#region @connor4312 extension mode: https://github.com/microsoft/vscode/issues/95926
+
+ /**
+ * The ExtensionMode is provided on the `ExtensionContext` and indicates the
+ * mode the specific extension is running in.
+ */
+ export enum ExtensionMode {
+ /**
+ * The extension is installed normally (for example, from the marketplace
+ * or VSIX) in VS Code.
+ */
+ Release = 1,
+
+ /**
+ * The extension is running from an `--extensionDevelopmentPath` provided
+ * when launching VS Code.
+ */
+ Development = 2,
+
+ /**
+ * The extension is running from an `--extensionDevelopmentPath` and
+ * the extension host is running unit tests.
+ */
+ Test = 3,
+ }
+
+ export interface ExtensionContext {
+ /**
+ * The mode the extension is running in. This is specific to the current
+ * extension. One extension may be in `ExtensionMode.Development` while
+ * other extensions in the host run in `ExtensionMode.Release`.
+ */
+ readonly extensionMode: ExtensionMode;
+ }
+
+ //#endregion
+
//#region https://github.com/microsoft/vscode/issues/39441
export interface CompletionItem {
diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts
index 7f658546f0..fc4fbf4fcd 100644
--- a/src/vs/workbench/api/browser/mainThreadNotebook.ts
+++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts
@@ -8,7 +8,7 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx
import { Disposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
-import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -84,7 +84,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
registerListeners() {
this._register(this._notebookService.onDidChangeActiveEditor(e => {
- this._proxy.$updateActiveEditor(e.viewType, e.uri);
+ this._proxy.$acceptDocumentAndEditorsDelta({
+ newActiveEditor: e.uri
+ });
}));
const updateOrder = () => {
@@ -129,16 +131,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return;
}
- async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise {
- let controller = this._notebookProviders.get(viewType);
-
- if (controller) {
- controller.createNotebookDocument(handle, viewType, resource);
- }
-
- return;
- }
-
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise {
let controller = this._notebookProviders.get(viewType);
@@ -163,11 +155,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
}
- async resolveNotebook(viewType: string, uri: URI): Promise {
- let handle = await this._proxy.$resolveNotebook(viewType, uri);
- return handle;
- }
-
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise {
let controller = this._notebookProviders.get(viewType);
controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
@@ -195,6 +182,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
export class MainThreadNotebookController implements IMainNotebookController {
private _mapping: Map = new Map();
+ static documentHandle: number = 0;
constructor(
private readonly _proxy: ExtHostNotebookShape,
@@ -203,26 +191,44 @@ export class MainThreadNotebookController implements IMainNotebookController {
) {
}
- async resolveNotebook(viewType: string, uri: URI): Promise {
- // TODO: resolve notebook should wait for all notebook document destory operations to finish.
+ async createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
if (mainthreadNotebook) {
+ if (forceReload) {
+ const data = await this._proxy.$resolveNotebookData(viewType, uri);
+ if (!data) {
+ return undefined; // {{SQL CARBON EDIT}}
+ }
+
+ mainthreadNotebook.textModel.languages = data.languages;
+ mainthreadNotebook.textModel.metadata = data.metadata;
+ mainthreadNotebook.textModel.applyEdit(mainthreadNotebook.textModel.versionId, [
+ { editType: CellEditType.Delete, count: mainthreadNotebook.textModel.cells.length, index: 0 },
+ { editType: CellEditType.Insert, index: 0, cells: data.cells }
+ ]);
+ }
return mainthreadNotebook.textModel;
}
- let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri);
- if (notebookHandle !== undefined) {
- mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
- if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) {
- // it's empty, we should create an empty template one
- const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined);
- mainthreadNotebook.textModel.insertTemplateCell(mainCell);
- }
- return mainthreadNotebook?.textModel;
+ let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, uri);
+ await this.createNotebookDocument(document);
+
+ if (forBackup) {
+ return document.textModel;
}
- return undefined;
+ // open notebook document
+ const data = await this._proxy.$resolveNotebookData(viewType, uri);
+ if (!data) {
+ return undefined; // {{SQL CARBON EDIT}}
+ }
+
+ document.textModel.languages = data.languages;
+ document.textModel.metadata = data.metadata;
+ document.textModel.initialize(data!.cells);
+
+ return document.textModel;
}
async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise {
@@ -250,12 +256,32 @@ export class MainThreadNotebookController implements IMainNotebookController {
this._proxy.$onDidReceiveMessage(uri, message);
}
- // Methods for ExtHost
- async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise {
- let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource));
- this._mapping.set(URI.revive(resource).toString(), document);
+ async createNotebookDocument(document: MainThreadNotebookDocument): Promise {
+ this._mapping.set(document.uri.toString(), document);
+
+ await this._proxy.$acceptDocumentAndEditorsDelta({
+ addedDocuments: [{
+ viewType: document.viewType,
+ handle: document.handle,
+ uri: document.uri
+ }]
+ });
}
+ async removeNotebookDocument(notebook: INotebookTextModel): Promise {
+ let document = this._mapping.get(URI.from(notebook.uri).toString());
+
+ if (!document) {
+ return;
+ }
+
+ await this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [notebook.uri] });
+ document.dispose();
+ this._mapping.delete(URI.from(notebook.uri).toString());
+ }
+
+ // Methods for ExtHost
+
updateLanguages(resource: UriComponents, languages: string[]) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateLanguages(languages);
@@ -280,21 +306,12 @@ export class MainThreadNotebookController implements IMainNotebookController {
return this._proxy.$executeNotebook(this._viewType, uri, handle, token);
}
- async destoryNotebookDocument(notebook: INotebookTextModel): Promise {
- let document = this._mapping.get(URI.from(notebook.uri).toString());
-
- if (!document) {
- return;
- }
-
- let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri);
- if (removeFromExtHost) {
- document.dispose();
- this._mapping.delete(URI.from(notebook.uri).toString());
- }
- }
-
async save(uri: URI, token: CancellationToken): Promise {
return this._proxy.$saveNotebook(this._viewType, uri, token);
}
+
+ async saveAs(uri: URI, target: URI, token: CancellationToken): Promise {
+ return this._proxy.$saveNotebookAs(this._viewType, uri, target, token);
+
+ }
}
diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts
index a4385b7929..f5cf39b33c 100644
--- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts
+++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts
@@ -368,6 +368,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
private addViews(extensions: readonly IExtensionPointUser[]): void {
+ const viewIds: Set = new Set();
+ const allViewDescriptors: { views: IViewDescriptor[], viewContainer: ViewContainer }[] = [];
+
for (const extension of extensions) {
const { value, collector } = extension;
@@ -386,10 +389,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key));
}
const container = viewContainer || this.getDefaultViewContainer();
- const viewIds: string[] = [];
const viewDescriptors = coalesce(entry.value.map((item, index) => {
// validate
- if (viewIds.indexOf(item.id) !== -1) {
+ if (viewIds.has(item.id)) {
collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}`", item.id));
return null;
}
@@ -421,12 +423,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
remoteAuthority: item.remoteName || (item).remoteAuthority // TODO@roblou - delete after remote extensions are updated
};
- viewIds.push(viewDescriptor.id);
+ viewIds.add(viewDescriptor.id);
return viewDescriptor;
}));
- this.viewsRegistry.registerViews(viewDescriptors, container);
+
+ allViewDescriptors.push({ viewContainer: container, views: viewDescriptors });
+
});
}
+
+ this.viewsRegistry.registerViews2(allViewDescriptors);
}
private getDefaultViewContainer(): ViewContainer {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 89af44294d..9fdfd2fc92 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -931,10 +931,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidCloseNotebookDocument;
},
- registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
- checkProposedApiEnabled(extension);
- return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
- },
registerNotebookContentProvider: (viewType: string, provider: vscode.NotebookContentProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider);
@@ -1015,6 +1011,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
EvaluatableExpression: extHostTypes.EvaluatableExpression,
EventEmitter: Emitter,
ExtensionKind: extHostTypes.ExtensionKind,
+ ExtensionMode: extHostTypes.ExtensionMode,
CustomExecution: extHostTypes.CustomExecution,
CustomExecution2: extHostTypes.CustomExecution,
FileChangeType: extHostTypes.FileChangeType,
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index b8487f6d5d..8d8db3c9f0 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
-import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
@@ -695,7 +695,6 @@ export interface MainThreadNotebookShape extends IDisposable {
$unregisterNotebookProvider(viewType: string): Promise;
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise;
$unregisterNotebookRenderer(handle: number): Promise;
- $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise;
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise;
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise;
@@ -1542,16 +1541,31 @@ export interface INotebookEditorPropertiesChangeData {
selections: INotebookSelectionChangeEvent | null;
}
+export interface INotebookModelAddedData {
+ uri: UriComponents;
+ handle: number;
+ // versionId: number;
+ viewType: string;
+}
+
+export interface INotebookDocumentsAndEditorsDelta {
+ removedDocuments?: UriComponents[];
+ addedDocuments?: INotebookModelAddedData[];
+ // removedEditors?: string[];
+ // addedEditors?: ITextEditorAddData[];
+ newActiveEditor?: UriComponents | null;
+}
+
export interface ExtHostNotebookShape {
- $resolveNotebook(viewType: string, uri: UriComponents): Promise;
+ $resolveNotebookData(viewType: string, uri: UriComponents): Promise;
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise;
- $updateActiveEditor(viewType: string, uri: UriComponents): Promise;
- $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise;
+ $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise;
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
$onDidReceiveMessage(uri: UriComponents, message: any): void;
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
+ $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise;
}
export interface ExtHostStorageShape {
diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts
index 055fefef06..dc71c421a7 100644
--- a/src/vs/workbench/api/common/extHostExtensionService.ts
+++ b/src/vs/workbench/api/common/extHostExtensionService.ts
@@ -25,7 +25,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
import { Schemas } from 'vs/base/common/network';
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
-import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes';
+import { RemoteAuthorityResolverError, ExtensionMode } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
@@ -358,8 +358,12 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage);
const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage);
+ const extensionMode = extensionDescription.isUnderDevelopment
+ ? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
+ : ExtensionMode.Release;
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`);
+
return Promise.all([
globalState.whenReady,
workspaceState.whenReady,
@@ -376,10 +380,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); },
asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); },
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
- get environmentVariableCollection() {
+ get extensionMode() {
checkProposedApiEnabled(extensionDescription);
- return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription);
- }
+ return extensionMode;
+ },
+ get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
});
});
}
diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts
index 7d6945ad8a..1f01961b11 100644
--- a/src/vs/workbench/api/common/extHostNotebook.ts
+++ b/src/vs/workbench/api/common/extHostNotebook.ts
@@ -10,10 +10,10 @@ import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecyc
import { ISplice } from 'vs/base/common/sequence';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
-import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol';
+import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData, INotebookDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
-import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType, NotebookDataDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Disposable as VSCodeDisposable } from './extHostTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData';
@@ -345,8 +345,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
let mimeTypes = Object.keys(output.data);
-
- // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side.
let coreDisplayOrder = this.renderingHandler.outputDisplayOrder;
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []);
@@ -415,7 +413,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
}
}
-export class NotebookEditorCellEdit {
+export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit {
private _finalized: boolean = false;
private readonly _documentVersionId: number;
private _collectedEdits: ICellEditOperation[] = [];
@@ -526,13 +524,13 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
}));
}
- edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable {
- const edit = new NotebookEditorCellEdit(this);
+ edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable {
+ const edit = new NotebookEditorCellEditBuilder(this);
callback(edit);
return this._applyEdit(edit);
}
- private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise {
+ private _applyEdit(editBuilder: NotebookEditorCellEditBuilder): Promise {
const editData = editBuilder.finalize();
// return when there is nothing to do
@@ -625,7 +623,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private static _handlePool: number = 0;
private readonly _proxy: MainThreadNotebookShape;
- private readonly _notebookProviders = new Map();
private readonly _notebookContentProviders = new Map();
private readonly _documents = new Map();
private readonly _editors = new Map; }>();
@@ -706,31 +703,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return matches;
}
- registerNotebookProvider(
- extension: IExtensionDescription,
- viewType: string,
- provider: vscode.NotebookProvider,
- ): vscode.Disposable {
-
- if (this._notebookProviders.has(viewType)) {
- throw new Error(`Notebook provider for '${viewType}' already registered`);
- }
-
- this._notebookProviders.set(viewType, { extension, provider });
- this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
- return new VSCodeDisposable(() => {
- this._notebookProviders.delete(viewType);
- this._proxy.$unregisterNotebookProvider(viewType);
- });
- }
-
registerNotebookContentProvider(
extension: IExtensionDescription,
viewType: string,
provider: vscode.NotebookContentProvider,
): vscode.Disposable {
- if (this._notebookProviders.has(viewType)) {
+ if (this._notebookContentProviders.has(viewType)) {
throw new Error(`Notebook provider for '${viewType}' already registered`);
}
@@ -742,97 +721,99 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
});
}
- async _resolveNotebookFromContentProvider(viewType: string, uri: UriComponents): Promise {
+ async $resolveNotebookData(viewType: string, uri: UriComponents): Promise {
let provider = this._notebookContentProviders.get(viewType);
+ let document = this._documents.get(URI.revive(uri).toString());
- if (provider) {
- const revivedUri = URI.revive(uri);
- if (!this._documents.has(revivedUri.toString())) {
- let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
- await this._proxy.$createNotebookDocument(
- document.handle,
- viewType,
- uri
- );
+ if (provider && document) {
+ const rawCells = await provider.provider.openNotebook(URI.revive(uri));
+ const renderers = new Set();
+ const dto = {
+ metadata: {
+ ...notebookDocumentMetadataDefaults,
+ ...rawCells.metadata
+ },
+ languages: rawCells.languages,
+ cells: rawCells.cells.map(cell => {
+ let transformedOutputs = cell.outputs.map(output => {
+ if (output.outputKind === CellOutputKind.Rich) {
+ // TODO display string[]
+ const ret = this._transformMimeTypes(document!, (rawCells.metadata.displayOrder as string[]) || [], output);
- this._documents.set(revivedUri.toString(), document);
- }
+ if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
+ renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
+ }
+ return ret;
+ } else {
+ return output as IStreamOutput | IErrorOutput;
+ }
+ });
- const onDidReceiveMessage = new Emitter();
-
- let editor = new ExtHostNotebookEditor(
- viewType,
- `${ExtHostNotebookController._handlePool++}`,
- revivedUri,
- this._proxy,
- onDidReceiveMessage,
- this._documents.get(revivedUri.toString())!,
- this._documentsAndEditors
- );
-
- this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
-
- const data = await provider.provider.openNotebook(revivedUri);
- editor.document.languages = data.languages;
- editor.document.metadata = {
- ...notebookDocumentMetadataDefaults,
- ...data.metadata
+ return {
+ language: cell.language,
+ cellKind: cell.cellKind,
+ metadata: cell.metadata,
+ source: cell.source,
+ outputs: transformedOutputs
+ };
+ })
};
- await editor.edit(editBuilder => {
- for (let i = 0; i < data.cells.length; i++) {
- const cell = data.cells[i];
- editBuilder.insert(0, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata);
- }
- });
-
- this._onDidOpenNotebookDocument.fire(editor.document);
- return editor.document.handle;
- } else {
- return Promise.resolve(undefined);
+ return dto;
}
+
+ return undefined; // {{SQL CARBON EDIT}}
}
- async $resolveNotebook(viewType: string, uri: UriComponents): Promise {
- let notebookFromNotebookContentProvider = await this._resolveNotebookFromContentProvider(viewType, uri);
+ private _transformMimeTypes(document: ExtHostNotebookDocument, displayOrder: string[], output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
+ let mimeTypes = Object.keys(output.data);
+ let coreDisplayOrder = this.outputDisplayOrder;
+ const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], displayOrder, coreDisplayOrder?.defaultOrder || []);
- if (notebookFromNotebookContentProvider !== undefined) {
- return notebookFromNotebookContentProvider;
- }
+ let orderMimeTypes: IOrderedMimeType[] = [];
- let provider = this._notebookProviders.get(viewType);
+ sorted.forEach(mimeType => {
+ let handlers = this.findBestMatchedRenderer(mimeType);
- if (provider) {
- if (!this._documents.has(URI.revive(uri).toString())) {
- let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this);
- await this._proxy.$createNotebookDocument(
- document.handle,
- viewType,
- uri
- );
+ if (handlers.length) {
+ let renderedOutput = handlers[0].render(document, output, mimeType);
- this._documents.set(URI.revive(uri).toString(), document);
+ orderMimeTypes.push({
+ mimeType: mimeType,
+ isResolved: true,
+ rendererId: handlers[0].handle,
+ output: renderedOutput
+ });
+
+ for (let i = 1; i < handlers.length; i++) {
+ orderMimeTypes.push({
+ mimeType: mimeType,
+ isResolved: false,
+ rendererId: handlers[i].handle
+ });
+ }
+
+ if (mimeTypeSupportedByCore(mimeType)) {
+ orderMimeTypes.push({
+ mimeType: mimeType,
+ isResolved: false,
+ rendererId: -1
+ });
+ }
+ } else {
+ orderMimeTypes.push({
+ mimeType: mimeType,
+ isResolved: false
+ });
}
+ });
- const onDidReceiveMessage = new Emitter();
-
- let editor = new ExtHostNotebookEditor(
- viewType,
- `${ExtHostNotebookController._handlePool++}`,
- URI.revive(uri),
- this._proxy,
- onDidReceiveMessage,
- this._documents.get(URI.revive(uri).toString())!,
- this._documentsAndEditors
- );
-
- this._editors.set(URI.revive(uri).toString(), { editor, onDidReceiveMessage });
- await provider.provider.resolveNotebook(editor);
- // await editor.document.$updateCells();
- return editor.document.handle;
- }
-
- return Promise.resolve(undefined);
+ return {
+ outputKind: output.outputKind,
+ data: output.data,
+ orderedMimeTypes: orderMimeTypes,
+ pickedMimeTypeIndex: 0
+ };
}
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined, token: CancellationToken): Promise {
@@ -847,15 +828,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return this._notebookContentProviders.get(viewType)!.provider.executeCell(document, cell, token);
}
-
- let provider = this._notebookProviders.get(viewType);
-
- if (!provider) {
- return;
- }
-
- let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
- return provider.provider.executeCell(document!, cell, token);
}
async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise {
@@ -874,44 +846,26 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return true;
}
- let provider = this._notebookProviders.get(viewType);
-
- if (provider && document) {
- return await provider.provider.save(document);
- }
-
return false;
}
- async $updateActiveEditor(viewType: string, uri: UriComponents): Promise {
- this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString());
- this._activeNotebookEditor = this._editors.get(URI.revive(uri).toString())?.editor;
- }
-
- async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise {
- let provider = this._notebookProviders.get(viewType);
-
- if (!provider) {
+ async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise {
+ let document = this._documents.get(URI.revive(uri).toString());
+ if (!document) {
return false;
}
- let document = this._documents.get(URI.revive(uri).toString());
+ if (this._notebookContentProviders.has(viewType)) {
+ try {
+ await this._notebookContentProviders.get(viewType)!.provider.saveNotebookAs(URI.revive(target), document, token);
+ } catch (e) {
+ return false;
+ }
- if (document) {
- document.dispose();
- this._documents.delete(URI.revive(uri).toString());
- this._onDidCloseNotebookDocument.fire(document);
+ return true;
}
- let editor = this._editors.get(URI.revive(uri).toString());
-
- if (editor) {
- editor.editor.dispose();
- editor.onDidReceiveMessage.dispose();
- this._editors.delete(URI.revive(uri).toString());
- }
-
- return true;
+ return false;
}
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void {
@@ -957,4 +911,60 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
}
}
+
+ async $acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta) {
+ if (delta.removedDocuments) {
+ delta.removedDocuments.forEach((uri) => {
+ let document = this._documents.get(URI.revive(uri).toString());
+
+ if (document) {
+ document.dispose();
+ this._documents.delete(URI.revive(uri).toString());
+ this._onDidCloseNotebookDocument.fire(document);
+ }
+
+ let editor = this._editors.get(URI.revive(uri).toString());
+
+ if (editor) {
+ editor.editor.dispose();
+ editor.onDidReceiveMessage.dispose();
+ this._editors.delete(URI.revive(uri).toString());
+ }
+ });
+ }
+
+ if (delta.addedDocuments) {
+ delta.addedDocuments.forEach(modelData => {
+ const revivedUri = URI.revive(modelData.uri);
+ const viewType = modelData.viewType;
+ if (!this._documents.has(revivedUri.toString())) {
+ let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, revivedUri, this);
+ this._documents.set(revivedUri.toString(), document);
+ }
+
+ const onDidReceiveMessage = new Emitter();
+ const document = this._documents.get(revivedUri.toString())!;
+
+ let editor = new ExtHostNotebookEditor(
+ viewType,
+ `${ExtHostNotebookController._handlePool++}`,
+ revivedUri,
+ this._proxy,
+ onDidReceiveMessage,
+ document,
+ this._documentsAndEditors
+ );
+
+ this._onDidOpenNotebookDocument.fire(document);
+
+ // TODO, does it already exist?
+ this._editors.set(revivedUri.toString(), { editor, onDidReceiveMessage });
+ });
+ }
+
+ if (delta.newActiveEditor) {
+ this._activeNotebookDocument = this._documents.get(URI.revive(delta.newActiveEditor).toString());
+ this._activeNotebookEditor = this._editors.get(URI.revive(delta.newActiveEditor).toString())?.editor;
+ }
+ }
}
diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts
index 5e27ac4eff..0e734ba902 100644
--- a/src/vs/workbench/api/common/extHostTypes.ts
+++ b/src/vs/workbench/api/common/extHostTypes.ts
@@ -2742,3 +2742,27 @@ export class TimelineItem implements vscode.TimelineItem {
}
//#endregion Timeline
+
+//#region ExtensionContext
+
+export enum ExtensionMode {
+ /**
+ * The extension is installed normally (for example, from the marketplace
+ * or VSIX) in VS Code.
+ */
+ Release = 1,
+
+ /**
+ * The extension is running from an `--extensionDevelopmentPath` provided
+ * when launching VS Code.
+ */
+ Development = 2,
+
+ /**
+ * The extension is running from an `--extensionDevelopmentPath` and
+ * the extension host is running unit tests.
+ */
+ Test = 3,
+}
+
+//#endregion ExtensionContext
diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts
index 4077020a39..9e8f5aacdb 100644
--- a/src/vs/workbench/api/common/menusExtensionPoint.ts
+++ b/src/vs/workbench/api/common/menusExtensionPoint.ts
@@ -37,7 +37,6 @@ namespace schema {
case 'debug/callstack/context': return MenuId.DebugCallStackContext;
case 'debug/toolbar': return MenuId.DebugToolBar;
case 'debug/toolBar': return MenuId.DebugToolBar;
- case 'menuBar/file': return MenuId.MenubarFileMenu;
case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu;
case 'scm/title': return MenuId.SCMTitle;
case 'scm/sourceControl': return MenuId.SCMSourceControl;
@@ -70,7 +69,6 @@ namespace schema {
export function isProposedAPI(menuId: MenuId): boolean {
switch (menuId) {
case MenuId.StatusBarWindowIndicatorMenu:
- case MenuId.MenubarFileMenu:
case MenuId.MenubarWebNavigationMenu:
return true;
}
diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts
index 6feda76860..1ab607e76c 100644
--- a/src/vs/workbench/api/node/extHostTerminalService.ts
+++ b/src/vs/workbench/api/node/extHostTerminalService.ts
@@ -230,13 +230,10 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
public getEnvironmentVariableCollection(extension: IExtensionDescription): vscode.EnvironmentVariableCollection {
let collection = this._environmentVariableCollections.get(extension.identifier.value);
-
if (!collection) {
- // TODO: Disable dispose
collection = new EnvironmentVariableCollection();
this._setEnvironmentVariableCollection(extension.identifier.value, collection);
}
-
return collection;
}
diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts
index a45cbc3b33..5889c7a30d 100644
--- a/src/vs/workbench/browser/actions/navigationActions.ts
+++ b/src/vs/workbench/browser/actions/navigationActions.ts
@@ -202,8 +202,9 @@ class NavigateDownAction extends BaseNavigationAction {
}
function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Parts, next: boolean): Parts {
- const neighbour = part === Parts.EDITOR_PART ? (next ? Parts.STATUSBAR_PART : Parts.PANEL_PART) : part === Parts.STATUSBAR_PART ? (next ? Parts.SIDEBAR_PART : Parts.EDITOR_PART) :
- part === Parts.SIDEBAR_PART ? (next ? Parts.PANEL_PART : Parts.STATUSBAR_PART) : part === Parts.PANEL_PART ? (next ? Parts.EDITOR_PART : Parts.SIDEBAR_PART) : Parts.EDITOR_PART;
+ const neighbour = part === Parts.EDITOR_PART ? (next ? Parts.STATUSBAR_PART : Parts.PANEL_PART) : part === Parts.STATUSBAR_PART ? (next ? Parts.ACTIVITYBAR_PART : Parts.EDITOR_PART) :
+ part === Parts.ACTIVITYBAR_PART ? (next ? Parts.SIDEBAR_PART : Parts.STATUSBAR_PART) : part === Parts.SIDEBAR_PART ? (next ? Parts.PANEL_PART : Parts.ACTIVITYBAR_PART) :
+ part === Parts.PANEL_PART ? (next ? Parts.EDITOR_PART : Parts.SIDEBAR_PART) : Parts.EDITOR_PART;
if (layoutService.isVisible(neighbour) || neighbour === Parts.EDITOR_PART) {
return neighbour;
}
@@ -212,8 +213,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
}
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, next: boolean): void {
- const currentlyFocusedPart = layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART :
- layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
+ const currentlyFocusedPart = layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
+ layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
let partToFocus = Parts.EDITOR_PART;
if (currentlyFocusedPart) {
partToFocus = findVisibleNeighbour(layoutService, currentlyFocusedPart, next);
diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts
index b9c31c0e36..61e3973c11 100644
--- a/src/vs/workbench/browser/dnd.ts
+++ b/src/vs/workbench/browser/dnd.ts
@@ -11,7 +11,7 @@ import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { URI } from 'vs/base/common/uri';
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { Schemas } from 'vs/base/common/network';
-import { IEditorViewState } from 'vs/editor/common/editorCommon';
+import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { normalizeDriveLetter } from 'vs/base/common/labels';
@@ -58,7 +58,7 @@ export interface IDraggedEditor extends IDraggedResource {
content?: string;
encoding?: string;
mode?: string;
- viewState?: IEditorViewState;
+ options?: ITextEditorOptions;
}
export interface ISerializedDraggedEditor {
@@ -66,7 +66,7 @@ export interface ISerializedDraggedEditor {
content?: string;
encoding?: string;
mode?: string;
- viewState?: IEditorViewState;
+ options?: ITextEditorOptions;
}
export const CodeDataTransfers = {
@@ -90,7 +90,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array ITextEditorOptions) | undefined, event: DragMouseEvent | DragEvent): void {
if (resources.length === 0 || !event.dataTransfer) {
return;
}
@@ -346,18 +346,30 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
const draggedEditors: ISerializedDraggedEditor[] = [];
files.forEach(file => {
+ let options: ITextEditorOptions | undefined = undefined;
- // Try to find editor view state from the visible editors that match given resource
- let viewState: IEditorViewState | undefined = undefined;
- const textEditorControls = editorService.visibleTextEditorControls;
- for (const textEditorControl of textEditorControls) {
- if (isCodeEditor(textEditorControl)) {
- const model = textEditorControl.getModel();
- if (model?.uri?.toString() === file.resource.toString()) {
- viewState = withNullAsUndefined(textEditorControl.saveViewState());
- break;
- }
- }
+ // Use provided callback for editor options
+ if (typeof optionsCallback === 'function') {
+ options = optionsCallback(file.resource);
+ }
+
+ // Otherwise try to figure out the view state from opened editors that match
+ else {
+ options = {
+ viewState: (() => {
+ const textEditorControls = editorService.visibleTextEditorControls;
+ for (const textEditorControl of textEditorControls) {
+ if (isCodeEditor(textEditorControl)) {
+ const model = textEditorControl.getModel();
+ if (model?.uri?.toString() === file.resource.toString()) {
+ return withNullAsUndefined(textEditorControl.saveViewState());
+ }
+ }
+ }
+
+ return undefined;
+ })()
+ };
}
// Try to find encoding and mode from text model
@@ -378,7 +390,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
}
// Add as dragged editor
- draggedEditors.push({ resource: file.resource.toString(), content, viewState, encoding, mode });
+ draggedEditors.push({ resource: file.resource.toString(), content, options, encoding, mode });
});
if (draggedEditors.length) {
diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts
index 701ff0bc29..3e551bac6b 100644
--- a/src/vs/workbench/browser/labels.ts
+++ b/src/vs/workbench/browser/labels.ts
@@ -42,8 +42,21 @@ function toResource(props: IResourceLabelProps | undefined): URI | undefined {
}
export interface IResourceLabelOptions extends IIconLabelValueOptions {
+
+ /**
+ * A hint to the file kind of the resource.
+ */
fileKind?: FileKind;
+
+ /**
+ * File decorations to use for the label.
+ */
fileDecorations?: { colors: boolean, badges: boolean };
+
+ /**
+ * Will take the provided label as is and e.g. not override it for untitled files.
+ */
+ forceLabel?: boolean;
}
export interface IFileLabelOptions extends IResourceLabelOptions {
@@ -368,7 +381,7 @@ class ResourceLabelWidget extends IconLabel {
/*const resource = toResource(label); {{SQL CARBON EDIT}} we don't want to special case untitled files
const isMasterDetail = label?.resource && !URI.isUri(label.resource);
- if (!isMasterDetail && resource?.scheme === Schemas.untitled) {
+ if (!options.forceLabel && !isMasterDetail && resource?.scheme === Schemas.untitled) {
// Untitled labels are very dynamic because they may change
// whenever the content changes (unless a path is associated).
// As such we always ask the actual editor for it's name and
@@ -528,7 +541,6 @@ class ResourceLabelWidget extends IconLabel {
);
if (deco) {
-
this.renderDisposables.add(deco);
if (deco.tooltip) {
diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts
index 593e3371e2..318e96ed07 100644
--- a/src/vs/workbench/browser/layout.ts
+++ b/src/vs/workbench/browser/layout.ts
@@ -177,6 +177,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private backupFileService!: IBackupFileService;
private notificationService!: INotificationService;
private themeService!: IThemeService;
+ private activityBarService!: IActivityBarService;
protected readonly state = {
fullscreen: false,
@@ -260,8 +261,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.viewDescriptorService = accessor.get(IViewDescriptorService);
this.titleService = accessor.get(ITitleService);
this.notificationService = accessor.get(INotificationService);
+ this.activityBarService = accessor.get(IActivityBarService);
accessor.get(IStatusbarService); // not used, but called to ensure instantiated
- accessor.get(IActivityBarService); // not used, but called to ensure instantiated
// Listeners
this.registerLayoutListeners();
@@ -846,6 +847,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
activeViewlet.focus();
}
break;
+ case Parts.ACTIVITYBAR_PART:
+ this.activityBarService.focusActivityBar();
+ break;
default:
// Status Bar, Activity Bar and Title Bar simply pass focus to container
const container = this.getContainer(part);
diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css
index 389b283c40..32889c8a07 100644
--- a/src/vs/workbench/browser/media/part.css
+++ b/src/vs/workbench/browser/media/part.css
@@ -10,7 +10,6 @@
.monaco-workbench .part > .drop-block-overlay.visible {
visibility: visible;
- opacity: 1;
}
.monaco-workbench .part > .drop-block-overlay {
@@ -18,10 +17,8 @@
top: 0;
width: 100%;
height: 100%;
- backdrop-filter: brightness(97%) blur(2px);
visibility: hidden;
opacity: 0;
- transition: opacity .5s, visibility .5s;
z-index: 10;
}
diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
index 3f72b4249a..a046a2b2a5 100644
--- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
+++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts
@@ -168,6 +168,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.registerListeners();
}
+ focusActivityBar(): void {
+ this.compositeBar.focus();
+ }
+
private registerListeners(): void {
// View Container Changes
diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts
index 4fcb0e24d6..e144f9914f 100644
--- a/src/vs/workbench/browser/parts/compositeBar.ts
+++ b/src/vs/workbench/browser/parts/compositeBar.ts
@@ -66,8 +66,6 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
if (targetCompositeId) {
this.moveComposite(currentContainer.id, targetCompositeId, before);
}
-
- this.openComposite(currentContainer.id, true);
}
}
@@ -255,6 +253,12 @@ export class CompositeBar extends Widget implements ICompositeBar {
return actionBarDiv;
}
+ focus(): void {
+ if (this.compositeSwitcherBar) {
+ this.compositeSwitcherBar.focus();
+ }
+ }
+
layout(dimension: Dimension): void {
this.dimension = dimension;
if (dimension.height === 0 || dimension.width === 0) {
@@ -275,8 +279,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
// Add to the model
if (this.model.add(id, name, order)) {
this.computeSizes([this.model.findItem(id)]);
- // Set timeout helps prevent flicker
- setTimeout(() => this.updateCompositeSwitcher(), 0);
+ this.updateCompositeSwitcher();
}
}
diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts
index 14a5c6438d..ae5a50c44e 100644
--- a/src/vs/workbench/browser/parts/compositeBarActions.ts
+++ b/src/vs/workbench/browser/parts/compositeBarActions.ts
@@ -548,11 +548,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
if (e.eventData.dataTransfer) {
e.eventData.dataTransfer.effectAllowed = 'move';
}
-
- // Trigger the action even on drag start to prevent clicks from failing that started a drag
- if (!this.getAction().checked) {
- this.getAction().run();
- }
+ // Remove focus indicator when dragging
+ this.blur();
}
}));
diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts
index 11a916565e..42e827ebbf 100644
--- a/src/vs/workbench/browser/parts/editor/baseEditor.ts
+++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts
@@ -19,6 +19,7 @@ import { MementoObject } from 'vs/workbench/common/memento';
import { isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { isLinux } from 'vs/base/common/platform';
import { indexOfPath } from 'vs/base/common/extpath';
+import { IDisposable } from 'vs/base/common/lifecycle';
/**
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
@@ -171,6 +172,7 @@ interface MapGroupToMemento {
export class EditorMemento implements IEditorMemento {
private cache: LRUCache> | undefined;
private cleanedUp = false;
+ private editorDisposables: Map | undefined;
constructor(
public readonly id: string,
@@ -200,9 +202,18 @@ export class EditorMemento implements IEditorMemento {
// Automatically clear when editor input gets disposed if any
if (resourceOrEditor instanceof EditorInput) {
- Event.once(resourceOrEditor.onDispose)(() => {
- this.clearEditorState(resource);
- });
+ const editor = resourceOrEditor;
+
+ if (!this.editorDisposables) {
+ this.editorDisposables = new Map();
+ }
+
+ if (!this.editorDisposables.has(editor)) {
+ this.editorDisposables.set(editor, Event.once(resourceOrEditor.onDispose)(() => {
+ this.clearEditorState(resource);
+ this.editorDisposables?.delete(editor);
+ }));
+ }
}
}
diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts
index ad1139571c..8200bdc682 100644
--- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts
+++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import * as nls from 'vs/nls';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
-import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor';
+import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext, EditorStickyContext } from 'vs/workbench/common/editor';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@@ -433,6 +433,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: EditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
+MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: nls.localize('pin', "Pin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')) });
+MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')) });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 });
@@ -579,6 +581,8 @@ appendEditorToolItem(
// Editor Commands for Command Palette
const viewCategory = { value: nls.localize('view', "View"), original: 'View' };
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
+MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: { value: nls.localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
+MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: { value: nls.localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: viewCategory } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: viewCategory } });
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: viewCategory } });
diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts
index eae878cfcc..f46d470920 100644
--- a/src/vs/workbench/browser/parts/editor/editorActions.ts
+++ b/src/vs/workbench/browser/parts/editor/editorActions.ts
@@ -500,7 +500,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
async run(context?: IEditorIdentifier): Promise {
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
if (group && editor) {
- return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
+ return group.closeEditors({ direction: CloseDirection.LEFT, except: editor, excludeSticky: true });
}
}
}
@@ -514,7 +514,7 @@ function getTarget(editorService: IEditorService, editorGroupService: IEditorGro
return { group: editorGroupService.activeGroup, editor: editorGroupService.activeGroup.activeEditor };
}
-export abstract class BaseCloseAllAction extends Action {
+abstract class BaseCloseAllAction extends Action {
constructor(
id: string,
@@ -554,7 +554,7 @@ export abstract class BaseCloseAllAction extends Action {
// to bring each dirty editor to the front so that the user
// can review if the files should be changed or not.
await Promise.all(this.groupsToClose.map(async groupToClose => {
- for (const editor of groupToClose.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
+ for (const editor of groupToClose.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: this.excludeSticky })) {
if (editor.isDirty() && !editor.isSaving() /* ignore editors that are being saved */) {
return groupToClose.openEditor(editor);
}
@@ -566,7 +566,7 @@ export abstract class BaseCloseAllAction extends Action {
const dirtyEditorsToConfirm = new Set();
const dirtyEditorsToAutoSave = new Set();
- for (const editor of this.editorService.editors) {
+ for (const editor of this.editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: this.excludeSticky }).map(({ editor }) => editor)) {
if (!editor.isDirty() || editor.isSaving()) {
continue; // only interested in dirty editors (unless in the process of saving)
}
@@ -601,21 +601,29 @@ export abstract class BaseCloseAllAction extends Action {
confirmation = ConfirmResult.DONT_SAVE;
}
- if (confirmation === ConfirmResult.CANCEL) {
- return;
+ // Handle result from asking user
+ let result: boolean | undefined = undefined;
+ switch (confirmation) {
+ case ConfirmResult.CANCEL:
+ return;
+ case ConfirmResult.DONT_SAVE:
+ result = await this.editorService.revertAll({ soft: true, includeUntitled: true, excludeSticky: this.excludeSticky });
+ break;
+ case ConfirmResult.SAVE:
+ result = await this.editorService.saveAll({ reason: saveReason, includeUntitled: true, excludeSticky: this.excludeSticky });
+ break;
}
- if (confirmation === ConfirmResult.DONT_SAVE) {
- await this.editorService.revertAll({ soft: true, includeUntitled: true });
- } else {
- await this.editorService.saveAll({ reason: saveReason, includeUntitled: true });
- }
- if (!this.workingCopyService.hasDirty) {
+ // Only continue to close editors if we either have no more dirty
+ // editors or the result from the save/revert was successful
+ if (!this.workingCopyService.hasDirty || result) {
return this.doCloseAll();
}
}
+ protected abstract get excludeSticky(): boolean;
+
protected abstract doCloseAll(): Promise;
}
@@ -636,8 +644,12 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
super(id, label, Codicon.closeAll.classNames, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService);
}
+ protected get excludeSticky(): boolean {
+ return true;
+ }
+
protected async doCloseAll(): Promise {
- await Promise.all(this.groupsToClose.map(g => g.closeAllEditors()));
+ await Promise.all(this.groupsToClose.map(group => group.closeAllEditors({ excludeSticky: true })));
}
}
@@ -658,6 +670,10 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService);
}
+ protected get excludeSticky(): boolean {
+ return false;
+ }
+
protected async doCloseAll(): Promise {
await Promise.all(this.groupsToClose.map(group => group.closeAllEditors()));
@@ -680,12 +696,12 @@ export class CloseEditorsInOtherGroupsAction extends Action {
async run(context?: IEditorIdentifier): Promise {
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
- await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => {
- if (groupToSkip && g.id === groupToSkip.id) {
+ await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async group => {
+ if (groupToSkip && group.id === groupToSkip.id) {
return;
}
- return g.closeAllEditors();
+ return group.closeAllEditors({ excludeSticky: true });
}));
}
}
@@ -707,7 +723,7 @@ export class CloseEditorInAllGroupsAction extends Action {
async run(): Promise {
const activeEditor = this.editorService.activeEditor;
if (activeEditor) {
- await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
+ await Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(group => group.closeEditor(activeEditor)));
}
}
}
diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts
index 0894e73047..07c0e4b26d 100644
--- a/src/vs/workbench/browser/parts/editor/editorCommands.ts
+++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
-import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane } from 'vs/workbench/common/editor';
+import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, EditorStickyContext, EditorsOrder } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
@@ -37,6 +37,9 @@ export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
+export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor';
+export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor';
+
export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide';
export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange';
export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange';
@@ -258,7 +261,7 @@ function registerDiffEditorCommands(): void {
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
const editorService = accessor.get(IEditorService);
- const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(e => e instanceof TextDiffEditor);
+ const candidates = [editorService.activeEditorPane, ...editorService.visibleEditorPanes].filter(editor => editor instanceof TextDiffEditor);
if (candidates.length > 0) {
const navigator = (candidates[0]).getDiffNavigator();
@@ -491,7 +494,7 @@ function registerCloseEditorCommands() {
return Promise.all(distinct(contexts.map(c => c.groupId)).map(async groupId => {
const group = editorGroupService.getGroup(groupId);
if (group) {
- return group.closeEditors({ savedOnly: true });
+ return group.closeEditors({ savedOnly: true, excludeSticky: true });
}
}));
}
@@ -514,7 +517,7 @@ function registerCloseEditorCommands() {
return Promise.all(distinctGroupIds.map(async groupId => {
const group = editorGroupService.getGroup(groupId);
if (group) {
- return group.closeAllEditors();
+ return group.closeAllEditors({ excludeSticky: true });
}
}));
}
@@ -596,7 +599,8 @@ function registerCloseEditorCommands() {
const editors = contexts
.filter(context => context.groupId === groupId)
.map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor);
- const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
+
+ const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => editors.indexOf(editor) === -1);
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
@@ -622,7 +626,7 @@ function registerCloseEditorCommands() {
group.pinEditor(group.activeEditor);
}
- return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
+ return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor, excludeSticky: true });
}
}
});
@@ -642,6 +646,36 @@ function registerCloseEditorCommands() {
}
});
+ KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: PIN_EDITOR_COMMAND_ID,
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')),
+ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Enter),
+ handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
+ const editorGroupService = accessor.get(IEditorGroupsService);
+
+ const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
+ if (group && editor) {
+ return group.stickEditor(editor);
+ }
+ }
+ });
+
+ KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: UNPIN_EDITOR_COMMAND_ID,
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')),
+ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Enter),
+ handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
+ const editorGroupService = accessor.get(IEditorGroupsService);
+
+ const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
+ if (group && editor) {
+ return group.unstickEditor(editor);
+ }
+ }
+ });
+
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: SHOW_EDITORS_IN_GROUP,
weight: KeybindingWeight.WorkbenchContrib,
diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
index 988cf5a1ee..1e32861d54 100644
--- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
+++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
@@ -266,7 +266,10 @@ class DropOverlay extends Themable {
}
// Open in target group
- const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
+ const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
+ pinned: true, // always pin dropped editor
+ sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
+ }));
targetGroup.openEditor(draggedEditor.editor, options);
// Ensure target has focus
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index 368c19a337..3757a16a9c 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -17,7 +17,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme';
-import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
+import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
@@ -441,6 +441,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
+ options.sticky = this._group.isSticky(activeEditor); // preserve sticky state
options.preserveFocus = true; // handle focus after editor is opened
const activeElement = document.activeElement;
@@ -732,6 +733,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._group.count;
}
+ get stickyCount(): number {
+ return this._group.stickyCount;
+ }
+
get activeEditorPane(): IVisibleEditorPane | undefined {
return this.editorControl ? withNullAsUndefined(this.editorControl.activeEditorPane) : undefined;
}
@@ -748,12 +753,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this._group.isPinned(editor);
}
+ isSticky(editorOrIndex: EditorInput | number): boolean {
+ return this._group.isSticky(editorOrIndex);
+ }
+
isActive(editor: EditorInput): boolean {
return this._group.isActive(editor);
}
- getEditors(order: EditorsOrder): EditorInput[] {
- return this._group.getEditors(order);
+ getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
+ return this._group.getEditors(order, options);
}
getEditorByIndex(index: number): EditorInput | undefined {
@@ -794,6 +803,43 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
+ stickEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void {
+ this.doStickEditor(candidate, true);
+ }
+
+ unstickEditor(candidate: EditorInput | undefined = this.activeEditor || undefined): void {
+ this.doStickEditor(candidate, false);
+ }
+
+ private doStickEditor(candidate: EditorInput | undefined, sticky: boolean): void {
+ if (candidate && this._group.isSticky(candidate) !== sticky) {
+ const oldIndexOfEditor = this.getIndexOfEditor(candidate);
+
+ // Update model
+ const editor = sticky ? this._group.stick(candidate) : this._group.unstick(candidate);
+ if (!editor) {
+ return;
+ }
+
+ // If the index of the editor changed, we need to forward this to
+ // title control and also make sure to emit this as an event
+ const newIndexOfEditor = this.getIndexOfEditor(editor);
+ if (newIndexOfEditor !== oldIndexOfEditor) {
+ this.titleAreaControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor);
+
+ // Event
+ this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor });
+ }
+
+ // Forward sticky state to title control
+ if (sticky) {
+ this.titleAreaControl.stickEditor(editor);
+ } else {
+ this.titleAreaControl.unstickEditor(editor);
+ }
+ }
+ }
+
invokeWithinContext(fn: (accessor: ServicesAccessor) => T): T {
return this.scopedInstantiationService.invokeFunction(fn);
}
@@ -833,11 +879,19 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Determine options
const openEditorOptions: IEditorOpenOptions = {
index: options ? options.index : undefined,
- pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number'), // unless specified, prefer to pin when opening with index
+ pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
+ sticky: options?.sticky || (typeof options?.index === 'number' && this._group.isSticky(options.index)),
active: this._group.count === 0 || !options || !options.inactive
};
- if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && this._group.isPreview(this._group.activeEditor)) {
+ if (options?.sticky && typeof options?.index === 'number' && !this._group.isSticky(options.index)) {
+ // Special case: we are to open an editor sticky but at an index that is not sticky
+ // In that case we prefer to open the editor at the index but not sticky. This enables
+ // to drag a sticky editor to an index that is not sticky to unstick it.
+ openEditorOptions.sticky = false;
+ }
+
+ if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && !this._group.isPinned(this._group.activeEditor)) {
// Special case: we are to open an editor inactive and not pinned, but the current active
// editor is also not pinned, which means it will get replaced with this one. As such,
// the editor can only be active.
@@ -1095,8 +1149,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// When moving an editor, try to preserve as much view state as possible by checking
// for the editor to be a text editor and creating the options accordingly if so
- const options = getActiveTextEditorOptions(this, editor, EditorOptions.create(moveOptions));
- options.pinned = true; // always pin moved editor
+ const options = getActiveTextEditorOptions(this, editor, EditorOptions.create({
+ ...moveOptions,
+ pinned: true, // always pin moved editor
+ sticky: this._group.isSticky(editor) // preserve sticky state
+ }));
// A move to another group is an open first...
target.openEditor(editor, options);
@@ -1377,7 +1434,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return;
}
- const editors = this.getEditorsToClose(args);
+ const editors = this.doGetEditorsToClose(args);
// Check for dirty and veto
const veto = await this.handleDirtyClosing(editors.slice(0));
@@ -1389,31 +1446,31 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.doCloseEditors(editors, options);
}
- private getEditorsToClose(editors: EditorInput[] | ICloseEditorsFilter): EditorInput[] {
- if (Array.isArray(editors)) {
- return editors;
+ private doGetEditorsToClose(args: EditorInput[] | ICloseEditorsFilter): EditorInput[] {
+ if (Array.isArray(args)) {
+ return args;
}
- const filter = editors;
+ const filter = args;
const hasDirection = typeof filter.direction === 'number';
- let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE); // in MRU order only if direction is not specified
+ let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE, filter); // in MRU order only if direction is not specified
// Filter: saved or saving only
if (filter.savedOnly) {
- editorsToClose = editorsToClose.filter(e => !e.isDirty() || e.isSaving());
+ editorsToClose = editorsToClose.filter(editor => !editor.isDirty() || editor.isSaving());
}
// Filter: direction (left / right)
else if (hasDirection && filter.except) {
editorsToClose = (filter.direction === CloseDirection.LEFT) ?
- editorsToClose.slice(0, this._group.indexOf(filter.except)) :
- editorsToClose.slice(this._group.indexOf(filter.except) + 1);
+ editorsToClose.slice(0, this._group.indexOf(filter.except, editorsToClose)) :
+ editorsToClose.slice(this._group.indexOf(filter.except, editorsToClose) + 1);
}
// Filter: except
else if (filter.except) {
- editorsToClose = editorsToClose.filter(e => !e.matches(filter.except));
+ editorsToClose = editorsToClose.filter(editor => !editor.matches(filter.except));
}
return editorsToClose;
@@ -1444,7 +1501,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region closeAllEditors()
- async closeAllEditors(): Promise {
+ async closeAllEditors(options?: ICloseAllEditorsOptions): Promise {
if (this.isEmpty) {
// If the group is empty and the request is to close all editors, we still close
@@ -1458,30 +1515,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Check for dirty and veto
- const editors = this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
- const veto = await this.handleDirtyClosing(editors.slice(0));
+ const veto = await this.handleDirtyClosing(this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
if (veto) {
return;
}
// Do close
- this.doCloseAllEditors();
+ this.doCloseAllEditors(options);
}
- private doCloseAllEditors(): void {
+ private doCloseAllEditors(options?: ICloseAllEditorsOptions): void {
// Close all inactive editors first
- this.editors.forEach(editor => {
+ const editorsToClose: EditorInput[] = [];
+ this._group.getEditors(EditorsOrder.SEQUENTIAL, options).forEach(editor => {
if (!this.isActive(editor)) {
this.doCloseInactiveEditor(editor);
}
+
+ editorsToClose.push(editor);
});
- // Close active editor last
- this.doCloseActiveEditor();
+ // Close active editor last (unless we skip it, e.g. because it is sticky)
+ if (this.activeEditor && editorsToClose.includes(this.activeEditor)) {
+ this.doCloseActiveEditor();
+ }
// Forward to title control
- this.titleAreaControl.closeAllEditors();
+ this.titleAreaControl.closeEditors(editorsToClose);
}
//#endregion
diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts
index 7948dc3d5a..3631bff815 100644
--- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts
+++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts
@@ -48,7 +48,7 @@ export class EditorsObserver extends Disposable {
}
get editors(): IEditorIdentifier[] {
- return [...this.mostRecentEditorsMap.values()];
+ return this.mostRecentEditorsMap.values();
}
hasEditor(resource: URI): boolean {
@@ -283,7 +283,7 @@ export class EditorsObserver extends Disposable {
// Across all editor groups
else {
- await this.doEnsureOpenedEditorsLimit(limit, [...this.mostRecentEditorsMap.values()], exclude);
+ await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude);
}
}
@@ -302,6 +302,10 @@ export class EditorsObserver extends Disposable {
return false; // never the editor that should be excluded
}
+ if (this.editorGroupsService.getGroup(groupId)?.isSticky(editor)) {
+ return false; // never sticky editors
+ }
+
return true;
});
@@ -342,7 +346,7 @@ export class EditorsObserver extends Disposable {
private serialize(): ISerializedEditorsList {
const registry = Registry.as(Extensions.EditorInputFactories);
- const entries = [...this.mostRecentEditorsMap.values()];
+ const entries = this.mostRecentEditorsMap.values();
const mapGroupToSerializableEditorsOfGroup = new Map();
return {
diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css
index 8e7cf6329e..3d6cd72b8b 100644
--- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css
+++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css
@@ -63,8 +63,8 @@
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
-.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
- padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off:not(.sticky) {
+ padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left (unless sticky) */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
@@ -82,6 +82,30 @@
max-width: -moz-fit-content;
}
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky {
+
+ /** Sticky tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */
+ position: sticky;
+ z-index: 1;
+
+ /** Sticky tabs are even and never grow */
+ flex-basis: 0;
+ flex-grow: 0;
+
+ /** Sticky tabs have a fixed width of 38px */
+ width: 38px;
+ min-width: 38px;
+ max-width: 38px;
+}
+
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky {
+
+ /** Disable sticky positions for sticky tabs if the available space is too little */
+ position: static;
+}
+
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label {
margin-right: 4px !important;
}
@@ -174,6 +198,10 @@
opacity: 0; /* when tab has the focus this shade breaks the tab border (fixes https://github.com/Microsoft/vscode/issues/57819) */
}
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.has-icon-theme) .monaco-icon-label {
+ text-align: center; /* ensure that sticky tabs without icon have label centered */
+}
+
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-container {
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
@@ -210,14 +238,15 @@
overflow: visible; /* ...but still show the close button on hover, focus and when dirty */
}
-.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close {
- display: none; /* hide the close action bar when we are configured to hide it */
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close,
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.sticky > .tab-close {
+ display: none; /* hide the close action bar when we are configured to hide it (unless dirty, but always when sticky) */
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
-.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
+.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
opacity: 1;
}
@@ -233,10 +262,10 @@
content: "\ea71"; /* use `circle-filled` icon unicode */
}
-.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
-.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
opacity: 0.5;
}
@@ -257,8 +286,8 @@
padding-right: 10px; /* give a little bit more room if close button is off */
}
-.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
- padding-right: 5px; /* we need less room when sizing is shrink */
+.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off:not(.sticky) {
+ padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close,
diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts
index 2a348f1ef1..e2c1943dce 100644
--- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts
+++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts
@@ -136,10 +136,6 @@ export class NoTabsTitleControl extends TitleControl {
this.ifActiveEditorChanged(() => this.redraw());
}
- closeAllEditors(): void {
- this.redraw();
- }
-
moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void {
this.ifActiveEditorChanged(() => this.redraw());
}
@@ -148,6 +144,14 @@ export class NoTabsTitleControl extends TitleControl {
this.ifEditorIsActive(editor, () => this.redraw());
}
+ stickEditor(editor: IEditorInput): void {
+ // Sticky editors are not presented any different with tabs disabled
+ }
+
+ unstickEditor(editor: IEditorInput): void {
+ // Sticky editors are not presented any different with tabs disabled
+ }
+
setActive(isActive: boolean): void {
this.redraw();
}
@@ -219,7 +223,6 @@ export class NoTabsTitleControl extends TitleControl {
}
}
-
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
if (this.group.isActive(editor)) {
fn(); // only run if editor is current active
diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
index 331494c005..cfe1295d2a 100644
--- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
+++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
@@ -27,7 +27,7 @@ import { getOrSet } from 'vs/base/common/map';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
-import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
+import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -67,6 +67,11 @@ export class TabsTitleControl extends TitleControl {
large: 10
};
+ private static readonly TAB_SIZES = {
+ sticky: 38,
+ fit: 120
+ };
+
private titleContainer: HTMLElement | undefined;
private tabsAndActionsContainer: HTMLElement | undefined;
private tabsContainer: HTMLElement | undefined;
@@ -392,10 +397,6 @@ export class TabsTitleControl extends TitleControl {
this.handleClosedEditors();
}
- closeAllEditors(): void {
- this.handleClosedEditors();
- }
-
private handleClosedEditors(): void {
// There are tabs to show
@@ -453,7 +454,24 @@ export class TabsTitleControl extends TitleControl {
}
pinEditor(editor: IEditorInput): void {
- this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel));
+ this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel));
+ }
+
+ stickEditor(editor: IEditorInput): void {
+ this.doHandleStickyEditorChange(editor);
+ }
+
+ unstickEditor(editor: IEditorInput): void {
+ this.doHandleStickyEditorChange(editor);
+ }
+
+ private doHandleStickyEditorChange(editor: IEditorInput): void {
+
+ // Update tab
+ this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel));
+
+ // A change to the sticky state requires a layout to keep the active editor visible
+ this.layout(this.dimension);
}
setActive(isGroupActive: boolean): void {
@@ -487,7 +505,7 @@ export class TabsTitleControl extends TitleControl {
// As such we need to redraw each label
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
- this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
+ this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
});
// A change to a label requires a layout to keep the active editor visible
@@ -745,10 +763,7 @@ export class TabsTitleControl extends TitleControl {
}
// Apply some datatransfer types to allow for dragging the element outside of the application
- const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
- if (resource) {
- this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
- }
+ this.doFillResourceDataTransfers(editor, e);
// Fixes https://github.com/Microsoft/vscode/issues/18733
addClass(tab, 'dragged');
@@ -1010,7 +1025,7 @@ export class TabsTitleControl extends TitleControl {
private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
// Label
- this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
+ this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
// Borders / Outline
const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
@@ -1018,10 +1033,12 @@ export class TabsTitleControl extends TitleControl {
tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || '';
// Settings
+ const isTabSticky = this.group.isSticky(index);
const options = this.accessor.partOptions;
+ const tabCloseButton = isTabSticky ? 'off' /* treat sticky tabs as tabCloseButton: 'off' */ : options.tabCloseButton;
['off', 'left', 'right'].forEach(option => {
- const domAction = options.tabCloseButton === option ? addClass : removeClass;
+ const domAction = tabCloseButton === option ? addClass : removeClass;
domAction(tabContainer, `close-button-${option}`);
});
@@ -1036,13 +1053,37 @@ export class TabsTitleControl extends TitleControl {
removeClass(tabContainer, 'has-icon-theme');
}
+ // Sticky Tabs need a position to remain at their location
+ // when scrolling to stay in view (requirement for position: sticky)
+ if (isTabSticky) {
+ addClass(tabContainer, 'sticky');
+ tabContainer.style.left = `${index * TabsTitleControl.TAB_SIZES.sticky}px`;
+ } else {
+ removeClass(tabContainer, 'sticky');
+ tabContainer.style.left = 'auto';
+ }
+
// Active / dirty state
this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
}
- private redrawLabel(editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
- const name = tabLabel.name;
- const description = tabLabel.description || '';
+ private redrawLabel(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
+ const isTabSticky = this.group.isSticky(index);
+
+ // Unless tabs are sticky, show the full label and description
+ // Sticky tabs will only show an icon if icons are enabled
+ // or their first character of the name otherwise
+ let name: string | undefined;
+ let description: string;
+ if (isTabSticky) {
+ const isShowingIcons = this.accessor.partOptions.showIcons && !!this.accessor.partOptions.iconTheme;
+ name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase();
+ description = '';
+ } else {
+ name = tabLabel.name;
+ description = tabLabel.description || '';
+ }
+
const title = tabLabel.title || '';
if (tabLabel.ariaLabel) {
@@ -1056,7 +1097,7 @@ export class TabsTitleControl extends TitleControl {
// Label
tabLabelWidget.setResource(
{ name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) },
- { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }
+ { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel: isTabSticky }
);
this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor)); // {{SQL CARBON EDIT}} -- Display the editor's tab color
@@ -1173,8 +1214,8 @@ export class TabsTitleControl extends TitleControl {
layout(dimension: Dimension | undefined): void {
this.dimension = dimension;
- const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined;
- if (!activeTab || !this.dimension) {
+ const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
+ if (!activeTabAndIndex || !this.dimension) {
return;
}
@@ -1192,20 +1233,66 @@ export class TabsTitleControl extends TitleControl {
}
private doLayout(dimension: Dimension): void {
- const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined;
- if (!activeTab) {
- return;
+ const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
+ if (!activeTabAndIndex) {
+ return; // nothing to do if not editor opened
}
- const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
+ // Breadcrumbs
+ this.doLayoutBreadcrumbs(dimension);
+ // Tabs
+ const [activeTab, activeIndex] = activeTabAndIndex;
+ this.doLayoutTabs(activeTab, activeIndex);
+ }
+
+ private doLayoutBreadcrumbs(dimension: Dimension): void {
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
+ const tabsScrollbar = assertIsDefined(this.tabsScrollbar);
+
this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT });
tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
}
+ }
- const visibleContainerWidth = tabsContainer.offsetWidth;
- const totalContainerWidth = tabsContainer.scrollWidth;
+ private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void {
+ const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
+
+ //
+ // Synopsis
+ // - allTabsWidth: sum of all tab widths
+ // - stickyTabsWidth: sum of all sticky tab widths
+ // - visibleContainerWidth: size of tab container
+ // - availableContainerWidth: size of tab container minus size of sticky tabs
+ //
+ // [------------------------------ All tabs width ---------------------------------------]
+ // [------------------- Visible container width -------------------]
+ // [------ Available container width ------]
+ // [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
+ // Active Tab Width [-------]
+ // [------- Active Tab Pos X -------]
+ // [-- Sticky Tabs Width --]
+ //
+
+ const visibleTabsContainerWidth = tabsContainer.offsetWidth;
+ const allTabsWidth = tabsContainer.scrollWidth;
+
+ let stickyTabsWidth = this.group.stickyCount * TabsTitleControl.TAB_SIZES.sticky;
+ let activeTabSticky = this.group.isSticky(activeIndex);
+ let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth;
+
+ // Special case: we have sticky tabs but the available space for showing tabs
+ // is little enough that we need to disable sticky tabs sticky positioning
+ // so that tabs can be scrolled at naturally.
+ if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_SIZES.fit) {
+ addClass(tabsContainer, 'disable-sticky-tabs');
+
+ availableTabsContainerWidth = visibleTabsContainerWidth;
+ stickyTabsWidth = 0;
+ activeTabSticky = false;
+ } else {
+ removeClass(tabsContainer, 'disable-sticky-tabs');
+ }
let activeTabPosX: number | undefined;
let activeTabWidth: number | undefined;
@@ -1217,42 +1304,78 @@ export class TabsTitleControl extends TitleControl {
// Update scrollbar
tabsScrollbar.setScrollDimensions({
- width: visibleContainerWidth,
- scrollWidth: totalContainerWidth
+ width: visibleTabsContainerWidth,
+ scrollWidth: allTabsWidth
});
// Return now if we are blocked to reveal the active tab and clear flag
- if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number') {
+ // We also return if the active tab is sticky because this means it is
+ // always visible anyway.
+ if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number' || activeTabSticky) {
this.blockRevealActiveTab = false;
return;
}
// Reveal the active one
- const containerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft;
- const activeTabFits = activeTabWidth <= visibleContainerWidth;
+ const tabsContainerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft;
+ const activeTabFits = activeTabWidth <= availableTabsContainerWidth;
+ const adjustedActiveTabPosX = activeTabPosX - stickyTabsWidth;
+ //
+ // Synopsis
+ // - adjustedActiveTabPosX: the adjusted tabPosX takes the width of sticky tabs into account
+ // conceptually the scrolling only begins after sticky tabs so in order to reveal a tab fully
+ // the actual position needs to be adjusted for sticky tabs.
+ //
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
// Note: only try to do this if we actually have enough width to give to show the tab fully!
- if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
+ //
+ // Example: Tab G should be made active and needs to be fully revealed as such.
+ //
+ // [-------------------------------- All tabs width -----------------------------------------]
+ // [-------------------- Visible container width --------------------]
+ // [----- Available container width -------]
+ // [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
+ // Active Tab Width [-------]
+ // [------- Active Tab Pos X -------]
+ // [-------- Adjusted Tab Pos X -------]
+ // [-- Sticky Tabs Width --]
+ //
+ //
+ if (activeTabFits && tabsContainerScrollPosX + availableTabsContainerWidth < adjustedActiveTabPosX + activeTabWidth) {
tabsScrollbar.setScrollPosition({
- scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
+ scrollLeft: tabsContainerScrollPosX + ((adjustedActiveTabPosX + activeTabWidth) /* right corner of tab */ - (tabsContainerScrollPosX + availableTabsContainerWidth) /* right corner of view port */)
});
}
- // Tab is overlflowng to the left or does not fit: Scroll it into view to the left
- else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
+ //
+ // Tab is overlflowing to the left or does not fit: Scroll it into view to the left
+ //
+ // Example: Tab C should be made active and needs to be fully revealed as such.
+ //
+ // [----------------------------- All tabs width ----------------------------------------]
+ // [------------------ Visible container width ------------------]
+ // [----- Available container width -------]
+ // [ Sticky A ][ Sticky B ][ Tab C ][ Tab D ][ Tab E ][ Tab F ][ Tab G ][ Tab H ][ Tab I ]
+ // Active Tab Width [-------]
+ // [------- Active Tab Pos X -------]
+ // Adjusted Tab Pos X []
+ // [-- Sticky Tabs Width --]
+ //
+ //
+ else if (tabsContainerScrollPosX > adjustedActiveTabPosX || !activeTabFits) {
tabsScrollbar.setScrollPosition({
- scrollLeft: activeTabPosX
+ scrollLeft: adjustedActiveTabPosX
});
}
}
- private getTab(editor: IEditorInput): HTMLElement | undefined {
+ private getTabAndIndex(editor: IEditorInput): [HTMLElement, number /* index */] | undefined {
const editorIndex = this.group.getIndexOfEditor(editor);
if (editorIndex >= 0) {
const tabsContainer = assertIsDefined(this.tabsContainer);
- return tabsContainer.children[editorIndex] as HTMLElement;
+ return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex];
}
return undefined;
@@ -1494,11 +1617,11 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
// Adjust gradient for focused and unfocused hover background
const makeTabHoverBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => `
- .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
+ .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after {
background: linear-gradient(to left, ${color}, transparent) !important;
}
- .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
+ .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after {
background: linear-gradient(to left, ${colorDrag}, transparent) !important;
}
`;
@@ -1521,19 +1644,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
if (editorDragAndDropBackground && adjustedTabDragBackground) {
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
collector.addRule(`
- .monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
- .monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
+ .monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged):not(.sticky) > .tab-label::after,
+ .monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged):not(.sticky) > .tab-label::after {
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
}
`);
}
const makeTabBackgroundRule = (color: Color, colorDrag: Color, focused: boolean, active: boolean) => `
- .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged) > .tab-label::after {
+ .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after {
background: linear-gradient(to left, ${color}, transparent);
}
- .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged) > .tab-label::after {
+ .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after {
background: linear-gradient(to left, ${colorDrag}, transparent);
}
`;
diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts
index b44ecbd5bc..4c554db927 100644
--- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts
+++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts
@@ -181,7 +181,12 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
// because we are triggering another openEditor() call
// and do not control the initial intent that resulted
// in us now opening as binary.
- const preservingOptions: IEditorOptions = { activation: EditorActivation.PRESERVE, pinned: this.group?.isPinned(input) };
+ const preservingOptions: IEditorOptions = {
+ activation: EditorActivation.PRESERVE,
+ pinned: this.group?.isPinned(input),
+ sticky: this.group?.isSticky(input)
+ };
+
if (options) {
options.overwrite(preservingOptions);
} else {
@@ -237,7 +242,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
if (isArray(error)) {
const errors = error;
- return errors.some(e => this.isFileBinaryError(e));
+ return errors.some(error => this.isFileBinaryError(error));
}
return (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY;
diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts
index 4a74150f1e..38c91390b1 100644
--- a/src/vs/workbench/browser/parts/editor/titleControl.ts
+++ b/src/vs/workbench/browser/parts/editor/titleControl.ts
@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import 'vs/css!./media/titlecontrol';
import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd';
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -12,8 +13,7 @@ import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecut
import * as arrays from 'vs/base/common/arrays';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
-import 'vs/css!./media/titlecontrol';
-import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
+import { getCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
@@ -32,13 +32,14 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
-import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor';
+import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext, EditorStickyContext } from 'vs/workbench/common/editor';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IFileService } from 'vs/platform/files/common/files';
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
import { isFirefox } from 'vs/base/browser/browser';
+import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
export interface IToolbarActions {
primary: IAction[];
@@ -59,6 +60,7 @@ export abstract class TitleControl extends Themable {
private resourceContext: ResourceContextKey;
private editorPinnedContext: IContextKey;
+ private editorStickyContext: IContextKey;
private readonly editorToolBarMenuDisposables = this._register(new DisposableStore());
@@ -86,6 +88,7 @@ export abstract class TitleControl extends Themable {
this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey));
this.editorPinnedContext = EditorPinnedContext.bindTo(contextKeyService);
+ this.editorStickyContext = EditorStickyContext.bindTo(contextKeyService);
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
@@ -222,6 +225,7 @@ export abstract class TitleControl extends Themable {
// Update contexts
this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null);
this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false);
+ this.editorStickyContext.set(this.group.activeEditor ? this.group.isSticky(this.group.activeEditor) : false);
// Editor actions require the editor control to be there, so we retrieve it via service
const activeEditorPane = this.group.activeEditorPane;
@@ -266,10 +270,8 @@ export abstract class TitleControl extends Themable {
// If tabs are disabled, treat dragging as if an editor tab was dragged
let hasDataTransfer = false;
if (!this.accessor.partOptions.showTabs) {
- const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null;
- if (resource) {
- this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
- hasDataTransfer = true;
+ if (this.group.activeEditor) {
+ hasDataTransfer = this.doFillResourceDataTransfers(this.group.activeEditor, e);
}
}
@@ -295,6 +297,31 @@ export abstract class TitleControl extends Themable {
}));
}
+ protected doFillResourceDataTransfers(editor: IEditorInput, e: DragEvent): boolean {
+ const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
+ if (!resource) {
+ return false;
+ }
+
+ const editorOptions: ITextEditorOptions = {
+ viewState: (() => {
+ if (this.group.activeEditor === editor) {
+ const activeControl = this.group.activeEditorPane?.getControl();
+ if (isCodeEditor(activeControl)) {
+ return withNullAsUndefined(activeControl.saveViewState());
+ }
+ }
+
+ return undefined;
+ })(),
+ sticky: this.group.isSticky(editor)
+ };
+
+ this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], () => editorOptions, e);
+
+ return true;
+ }
+
protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
// Update contexts based on editor picked and remember previous to restore
@@ -302,6 +329,8 @@ export abstract class TitleControl extends Themable {
this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })));
const currentPinnedContext = !!this.editorPinnedContext.get();
this.editorPinnedContext.set(this.group.isPinned(editor));
+ const currentStickyContext = !!this.editorStickyContext.get();
+ this.editorStickyContext.set(this.group.isSticky(editor));
// Find target anchor
let anchor: HTMLElement | { x: number, y: number } = node;
@@ -325,6 +354,7 @@ export abstract class TitleControl extends Themable {
// restore previous contexts
this.resourceContext.set(currentResourceContext || null);
this.editorPinnedContext.set(currentPinnedContext);
+ this.editorStickyContext.set(currentStickyContext);
// restore focus to active group
this.accessor.activeGroup.focus();
@@ -351,12 +381,14 @@ export abstract class TitleControl extends Themable {
abstract closeEditors(editors: IEditorInput[]): void;
- abstract closeAllEditors(): void;
-
abstract moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void;
abstract pinEditor(editor: IEditorInput): void;
+ abstract stickEditor(editor: IEditorInput): void;
+
+ abstract unstickEditor(editor: IEditorInput): void;
+
abstract setActive(isActive: boolean): void;
abstract updateEditorLabel(editor: IEditorInput): void;
diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts
index 4d80bd76d8..329f21a63d 100644
--- a/src/vs/workbench/browser/parts/panel/panelPart.ts
+++ b/src/vs/workbench/browser/parts/panel/panelPart.ts
@@ -207,7 +207,8 @@ export class PanelPart extends CompositePart implements IPanelService {
}
if (isActive) {
- if (!activePanel) {
+ // Only try to open the panel if it has been created and visible
+ if (!activePanel && this.element && this.layoutService.isVisible(Parts.PANEL_PART)) {
this.doOpenPanel(panel.id);
}
diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
index ed63e150e6..c7dfb191a0 100644
--- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
+++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
@@ -22,7 +22,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { isThemeColor } from 'vs/editor/common/editorCommon';
import { Color } from 'vs/base/common/color';
-import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses } from 'vs/base/browser/dom';
+import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, removeClass, EventType, hide, show, removeClasses, isAncestor } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -35,6 +35,11 @@ import { assertIsDefined } from 'vs/base/common/types';
import { Emitter } from 'vs/base/common/event';
import { Command } from 'vs/editor/common/modes';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
+import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
+import { KeyCode } from 'vs/base/common/keyCodes';
+import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
+import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
interface IPendingStatusbarEntry {
id: string;
@@ -51,8 +56,11 @@ interface IStatusbarViewModelEntry {
alignment: StatusbarAlignment;
priority: number;
container: HTMLElement;
+ labelContainer: HTMLElement;
}
+const CONTEXT_STATUS_BAR_FOCUSED = new RawContextKey('statusBarFocused', false);
+
class StatusbarViewModel extends Disposable {
static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
@@ -188,6 +196,40 @@ class StatusbarViewModel extends Disposable {
return this._entries.filter(entry => entry.alignment === alignment);
}
+ focusNextEntry(): void {
+ this.focusEntry(+1, 0);
+ }
+
+ focusPreviousEntry(): void {
+ this.focusEntry(-1, this.entries.length - 1);
+ }
+
+ private focusEntry(delta: number, restartPosition: number): void {
+ const getVisibleEntry = (start: number) => {
+ let indexToFocus = start;
+ let entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
+ while (entry && this.isHidden(entry.id)) {
+ indexToFocus += delta;
+ entry = (indexToFocus >= 0 && indexToFocus < this._entries.length) ? this._entries[indexToFocus] : undefined;
+ }
+ return entry;
+ };
+
+ const focused = this._entries.find(entry => isAncestor(document.activeElement, entry.container));
+ if (focused) {
+ const entry = getVisibleEntry(this._entries.indexOf(focused) + delta);
+ if (entry) {
+ entry.labelContainer.focus();
+ return;
+ }
+ }
+
+ const entry = getVisibleEntry(restartPosition);
+ if (entry) {
+ entry.labelContainer.focus();
+ }
+ }
+
private updateVisibility(id: string, trigger: boolean): void;
private updateVisibility(entry: IStatusbarViewModelEntry, trigger: boolean): void;
private updateVisibility(arg1: string | IStatusbarViewModelEntry, trigger: boolean): void {
@@ -355,6 +397,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
@IStorageService private readonly storageService: IStorageService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IContextMenuService private contextMenuService: IContextMenuService,
+ @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
@@ -415,7 +458,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
this.appendOneStatusbarEntry(itemContainer, alignment, priority);
// Add to view model
- const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
+ const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer, labelContainer: item.labelContainer };
const viewModelEntryDispose = this.viewModel.add(viewModelEntry);
return {
@@ -442,9 +485,21 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
+ focusNextEntry(): void {
+ this.viewModel.focusNextEntry();
+ }
+
+ focusPreviousEntry(): void {
+ this.viewModel.focusPreviousEntry();
+ }
+
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
+ // Track focus within container
+ const scopedContextKeyService = this.contextKeyService.createScoped(this.element);
+ CONTEXT_STATUS_BAR_FOCUSED.bindTo(scopedContextKeyService).set(true);
+
// Left items container
this.leftItemsContainer = document.createElement('div');
addClasses(this.leftItemsContainer, 'left-items', 'items-container');
@@ -645,13 +700,14 @@ class StatusbarEntryItem extends Disposable {
private entry!: IStatusbarEntry;
- private labelContainer!: HTMLElement;
+ labelContainer!: HTMLElement;
private label!: CodiconLabel;
private readonly foregroundListener = this._register(new MutableDisposable());
private readonly backgroundListener = this._register(new MutableDisposable());
- private readonly commandListener = this._register(new MutableDisposable());
+ private readonly commandMouseListener = this._register(new MutableDisposable());
+ private readonly commandKeyboardListener = this._register(new MutableDisposable());
constructor(
private container: HTMLElement,
@@ -711,11 +767,18 @@ class StatusbarEntryItem extends Disposable {
// Update: Command
if (!this.entry || entry.command !== this.entry.command) {
- this.commandListener.clear();
+ this.commandMouseListener.clear();
+ this.commandKeyboardListener.clear();
const command = entry.command;
if (command) {
- this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
+ this.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));
+ this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_UP, e => {
+ const event = new StandardKeyboardEvent(e);
+ if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
+ this.executeCommand(command);
+ }
+ });
removeClass(this.labelContainer, 'disabled');
} else {
@@ -814,7 +877,8 @@ class StatusbarEntryItem extends Disposable {
dispose(this.foregroundListener);
dispose(this.backgroundListener);
- dispose(this.commandListener);
+ dispose(this.commandMouseListener);
+ dispose(this.commandKeyboardListener);
}
}
@@ -822,6 +886,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
+ collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus { background-color: ${statusBarItemHoverBackground}; }`);
}
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
@@ -846,3 +911,27 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
});
registerSingleton(IStatusbarService, StatusbarPart);
+
+KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: 'workbench.statusBar.focusPrevious',
+ weight: KeybindingWeight.WorkbenchContrib,
+ primary: KeyCode.LeftArrow,
+ secondary: [KeyCode.UpArrow],
+ when: CONTEXT_STATUS_BAR_FOCUSED,
+ handler: (accessor: ServicesAccessor) => {
+ const statusBarService = accessor.get(IStatusbarService);
+ statusBarService.focusPreviousEntry();
+ }
+});
+
+KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: 'workbench.statusBar.focusNext',
+ weight: KeybindingWeight.WorkbenchContrib,
+ primary: KeyCode.RightArrow,
+ secondary: [KeyCode.DownArrow],
+ when: CONTEXT_STATUS_BAR_FOCUSED,
+ handler: (accessor: ServicesAccessor) => {
+ const statusBarService = accessor.get(IStatusbarService);
+ statusBarService.focusNextEntry();
+ }
+});
diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts
index fc3c835c4e..a4da520a55 100644
--- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts
+++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts
@@ -934,17 +934,21 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] {
const result: IAction[] = [];
+ let showHide = true;
if (!viewDescriptor && this.isViewMergedWithContainer()) {
viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.panes[0].id) || undefined;
+ showHide = false;
}
if (viewDescriptor) {
- result.push({
- id: `${viewDescriptor.id}.removeView`,
- label: nls.localize('hideView', "Hide"),
- enabled: viewDescriptor.canToggleVisibility,
- run: () => this.toggleViewVisibility(viewDescriptor!.id)
- });
+ if (showHide) {
+ result.push({
+ id: `${viewDescriptor.id}.removeView`,
+ label: nls.localize('hideView', "Hide"),
+ enabled: viewDescriptor.canToggleVisibility,
+ run: () => this.toggleViewVisibility(viewDescriptor!.id)
+ });
+ }
const view = this.getView(viewDescriptor.id);
if (view) {
result.push(...view.getContextMenuActions());
@@ -955,7 +959,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
id: `${viewDescriptor.id}.toggleVisibility`,
label: viewDescriptor.name,
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
- enabled: viewDescriptor.canToggleVisibility,
+ enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
run: () => this.toggleViewVisibility(viewDescriptor.id)
}));
diff --git a/src/vs/workbench/browser/parts/views/viewsService.ts b/src/vs/workbench/browser/parts/views/viewsService.ts
index 1745e10c6d..591346aa9d 100644
--- a/src/vs/workbench/browser/parts/views/viewsService.ts
+++ b/src/vs/workbench/browser/parts/views/viewsService.ts
@@ -127,15 +127,10 @@ export class ViewsService extends Disposable implements IViewsService {
this.deregisterViewletOrPanel(container, location);
}
for (const { container, location } of added) {
- this.registerViewletOrPanel(container, location);
+ this.onDidRegisterViewContainer(container, location);
}
}
- private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void {
- this.deregisterViewletOrPanel(viewContainer, from);
- this.registerViewletOrPanel(viewContainer, to);
- }
-
private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void {
this.registerViewletOrPanel(viewContainer, viewContainerLocation);
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
@@ -146,6 +141,11 @@ export class ViewsService extends Disposable implements IViewsService {
}));
}
+ private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void {
+ this.deregisterViewletOrPanel(viewContainer, from);
+ this.registerViewletOrPanel(viewContainer, to);
+ }
+
private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void {
const location = this.viewDescriptorService.getViewContainerLocation(container);
if (location === null) {
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index 93d647fb34..7ef541546c 100644
--- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts
@@ -71,7 +71,7 @@ class BrowserMain extends Disposable {
services.storageService.store(Settings.WORKSPACE_FIRST_OPEN, !(firstOpen ?? false), StorageScope.WORKSPACE);
}
- { await domContentLoaded(); }
+ await domContentLoaded();
mark('willStartWorkbench');
// Base Theme
@@ -272,7 +272,7 @@ class BrowserMain extends Disposable {
if (!this.configuration.userDataProvider) {
const remoteUserDataUri = this.getRemoteUserDataUri();
if (remoteUserDataUri) {
- this.configuration.userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, joinPath(remoteUserDataUri, BACKUPS), remoteFileSystemProvider, environmentService));
+ this.configuration.userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, joinPath(remoteUserDataUri, BACKUPS), remoteFileSystemProvider, environmentService, logService));
}
}
}
diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts
index 612469dcfa..07605aae37 100644
--- a/src/vs/workbench/browser/workbench.contribution.ts
+++ b/src/vs/workbench/browser/workbench.contribution.ts
@@ -108,12 +108,12 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
},
'workbench.editor.enablePreview': {
'type': 'boolean',
- 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing) and show up with an italic font style."),
+ 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing) and show up with an italic font style."),
'default': true
},
'workbench.editor.enablePreviewFromQuickOpen': {
'type': 'boolean',
- 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."),
+ 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors are reused until they are explicitly set to be kept open (e.g. via double click or editing)."),
'default': true
},
'workbench.editor.closeOnFileDelete': {
diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts
index 88746d0046..d91b4c626d 100644
--- a/src/vs/workbench/common/editor.ts
+++ b/src/vs/workbench/common/editor.ts
@@ -35,6 +35,7 @@ export const ActiveEditorIsReadonlyContext = new RawContextKey('activeE
export const ActiveEditorAvailableEditorIdsContext = new RawContextKey('activeEditorAvailableEditorIds', '');
export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false);
export const EditorPinnedContext = new RawContextKey('editorPinned', false);
+export const EditorStickyContext = new RawContextKey('editorSticky', false);
export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false);
export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0);
export const NoEditorsVisibleContext = EditorsVisibleContext.toNegated();
@@ -1046,6 +1047,12 @@ export class EditorOptions implements IEditorOptions {
*/
pinned: boolean | undefined;
+ /**
+ * An editor that is sticky moves to the beginning of the editors list within the group and will remain
+ * there unless explicitly closed. Operations such as "Close All" will not close sticky editors.
+ */
+ sticky: boolean | undefined;
+
/**
* The index in the document stack where to insert the editor into when opening.
*/
@@ -1111,6 +1118,10 @@ export class EditorOptions implements IEditorOptions {
this.pinned = options.pinned;
}
+ if (typeof options.sticky === 'boolean') {
+ this.sticky = options.sticky;
+ }
+
if (typeof options.inactive === 'boolean') {
this.inactive = options.inactive;
}
@@ -1291,6 +1302,7 @@ export class EditorCommandsContextActionRunner extends ActionRunner {
export interface IEditorCloseEvent extends IEditorIdentifier {
replaced: boolean;
index: number;
+ sticky: boolean;
}
export type GroupIdentifier = number;
diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts
index faa94f777b..3982bb00a7 100644
--- a/src/vs/workbench/common/editor/editorGroup.ts
+++ b/src/vs/workbench/common/editor/editorGroup.ts
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
-import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
+import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { coalesce } from 'vs/base/common/arrays';
@@ -30,6 +30,7 @@ export interface EditorIdentifier extends IEditorIdentifier {
export interface IEditorOpenOptions {
pinned?: boolean;
+ sticky?: boolean;
active?: boolean;
index?: number;
}
@@ -44,6 +45,7 @@ export interface ISerializedEditorGroup {
editors: ISerializedEditorInput[];
mru: number[];
preview?: number;
+ sticky?: number;
}
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
@@ -92,6 +94,7 @@ export class EditorGroup extends Disposable {
private preview: EditorInput | null = null; // editor in preview state
private active: EditorInput | null = null; // editor in active state
+ private sticky: number = -1; // index of first editor in sticky state
private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
private focusRecentEditorAfterClose: boolean | undefined;
@@ -114,10 +117,10 @@ export class EditorGroup extends Disposable {
}
private registerListeners(): void {
- this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
+ this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
}
- private onConfigurationUpdated(event?: IConfigurationChangeEvent): void {
+ private onConfigurationUpdated(): void {
this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
}
@@ -126,8 +129,25 @@ export class EditorGroup extends Disposable {
return this.editors.length;
}
- getEditors(order: EditorsOrder): EditorInput[] {
- return order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
+ get stickyCount(): number {
+ return this.sticky + 1;
+ }
+
+ getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
+ const editors = order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);
+
+ if (options?.excludeSticky) {
+
+ // MRU: need to check for index on each
+ if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
+ return editors.filter(editor => !this.isSticky(editor));
+ }
+
+ // Sequential: simply start after sticky index
+ return editors.slice(this.sticky + 1);
+ }
+
+ return editors;
}
getEditorByIndex(index: number): EditorInput | undefined {
@@ -146,18 +166,15 @@ export class EditorGroup extends Disposable {
return this.preview;
}
- isPreview(editor: EditorInput): boolean {
- return this.matches(this.preview, editor);
- }
-
openEditor(candidate: EditorInput, options?: IEditorOpenOptions): EditorInput {
- const makePinned = options?.pinned;
+ const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
+ const makePinned = options?.pinned || options?.sticky;
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
- const existingEditor = this.findEditor(candidate);
+ const existingEditorAndIndex = this.findEditor(candidate);
// New editor
- if (!existingEditor) {
+ if (!existingEditorAndIndex) {
const newEditor = candidate;
const indexOfActive = this.indexOf(this.active);
@@ -170,6 +187,12 @@ export class EditorGroup extends Disposable {
// Insert to the BEGINNING
else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
targetIndex = 0;
+
+ // Always make sure targetIndex is after sticky editors
+ // unless we are explicitly told to make the editor sticky
+ if (!makeSticky && this.isSticky(targetIndex)) {
+ targetIndex = this.sticky + 1;
+ }
}
// Insert to the END
@@ -177,18 +200,38 @@ export class EditorGroup extends Disposable {
targetIndex = this.editors.length;
}
- // Insert to the LEFT of active editor
- else if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
- if (indexOfActive === 0 || !this.editors.length) {
- targetIndex = 0; // to the left becoming first editor in list
- } else {
- targetIndex = indexOfActive; // to the left of active editor
+ // Insert to LEFT or RIGHT of active editor
+ else {
+
+ // Insert to the LEFT of active editor
+ if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
+ if (indexOfActive === 0 || !this.editors.length) {
+ targetIndex = 0; // to the left becoming first editor in list
+ } else {
+ targetIndex = indexOfActive; // to the left of active editor
+ }
+ }
+
+ // Insert to the RIGHT of active editor
+ else {
+ targetIndex = indexOfActive + 1;
+ }
+
+ // Always make sure targetIndex is after sticky editors
+ // unless we are explicitly told to make the editor sticky
+ if (!makeSticky && this.isSticky(targetIndex)) {
+ targetIndex = this.sticky + 1;
}
}
- // Insert to the RIGHT of active editor
- else {
- targetIndex = indexOfActive + 1;
+ // If the editor becomes sticky, increment the sticky index and adjust
+ // the targetIndex to be at the end of sticky editors unless already.
+ if (makeSticky) {
+ this.sticky++;
+
+ if (!this.isSticky(targetIndex)) {
+ targetIndex = this.sticky;
+ }
}
// Insert into our list of editors if pinned or we have no preview editor
@@ -228,6 +271,7 @@ export class EditorGroup extends Disposable {
// Existing editor
else {
+ const [existingEditor] = existingEditorAndIndex;
// Pin it
if (makePinned) {
@@ -244,6 +288,12 @@ export class EditorGroup extends Disposable {
this.moveEditor(existingEditor, options.index);
}
+ // Stick it (intentionally after the moveEditor call in case
+ // the editor was already moved into the sticky range)
+ if (makeSticky) {
+ this.doStick(existingEditor, this.indexOf(existingEditor));
+ }
+
return existingEditor;
}
}
@@ -252,8 +302,7 @@ export class EditorGroup extends Disposable {
const listeners = new DisposableStore();
// Re-emit disposal of editor input as our own event
- const onceDispose = Event.once(editor.onDispose);
- listeners.add(onceDispose(() => {
+ listeners.add(Event.once(editor.onDispose)(() => {
if (this.indexOf(editor) >= 0) {
this._onDidDisposeEditor.fire(editor);
}
@@ -309,6 +358,7 @@ export class EditorGroup extends Disposable {
}
const editor = this.editors[index];
+ const sticky = this.isSticky(index);
// Active Editor closed
if (openNext && this.matches(this.active, editor)) {
@@ -344,45 +394,18 @@ export class EditorGroup extends Disposable {
this.splice(index, true);
// Event
- return { editor, replaced, index, groupId: this.id };
- }
-
- closeEditors(except: EditorInput, direction?: CloseDirection): void {
- const index = this.indexOf(except);
- if (index === -1) {
- return; // not found
- }
-
- // Close to the left
- if (direction === CloseDirection.LEFT) {
- for (let i = index - 1; i >= 0; i--) {
- this.closeEditor(this.editors[i]);
- }
- }
-
- // Close to the right
- else if (direction === CloseDirection.RIGHT) {
- for (let i = this.editors.length - 1; i > index; i--) {
- this.closeEditor(this.editors[i]);
- }
- }
-
- // Both directions
- else {
- this.mru.filter(e => !this.matches(e, except)).forEach(e => this.closeEditor(e));
- }
- }
-
- closeAllEditors(): void {
-
- // Optimize: close all non active editors first to produce less upstream work
- this.mru.filter(e => !this.matches(e, this.active)).forEach(e => this.closeEditor(e));
- if (this.active) {
- this.closeEditor(this.active);
- }
+ return { editor, replaced, sticky, index, groupId: this.id };
}
moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
+
+ // Ensure toIndex is in bounds of our model
+ if (toIndex >= this.editors.length) {
+ toIndex = this.editors.length - 1;
+ } else if (toIndex < 0) {
+ toIndex = 0;
+ }
+
const index = this.indexOf(candidate);
if (index < 0 || toIndex === index) {
return undefined; // {{SQL CARBON EDIT}} strict-null-check
@@ -390,6 +413,16 @@ export class EditorGroup extends Disposable {
const editor = this.editors[index];
+ // Adjust sticky index: editor moved out of sticky state into unsticky state
+ if (this.isSticky(index) && toIndex > this.sticky) {
+ this.sticky--;
+ }
+
+ // ...or editor moved into sticky state from unsticky state
+ else if (!this.isSticky(index) && toIndex <= this.sticky) {
+ this.sticky++;
+ }
+
// Move
this.editors.splice(index, 1);
this.editors.splice(toIndex, 0, editor);
@@ -401,11 +434,13 @@ export class EditorGroup extends Disposable {
}
setActive(candidate: EditorInput): EditorInput | undefined {
- const editor = this.findEditor(candidate);
- if (!editor) {
+ const res = this.findEditor(candidate);
+ if (!res) {
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
}
+ const [editor] = res;
+
this.doSetActive(editor);
return editor;
@@ -428,18 +463,20 @@ export class EditorGroup extends Disposable {
}
pin(candidate: EditorInput): EditorInput | undefined {
- const editor = this.findEditor(candidate);
- if (!editor) {
+ const res = this.findEditor(candidate);
+ if (!res) {
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
}
+ const [editor] = res;
+
this.doPin(editor);
return editor;
}
private doPin(editor: EditorInput): void {
- if (!this.isPreview(editor)) {
+ if (this.isPinned(editor)) {
return; // can only pin a preview editor
}
@@ -451,11 +488,13 @@ export class EditorGroup extends Disposable {
}
unpin(candidate: EditorInput): EditorInput | undefined {
- const editor = this.findEditor(candidate);
- if (!editor) {
+ const res = this.findEditor(candidate);
+ if (!res) {
return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
}
+ const [editor] = res;
+
this.doUnpin(editor);
return editor;
@@ -479,33 +518,97 @@ export class EditorGroup extends Disposable {
}
}
- isPinned(editor: EditorInput): boolean;
- isPinned(index: number): boolean;
- isPinned(arg1: EditorInput | number): boolean {
- if (!this.preview) {
- return true; // no preview editor
- }
-
+ isPinned(editorOrIndex: EditorInput | number): boolean {
let editor: EditorInput;
- let index: number;
- if (typeof arg1 === 'number') {
- editor = this.editors[arg1];
- index = arg1;
+ if (typeof editorOrIndex === 'number') {
+ editor = this.editors[editorOrIndex];
} else {
- editor = arg1;
- index = this.indexOf(editor);
- }
-
- if (index === -1 || !editor) {
- return false; // editor not found
+ editor = editorOrIndex;
}
return !this.matches(this.preview, editor);
}
+ stick(candidate: EditorInput): EditorInput | undefined {
+ const res = this.findEditor(candidate);
+ if (!res) {
+ return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
+ }
+
+ const [editor, index] = res;
+
+ this.doStick(editor, index);
+
+ return editor;
+ }
+
+ private doStick(editor: EditorInput, index: number): void {
+ if (this.isSticky(index)) {
+ return; // can only stick a non-sticky editor
+ }
+
+ // Pin editor
+ this.pin(editor);
+
+ // Move editor to be the last sticky editor
+ this.moveEditor(editor, this.sticky + 1);
+
+ // Adjust sticky index
+ this.sticky++;
+ }
+
+ unstick(candidate: EditorInput): EditorInput | undefined {
+ const res = this.findEditor(candidate);
+ if (!res) {
+ return undefined; // not found {{SQL CARBON EDIT}} strict-null-check
+ }
+
+ const [editor, index] = res;
+
+ this.doUnstick(editor, index);
+
+ return editor;
+ }
+
+ private doUnstick(editor: EditorInput, index: number): void {
+ if (!this.isSticky(index)) {
+ return; // can only unstick a sticky editor
+ }
+
+ // Move editor to be the first non-sticky editor
+ this.moveEditor(editor, this.sticky);
+
+ // Adjust sticky index
+ this.sticky--;
+ }
+
+ isSticky(candidateOrIndex: EditorInput | number): boolean {
+ if (this.sticky < 0) {
+ return false; // no sticky editor
+ }
+
+ let index: number;
+ if (typeof candidateOrIndex === 'number') {
+ index = candidateOrIndex;
+ } else {
+ index = this.indexOf(candidateOrIndex);
+ }
+
+ if (index < 0) {
+ return false;
+ }
+
+ return index <= this.sticky;
+ }
+
private splice(index: number, del: boolean, editor?: EditorInput): void {
const editorToDeleteOrReplace = this.editors[index];
+ // Perform on sticky index
+ if (del && this.isSticky(index)) {
+ this.sticky--;
+ }
+
// Perform on editors array
if (editor) {
this.editors.splice(index, del ? 1 : 0, editor);
@@ -513,35 +616,38 @@ export class EditorGroup extends Disposable {
this.editors.splice(index, del ? 1 : 0);
}
- // Add
- if (!del && editor) {
- if (this.mru.length === 0) {
- // the list of most recent editors is empty
- // so this editor can only be the most recent
- this.mru.push(editor);
- } else {
- // we have most recent editors. as such we
- // put this newly opened editor right after
- // the current most recent one because it cannot
- // be the most recently active one unless
- // it becomes active. but it is still more
- // active then any other editor in the list.
- this.mru.splice(1, 0, editor);
- }
- }
-
- // Remove / Replace
- else {
- const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
-
- // Remove
- if (del && !editor) {
- this.mru.splice(indexInMRU, 1); // remove from MRU
+ // Perform on MRU
+ {
+ // Add
+ if (!del && editor) {
+ if (this.mru.length === 0) {
+ // the list of most recent editors is empty
+ // so this editor can only be the most recent
+ this.mru.push(editor);
+ } else {
+ // we have most recent editors. as such we
+ // put this newly opened editor right after
+ // the current most recent one because it cannot
+ // be the most recently active one unless
+ // it becomes active. but it is still more
+ // active then any other editor in the list.
+ this.mru.splice(1, 0, editor);
+ }
}
- // Replace
- else if (del && editor) {
- this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
+ // Remove / Replace
+ else {
+ const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
+
+ // Remove
+ if (del && !editor) {
+ this.mru.splice(indexInMRU, 1); // remove from MRU
+ }
+
+ // Replace
+ else if (del && editor) {
+ this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
+ }
}
}
}
@@ -560,13 +666,13 @@ export class EditorGroup extends Disposable {
return -1;
}
- private findEditor(candidate: EditorInput | null): EditorInput | undefined {
+ private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
const index = this.indexOf(candidate, this.editors);
if (index === -1) {
return undefined;
}
- return this.editors[index];
+ return [this.editors[index], index];
}
contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean {
@@ -599,7 +705,7 @@ export class EditorGroup extends Disposable {
group.mru = this.mru.slice(0);
group.preview = this.preview;
group.active = this.active;
- group.editorOpenPositioning = this.editorOpenPositioning;
+ group.sticky = this.sticky;
return group;
}
@@ -609,32 +715,52 @@ export class EditorGroup extends Disposable {
// Serialize all editor inputs so that we can store them.
// Editors that cannot be serialized need to be ignored
- // from mru, active and preview if any.
+ // from mru, active, preview and sticky if any.
let serializableEditors: EditorInput[] = [];
let serializedEditors: ISerializedEditorInput[] = [];
let serializablePreviewIndex: number | undefined;
- this.editors.forEach(e => {
- const factory = registry.getEditorInputFactory(e.getTypeId());
- if (factory) {
- const value = factory.serialize(e);
- if (typeof value === 'string') {
- serializedEditors.push({ id: e.getTypeId(), value });
- serializableEditors.push(e);
+ let serializableSticky = this.sticky;
- if (this.preview === e) {
+ for (let i = 0; i < this.editors.length; i++) {
+ const editor = this.editors[i];
+ let canSerializeEditor = false;
+
+ const factory = registry.getEditorInputFactory(editor.getTypeId());
+ if (factory) {
+ const value = factory.serialize(editor);
+
+ // Editor can be serialized
+ if (typeof value === 'string') {
+ canSerializeEditor = true;
+
+ serializedEditors.push({ id: editor.getTypeId(), value });
+ serializableEditors.push(editor);
+
+ if (this.preview === editor) {
serializablePreviewIndex = serializableEditors.length - 1;
}
}
- }
- });
- const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0);
+ // Editor cannot be serialized
+ else {
+ canSerializeEditor = false;
+ }
+ }
+
+ // Adjust index of sticky editors if the editor cannot be serialized and is pinned
+ if (!canSerializeEditor && this.isSticky(i)) {
+ serializableSticky--;
+ }
+ }
+
+ const serializableMru = this.mru.map(editor => this.indexOf(editor, serializableEditors)).filter(i => i >= 0);
return {
id: this.id,
editors: serializedEditors,
mru: serializableMru,
preview: serializablePreviewIndex,
+ sticky: serializableSticky >= 0 ? serializableSticky : undefined
};
}
@@ -649,18 +775,22 @@ export class EditorGroup extends Disposable {
this._id = EditorGroup.IDS++; // backwards compatibility
}
- this.editors = coalesce(data.editors.map(e => {
+ this.editors = coalesce(data.editors.map((e, index) => {
+ let editor: EditorInput | undefined = undefined;
+
const factory = registry.getEditorInputFactory(e.id);
if (factory) {
- const editor = doHandleUpgrade(factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization
+ editor = doHandleUpgrade(factory.deserialize(this.instantiationService, e.value)); // {{SQL CARBON EDIT}} handle upgrade path to new serialization
if (editor) {
this.registerEditorListeners(editor);
}
-
- return editor;
}
- return null;
+ if (!editor && typeof data.sticky === 'number' && index <= data.sticky) {
+ data.sticky--; // if editor cannot be deserialized but was sticky, we need to decrease sticky index
+ }
+
+ return editor;
}));
this.mru = coalesce(data.mru.map(i => this.editors[i]));
@@ -671,6 +801,10 @@ export class EditorGroup extends Disposable {
this.preview = this.editors[data.preview];
}
+ if (typeof data.sticky === 'number') {
+ this.sticky = data.sticky;
+ }
+
return this._id;
}
}
diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts
index dfd947e301..b9cf1c3d63 100644
--- a/src/vs/workbench/common/views.ts
+++ b/src/vs/workbench/common/views.ts
@@ -282,7 +282,7 @@ export interface IViewContentDescriptor {
export interface IViewsRegistry {
- readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>;
+ readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>;
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>;
@@ -290,6 +290,8 @@ export interface IViewsRegistry {
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
+ registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void;
+
deregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
@@ -319,8 +321,8 @@ function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewConten
class ViewsRegistry extends Disposable implements IViewsRegistry {
- private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>());
- readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event;
+ private readonly _onViewsRegistered = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }[]>());
+ readonly onViewsRegistered = this._onViewsRegistered.event;
private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>());
readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event;
@@ -336,8 +338,12 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
private _viewWelcomeContents = new SetMap();
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
- this.addViews(views, viewContainer);
- this._onViewsRegistered.fire({ views: views, viewContainer });
+ this.registerViews2([{ views, viewContainer }]);
+ }
+
+ registerViews2(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void {
+ views.forEach(({ views, viewContainer }) => this.addViews(views, viewContainer));
+ this._onViewsRegistered.fire(views);
}
deregisterViews(viewDescriptors: IViewDescriptor[], viewContainer: ViewContainer): void {
diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
index b9d449596e..528d027fae 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
@@ -172,6 +172,20 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
updatePickerItems();
disposables.add(picker.onDidChangeValue(updatePickerItems));
+ let ignoreFirstActiveEvent = true;
+ disposables.add(picker.onDidChangeActive(() => {
+ const [entry] = picker.activeItems;
+
+ if (entry && entries[entry.index]) {
+ if (ignoreFirstActiveEvent) {
+ ignoreFirstActiveEvent = false;
+ return;
+ }
+
+ entries[entry.index]?.reveal();
+ }
+ }));
+
}).catch(err => {
onUnexpectedError(err);
picker.hide();
diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts
index 357c839a4e..0202571c2a 100644
--- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts
+++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts
@@ -350,7 +350,7 @@ class BreakpointsRenderer implements IListRendererstackFrame.thread).getStaleCallStack();
+ callStack = callStack.length > 0 ? callStack : stackFrame.thread.getCallStack();
+ const otherSources = callStack.map(sf => sf.source).filter(s => s !== stackFrame.source);
+ let suffixLength = 0;
+ otherSources.forEach(s => {
+ if (s.name === stackFrame.source.name) {
+ suffixLength = Math.max(suffixLength, commonSuffixLength(stackFrame.source.uri.path, s.uri.path));
+ }
+ });
+ if (suffixLength === 0) {
+ return stackFrame.source.name;
+ }
+
+ const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1));
+ return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substr(from);
+}
+
export class CallStackView extends ViewPane {
private pauseMessage!: HTMLSpanElement;
private pauseMessageLabel!: HTMLSpanElement;
@@ -453,7 +475,7 @@ class SessionsRenderer implements ITreeRenderer {
@@ -562,7 +584,7 @@ class StackFramesRenderer implements ITreeRendererelement).name);
}
if (element instanceof StackFrame) {
- return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
+ return nls.localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}, callstack, debug", element.name, element.range.startLineNumber, getSpecificSourceName(element));
}
if (isDebugSession(element)) {
return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
index 086f205c03..08570508fd 100644
--- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
@@ -229,11 +229,6 @@ configurationRegistry.registerConfiguration({
default: 'openOnSessionStart',
description: nls.localize('openDebug', "Controls when the debug view should open.")
},
- 'debug.enableAllHovers': {
- type: 'boolean',
- description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."),
- default: false
- },
'debug.showSubSessionsInToolBar': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showSubSessionsInToolBar' }, "Controls whether the debug sub-sessions are shown in the debug tool bar. When this setting is false the stop command on a sub-session will also stop the parent session."),
diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
index 9858d17d70..939b78c497 100644
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -24,7 +24,6 @@ import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { PanelFocusContext } from 'vs/workbench/common/panel';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
-import { onUnexpectedError } from 'vs/base/common/errors';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -206,7 +205,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5,
when: CONTEXT_IN_DEBUG_MODE,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const debugService = accessor.get(IDebugService);
let session: IDebugSession | undefined;
if (isSessionContext(context)) {
@@ -217,10 +216,10 @@ export function registerCommands(): void {
if (!session) {
const { launch, name } = debugService.getConfigurationManager().selectedConfiguration;
- debugService.startDebugging(launch, name, { noDebug: false });
+ await debugService.startDebugging(launch, name, { noDebug: false });
} else {
session.removeReplExpressions();
- debugService.restartSession(session).then(undefined, onUnexpectedError);
+ await debugService.restartSession(session);
}
}
});
@@ -268,10 +267,15 @@ export function registerCommands(): void {
CommandsRegistry.registerCommand({
id: DISCONNECT_ID,
- handler: (accessor: ServicesAccessor, sessionId: string | undefined) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const debugService = accessor.get(IDebugService);
- const session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession;
- debugService.stopSession(session).then(undefined, onUnexpectedError);
+ let session: IDebugSession | undefined;
+ if (isSessionContext(context)) {
+ session = debugService.getModel().getSession(context.sessionId);
+ } else {
+ session = debugService.getViewModel().focusedSession;
+ }
+ await debugService.stopSession(session);
}
});
@@ -280,7 +284,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift | KeyCode.F5,
when: CONTEXT_IN_DEBUG_MODE,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const debugService = accessor.get(IDebugService);
let session: IDebugSession | undefined;
if (isSessionContext(context)) {
@@ -296,7 +300,7 @@ export function registerCommands(): void {
session = session.parentSession;
}
- debugService.stopSession(session).then(undefined, onUnexpectedError);
+ await debugService.stopSession(session);
}
});
@@ -336,9 +340,9 @@ export function registerCommands(): void {
CommandsRegistry.registerCommand({
id: 'debug.startFromConfig',
- handler: (accessor, config: IConfig) => {
+ handler: async (accessor, config: IConfig) => {
const debugService = accessor.get(IDebugService);
- debugService.startDebugging(undefined, config).then(undefined, onUnexpectedError);
+ await debugService.startDebugging(undefined, config);
}
});
diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
index af8b4c6e8b..1d2e8b4643 100644
--- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
@@ -30,10 +30,8 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
import { first } from 'vs/base/common/arrays';
import { memoize, createMemoizer } from 'vs/base/common/decorators';
import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
-import { CancellationToken } from 'vs/base/common/cancellation';
import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover';
import { ITextModel } from 'vs/editor/common/model';
-import { getHover } from 'vs/editor/contrib/hover/getHover';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { basename } from 'vs/base/common/path';
@@ -170,7 +168,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
private toDispose: IDisposable[];
private hoverWidget: DebugHoverWidget;
- private nonDebugHoverPosition: Position | undefined;
private hoverRange: Range | null = null;
private mouseDown = false;
private static readonly MEMOIZER = createMemoizer();
@@ -204,7 +201,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.toDispose.push(this.editor.onMouseUp(() => this.mouseDown = false));
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
this.toDispose.push(this.editor.onMouseLeave((e: IPartialEditorMouseEvent) => {
- this.provideNonDebugHoverScheduler.cancel();
const hoverDomNode = this.hoverWidget.getDomNode();
if (!hoverDomNode) {
return;
@@ -315,24 +311,11 @@ class DebugEditorContribution implements IDebugEditorContribution {
return scheduler;
}
- @memoize
- private get provideNonDebugHoverScheduler(): RunOnceScheduler {
- const scheduler = new RunOnceScheduler(() => {
- if (this.editor.hasModel() && this.nonDebugHoverPosition) {
- getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None);
- }
- }, HOVER_DELAY);
- this.toDispose.push(scheduler);
-
- return scheduler;
- }
-
private hideHoverWidget(): void {
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
this.hideHoverScheduler.schedule();
}
this.showHoverScheduler.cancel();
- this.provideNonDebugHoverScheduler.cancel();
}
// hover business
@@ -351,10 +334,6 @@ class DebugEditorContribution implements IDebugEditorContribution {
return;
}
- if (this.configurationService.getValue('debug').enableAllHovers && mouseEvent.target.position) {
- this.nonDebugHoverPosition = mouseEvent.target.position;
- this.provideNonDebugHoverScheduler.schedule();
- }
const targetType = mouseEvent.target.type;
const stopKey = env.isMacintosh ? 'metaKey' : 'ctrlKey';
diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css
index a62ee921e9..39e4b01889 100644
--- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css
+++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css
@@ -66,10 +66,8 @@
/* Debug viewlet trees */
.debug-pane .line-number {
- border-radius: 2px;
- font-size: 0.9em;
- padding: 0 3px;
- line-height: 20px;
+ padding-left: 4px;
+ padding-right: 4px;
}
.debug-pane .disabled {
@@ -118,6 +116,8 @@
.debug-pane .debug-call-stack .thread > .state,
.debug-pane .debug-call-stack .session > .state {
+ display: flex;
+ align-items: center;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
@@ -159,9 +159,8 @@
.debug-pane .debug-call-stack .thread > .state > .label,
.debug-pane .debug-call-stack .session > .state > .label {
- border-radius: 2px;
font-size: 0.8em;
- padding: 0 3px;
+ min-height: auto;
}
.debug-pane .debug-call-stack .stack-frame {
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index c1ce98ff28..1f7289523f 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -318,7 +318,6 @@ export interface IStackFrame extends ITreeElement {
readonly source: Source;
getScopes(): Promise;
getMostSpecificScopes(range: IRange): Promise>;
- getSpecificSourceName(): string;
forgetScopes(): void;
restart(): Promise;
toString(): string;
diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts
index 31f3a9552b..b95ce2296e 100644
--- a/src/vs/workbench/contrib/debug/common/debugModel.ts
+++ b/src/vs/workbench/contrib/debug/common/debugModel.ts
@@ -17,8 +17,6 @@ import {
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint
} from 'vs/workbench/contrib/debug/common/debug';
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
-import { commonSuffixLength } from 'vs/base/common/strings';
-import { posix } from 'vs/base/common/path';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextEditorPane } from 'vs/workbench/common/editor';
@@ -325,26 +323,6 @@ export class StackFrame implements IStackFrame {
return this.scopes;
}
- getSpecificSourceName(): string {
- // To reduce flashing of the path name and the way we fetch stack frames
- // We need to compute the source name based on the other frames in the stale call stack
- let callStack = (this.thread).getStaleCallStack();
- callStack = callStack.length > 0 ? callStack : this.thread.getCallStack();
- const otherSources = callStack.map(sf => sf.source).filter(s => s !== this.source);
- let suffixLength = 0;
- otherSources.forEach(s => {
- if (s.name === this.source.name) {
- suffixLength = Math.max(suffixLength, commonSuffixLength(this.source.uri.path, s.uri.path));
- }
- });
- if (suffixLength === 0) {
- return this.source.name;
- }
-
- const from = Math.max(0, this.source.uri.path.lastIndexOf(posix.sep, this.source.uri.path.length - suffixLength - 1));
- return (from > 0 ? '...' : '') + this.source.uri.path.substr(from);
- }
-
async getMostSpecificScopes(range: IRange): Promise {
const scopes = await this.getScopes();
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts
index 75bb423d5e..a32e1a8af7 100644
--- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts
+++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts
@@ -14,7 +14,7 @@ import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/d
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
import { Constants } from 'vs/base/common/uint';
-import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
+import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView';
import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService';
import { generateUuid } from 'vs/base/common/uuid';
@@ -250,8 +250,8 @@ suite('Debug - CallStack', () => {
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
- assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
- assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
+ assert.equal(getSpecificSourceName(firstStackFrame), '.../b/c/d/internalModule.js');
+ assert.equal(getSpecificSourceName(secondStackFrame), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
index 80994a24ac..1287fd6051 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -1629,11 +1629,11 @@ export class ShowInstalledExtensionsAction extends Action {
super(id, label, undefined, true);
}
- run(): Promise {
+ run(refresh?: boolean): Promise {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
.then(viewlet => {
- viewlet.search('@installed ');
+ viewlet.search('@installed ', refresh);
viewlet.focus();
});
}
@@ -2983,90 +2983,84 @@ export class InstallVSIXAction extends Action {
@IHostService private readonly hostService: IHostService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IExtensionService private readonly extensionService: IExtensionService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService, // {{SQL CARBON EDIT}}
@IStorageService private storageService: IStorageService
) {
super(id, label, 'extension-action install-vsix', true);
}
- run(): Promise {
+ async run(vsixPaths?: URI[]): Promise {
// {{SQL CARBON EDIT}} - Replace run body
let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey);
if (extensionPolicy === ExtensionsPolicy.allowAll) {
- return Promise.resolve(this.fileDialogService.showOpenDialog({
- title: localize('installFromVSIX', "Install from VSIX"),
- filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
- canSelectFiles: true,
- openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
- }).then(result => {
- if (!result) {
- return Promise.resolve();
- }
- return Promise.all(result.map(vsix => {
- if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
+ if (!vsixPaths) {
+ vsixPaths = await this.fileDialogService.showOpenDialog({
+ title: localize('installFromVSIX', "Install from VSIX"),
+ filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
+ canSelectFiles: true,
+ openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
+ });
+ }
+
+ await Promise.all(vsixPaths.map(async vsix => {
+ if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) {
+ const accept = await new Promise(resolve => {
this.notificationService.prompt(
Severity.Warning,
localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'),
[
{
label: localize('thirdPartExt.yes', 'Yes'),
- run: () => {
- this.extensionsWorkbenchService.install(vsix).then(extension => {
- const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
- const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
- : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
- const actions = requireReload ? [{
- label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
- run: () => this.hostService.reload()
- }] : [];
- this.notificationService.prompt(
- Severity.Info,
- message,
- actions,
- { sticky: true }
- );
- });
- }
+ run: () => resolve(true)
},
{
label: localize('thirdPartyExt.no', 'No'),
- run: () => { return Promise.resolve(); }
+ run: () => resolve(false)
},
{
label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'),
isSecondary: true,
run: () => {
this.storageService.store(vsix.fsPath, true, StorageScope.GLOBAL);
- return Promise.resolve();
+ resolve(true);
}
}
],
{ sticky: true }
);
- } else {
- this.extensionsWorkbenchService.install(vsix).then(extension => {
- const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
- const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id)
- : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id);
- const actions = requireReload ? [{
- label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
- run: () => this.hostService.reload()
- }] : [];
- this.notificationService.prompt(
- Severity.Info,
- message,
- actions,
- { sticky: true }
- );
- });
+ });
+
+ if (!accept) {
+ return undefined;
}
- })).then(() => Promise.resolve());
- }));
+ }
+
+ return this.extensionsWorkbenchService.install(vsix);
+ })).then(async (extensions) => {
+ for (const extension of extensions) {
+ if (!extension) {
+ return;
+ }
+ const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local)));
+ const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.displayName || extension.name)
+ : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name);
+ const actions = requireReload ? [{
+ label: localize('InstallVSIXAction.reloadNow', "Reload Now"),
+ run: () => this.hostService.reload()
+ }] : [];
+ this.notificationService.prompt(
+ Severity.Info,
+ message,
+ actions,
+ { sticky: true }
+ );
+ }
+ await this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run(true);
+ });
} else {
this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.'));
- return Promise.resolve();
}
- // {{SQL CARBON EDIT}} - End
}
get enabled(): boolean { // {{SQL CARBON EDIT}} add enabled logic
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index df6ef5b797..fb38c6f116 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -14,7 +14,7 @@ import { IAction, Action } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
-import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
+import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -57,6 +57,9 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { MementoObject } from 'vs/workbench/common/memento';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
+import { DragAndDropObserver } from 'vs/workbench/browser/dnd';
+import { URI } from 'vs/base/common/uri';
+import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false);
const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true);
@@ -400,8 +403,12 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
addClass(parent, 'extensions-viewlet');
this.root = parent;
- const header = append(this.root, $('.header'));
+ const overlay = append(this.root, $('.overlay'));
+ const overlayBackgroundColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
+ overlay.style.backgroundColor = overlayBackgroundColor;
+ hide(overlay);
+ const header = append(this.root, $('.header'));
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : '';
@@ -435,6 +442,49 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
}
}));
+ // Register DragAndDrop support
+ this._register(new DragAndDropObserver(this.root, {
+ onDragEnd: (e: DragEvent) => undefined,
+ onDragEnter: (e: DragEvent) => {
+ if (this.isSupportedDragElement(e)) {
+ show(overlay);
+ }
+ },
+ onDragLeave: (e: DragEvent) => {
+ if (this.isSupportedDragElement(e)) {
+ hide(overlay);
+ }
+ },
+ onDragOver: (e: DragEvent) => {
+ if (e.dataTransfer) {
+ e.dataTransfer.dropEffect = this.isSupportedDragElement(e) ? 'copy' : 'none';
+ }
+ },
+ onDrop: async (e: DragEvent) => {
+ if (this.isSupportedDragElement(e)) {
+ hide(overlay);
+
+ if (e.dataTransfer && e.dataTransfer.files.length > 0) {
+ let vsixPaths: URI[] = [];
+ for (let index = 0; index < e.dataTransfer.files.length; index++) {
+ const path = e.dataTransfer.files.item(index)!.path;
+ if (path.indexOf('.vsix') !== -1) {
+ vsixPaths.push(URI.parse(path));
+ }
+ }
+
+ try {
+ // Attempt to install the extension(s)
+ await this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL).run(vsixPaths);
+ }
+ catch (err) {
+ this.notificationService.error(err);
+ }
+ }
+ }
+ },
+ }));
+
super.create(append(this.root, $('.extensions')));
}
@@ -622,6 +672,15 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.notificationService.error(err);
}
+
+ private isSupportedDragElement(e: DragEvent): boolean {
+ if (e.dataTransfer) {
+ const typesLowerCase = e.dataTransfer.types.map(t => t.toLocaleLowerCase());
+ return typesLowerCase.indexOf('files') !== -1;
+ }
+
+ return false;
+ }
}
export class StatusUpdater extends Disposable implements IWorkbenchContribution {
diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css
index 2f9d902154..dfe87d3d3d 100644
--- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css
+++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css
@@ -4,9 +4,19 @@
*--------------------------------------------------------------------------------------------*/
.extensions-viewlet {
+ position: relative;
height: 100%;
}
+.extensions-viewlet > .overlay {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 2;
+}
+
.extensions-viewlet > .header {
height: 41px;
box-sizing: border-box;
diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css
index 06a3588294..1f7baf8a48 100644
--- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css
+++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css
@@ -60,10 +60,9 @@
}
.dirty-count.monaco-count-badge {
- padding: 1px 6px 2px;
+ padding: 2px 4px;
margin-left: 6px;
min-height: auto;
- border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
}
.explorer-viewlet .explorer-item.nonexistent-root {
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
index 6318bef652..32d2699f7f 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -890,7 +890,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop {
const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData, originalEvent);
if (items && items.length && originalEvent.dataTransfer) {
// Apply some datatransfer types to allow for dragging the element outside of the application
- this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent);
+ this.instantiationService.invokeFunction(fillResourceDataTransfers, items, undefined, originalEvent);
// The only custom data transfer we set from the explorer is a file transfer
// to be able to DND between multiple code file explorers across windows
@@ -965,7 +965,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop {
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise {
const droppedResources = extractResources(originalEvent, true);
// Check for dropped external files to be folders
- const result = await this.fileService.resolveAll(droppedResources);
+ const result = await this.fileService.resolveAll(droppedResources.map(droppedResource => ({ resource: droppedResource.resource })));
// Pass focus to window
this.hostService.focus();
diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
index 36f69e6987..518bee68b5 100644
--- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
@@ -184,7 +184,7 @@ export class OpenEditorsView extends ViewPane {
super.renderHeaderTitle(container, this.title);
const count = dom.append(container, $('.count'));
- this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge'));
+ this.dirtyCountElement = dom.append(count, $('.dirty-count.monaco-count-badge.long'));
this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
@@ -660,7 +660,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop {
if (resources.length) {
// Apply some datatransfer types to allow for dragging the element outside of the application
- this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
+ this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts
index 47dad8eb1f..def77010a0 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts
@@ -540,7 +540,12 @@ registerAction2(class extends InsertCellCommand {
id: INSERT_CODE_CELL_ABOVE_COMMAND_ID,
title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above"),
category: NOTEBOOK_ACTIONS_CATEGORY,
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
},
CellKind.Code,
'above');
@@ -572,7 +577,12 @@ registerAction2(class extends InsertCellCommand {
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
category: NOTEBOOK_ACTIONS_CATEGORY,
icon: { id: 'codicon/add' },
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.CtrlCmd | KeyCode.Enter,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
},
CellKind.Code,
'below');
@@ -770,7 +780,12 @@ registerAction2(class extends Action2 {
title: localize('notebookActions.moveCellUp', "Move Cell Up"),
category: NOTEBOOK_ACTIONS_CATEGORY,
icon: { id: 'codicon/arrow-up' },
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.Alt | KeyCode.UpArrow,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
});
}
@@ -794,7 +809,12 @@ registerAction2(class extends Action2 {
title: localize('notebookActions.moveCellDown', "Move Cell Down"),
category: NOTEBOOK_ACTIONS_CATEGORY,
icon: { id: 'codicon/arrow-down' },
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.Alt | KeyCode.DownArrow,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
});
}
@@ -969,7 +989,12 @@ registerAction2(class extends Action2 {
id: COPY_CELL_UP_COMMAND_ID,
title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
category: NOTEBOOK_ACTIONS_CATEGORY,
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
});
}
@@ -992,7 +1017,12 @@ registerAction2(class extends Action2 {
id: COPY_CELL_DOWN_COMMAND_ID,
title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
category: NOTEBOOK_ACTIONS_CATEGORY,
- f1: true
+ f1: true,
+ keybinding: {
+ primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
+ weight: KeybindingWeight.WorkbenchContrib
+ }
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css
index 43c25648c3..5896d6add1 100644
--- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css
+++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css
@@ -21,6 +21,10 @@
overflow: visible !important;
} */
+.monaco-workbench .part.editor > .content .notebook-editor .simple-fr-find-part-wrapper.visible {
+ z-index: 100;
+}
+
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .overflowingContentWidgets > div {
z-index: 600 !important;
/* @rebornix: larger than the editor title bar */
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
index 72209e7926..8ceb73551b 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
@@ -110,7 +110,6 @@ export interface ICellViewModel {
resolveTextModel(): Promise;
getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata;
getSelectionsStartPosition(): IPosition[] | undefined;
- getLinesContent(): string[];
}
export interface IEditableCellViewModel extends ICellViewModel {
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index a13846a6a7..c2030ad417 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -47,6 +47,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IPosition, Position } from 'vs/editor/common/core/position';
+import { IReadonlyTextBuffer } from 'vs/editor/common/model';
const $ = DOM.$;
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
@@ -170,19 +171,20 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
return true;
}
+ private updateEditorFocus() {
+ // Note - focus going to the webview will fire 'blur', but the webview element will be
+ // a descendent of the notebook editor root.
+ this.editorFocus?.set(DOM.isAncestor(document.activeElement, this.getDomNode()));
+ }
+
protected createEditor(parent: HTMLElement): void {
this._rootElement = DOM.append(parent, $('.notebook-editor'));
this.createBody(this._rootElement);
this.generateFontInfo();
this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
this.editorFocus.set(true);
- this._register(this.onDidFocus(() => {
- this.editorFocus?.set(true);
- }));
-
- this._register(this.onDidBlur(() => {
- this.editorFocus?.set(false);
- }));
+ this._register(this.onDidFocus(() => this.updateEditorFocus()));
+ this._register(this.onDidBlur(() => this.updateEditorFocus()));
this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
this.editorEditable.set(true);
@@ -307,6 +309,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
this.webview = this.instantiationService.createInstance(BackLayerWebView, this);
+ this.webview.webview.onDidBlur(() => this.updateEditorFocus());
+ this.webview.webview.onDidFocus(() => this.updateEditorFocus());
this._register(this.webview.onMessage(message => {
if (this.viewModel) {
this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.viewModel.uri, message);
@@ -778,11 +782,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
return newCell;
}
- private isAtEOL(p: IPosition, lines: string[]) {
- const line = lines[p.lineNumber - 1];
- return line.length + 1 === p.column;
- }
-
private pushIfAbsent(positions: IPosition[], p: IPosition) {
const last = positions.length > 0 ? positions[positions.length - 1] : undefined;
if (!last || last.lineNumber !== p.lineNumber || last.column !== p.column) {
@@ -795,8 +794,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
* Move end of line split points to the beginning of the next line;
* Avoid duplicate split points
*/
- private splitPointsToBoundaries(splitPoints: IPosition[], lines: string[]): IPosition[] | null {
+ private splitPointsToBoundaries(splitPoints: IPosition[], textBuffer: IReadonlyTextBuffer): IPosition[] | null {
const boundaries: IPosition[] = [];
+ const lineCnt = textBuffer.getLineCount();
+ const getLineLen = (lineNumber: number) => {
+ return textBuffer.getLineLength(lineNumber);
+ };
// split points need to be sorted
splitPoints = splitPoints.sort((l, r) => {
@@ -809,22 +812,21 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
this.pushIfAbsent(boundaries, new Position(1, 1));
for (let sp of splitPoints) {
- if (this.isAtEOL(sp, lines) && sp.lineNumber < lines.length) {
+ if (getLineLen(sp.lineNumber) + 1 === sp.column && sp.lineNumber < lineCnt) {
sp = new Position(sp.lineNumber + 1, 1);
}
this.pushIfAbsent(boundaries, sp);
}
// eat-up any split point at the beginning, i.e. we ignore the split point at the very end
- this.pushIfAbsent(boundaries, new Position(lines.length, lines[lines.length - 1].length + 1));
+ this.pushIfAbsent(boundaries, new Position(lineCnt, getLineLen(lineCnt) + 1));
// if we only have two then they describe the whole range and nothing needs to be split
return boundaries.length > 2 ? boundaries : null;
}
private computeCellLinesContents(cell: IEditableCellViewModel, splitPoints: IPosition[]): string[] | null {
- const lines = cell.getLinesContent();
- const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, lines);
+ const rangeBoundaries = this.splitPointsToBoundaries(splitPoints, cell.textBuffer);
if (!rangeBoundaries) {
return null;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
index 8b1ac9e024..2880d46b8f 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts
@@ -3,13 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor';
+import { EditorInput, IEditorInput, GroupIdentifier, ISaveOptions, IMoveResult, IRevertOptions } from 'vs/workbench/common/editor';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { URI } from 'vs/base/common/uri';
-import { isEqual } from 'vs/base/common/resources';
+import { isEqual, basename } from 'vs/base/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
+import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
export class NotebookEditorInput extends EditorInput {
@@ -39,7 +40,10 @@ export class NotebookEditorInput extends EditorInput {
public name: string,
public readonly viewType: string | undefined,
@INotebookService private readonly notebookService: INotebookService,
- @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
+ @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
+ @IFileDialogService private readonly fileDialogService: IFileDialogService,
+ // @IEditorService private readonly editorService: IEditorService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
}
@@ -85,6 +89,48 @@ export class NotebookEditorInput extends EditorInput {
return undefined;
}
+ async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise {
+ if (!this.textModel) {
+ return undefined;
+ }
+
+ const dialogPath = this.textModel.resource;
+ const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
+ if (!target) {
+ return undefined; // save cancelled
+ }
+
+ if (!await this.textModel.saveAs(target)) {
+ return undefined;
+ }
+
+ return this._move(group, target)?.editor;
+ }
+
+ move(group: GroupIdentifier, target: URI): IMoveResult | undefined {
+ if (this.textModel) {
+ const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target);
+
+ if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) {
+ return this._move(group, target);
+ }
+ }
+ return undefined;
+ }
+
+ _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
+ const editorInput = NotebookEditorInput.getOrCreate(this.instantiationService, newResource, basename(newResource), this.viewType);
+ return { editor: editorInput };
+ }
+
+ async revert(group: GroupIdentifier, options?: IRevertOptions): Promise {
+ if (this.textModel) {
+ await this.textModel.revert(options);
+ }
+
+ return;
+ }
+
async resolve(): Promise {
if (!await this.notebookService.canResolve(this.viewType!)) {
throw new Error(`Cannot open notebook of type '${this.viewType}'`);
@@ -96,6 +142,10 @@ export class NotebookEditorInput extends EditorInput {
this._onDidChangeDirty.fire();
}));
+ if (this.textModel.isDirty()) {
+ this._onDidChangeDirty.fire();
+ }
+
return this.textModel;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
index a4200d6e2f..f807ed8626 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts
@@ -11,7 +11,7 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from '
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';
-import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, CellEditType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
import { Iterable } from 'vs/base/common/iterator';
@@ -202,13 +202,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return undefined; // {{SQL CARBON EDIT}} strict-null-check
}
- async resolveNotebook(viewType: string, uri: URI): Promise {
+ async createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise {
const provider = this._notebookProviders.get(viewType);
if (!provider) {
return undefined;
}
- const notebookModel = await provider.controller.resolveNotebook(viewType, uri);
+ const notebookModel = await provider.controller.createNotebook(viewType, uri, true, false);
if (!notebookModel) {
return undefined;
}
@@ -219,6 +219,39 @@ export class NotebookService extends Disposable implements INotebookService, ICu
notebookModel,
(model) => this._onWillDispose(model),
);
+ this._models[modelId] = modelData;
+
+ notebookModel.metadata = metadata;
+ notebookModel.languages = languages;
+
+ notebookModel.applyEdit(notebookModel.versionId, [
+ {
+ editType: CellEditType.Insert,
+ index: 0,
+ cells: cells
+ }
+ ]);
+
+ return modelData.model;
+ }
+
+ async resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise {
+ const provider = this._notebookProviders.get(viewType);
+ if (!provider) {
+ return undefined;
+ }
+
+ let notebookModel: NotebookTextModel | undefined;
+
+ notebookModel = await provider.controller.createNotebook(viewType, uri, false, forceReload);
+
+ // new notebook model created
+ const modelId = MODEL_ID(uri);
+ const modelData = new ModelData(
+ notebookModel!,
+ (model) => this._onWillDispose(model),
+ );
+
this._models[modelId] = modelData;
return modelData.model;
}
@@ -265,7 +298,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu
let provider = this._notebookProviders.get(viewType);
if (provider) {
- provider.controller.destoryNotebookDocument(notebook);
+ provider.controller.removeNotebookDocument(notebook);
}
}
@@ -291,6 +324,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return false;
}
+ async saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise {
+ let provider = this._notebookProviders.get(viewType);
+
+ if (provider) {
+ return provider.controller.saveAs(resource, target, token);
+ }
+
+ return false;
+ }
+
onDidReceiveMessage(viewType: string, uri: URI, message: any): void {
let provider = this._notebookProviders.get(viewType);
diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
index a3a309c441..e4d2abbe18 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
@@ -157,7 +157,7 @@ export class NotebookCellList extends WorkbenchList implements ID
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
const cell = this.element(i);
- if (this._viewModel!.hasCell(cell)) {
+ if (this._viewModel!.hasCell(cell.handle)) {
hideOutputs.push(...cell?.model.outputs);
} else {
deletedOutputs.push(...cell?.model.outputs);
@@ -177,7 +177,7 @@ export class NotebookCellList extends WorkbenchList implements ID
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
const cell = this.element(i);
- if (this._viewModel!.hasCell(cell)) {
+ if (this._viewModel!.hasCell(cell.handle)) {
hideOutputs.push(...cell?.model.outputs);
} else {
deletedOutputs.push(...cell?.model.outputs);
@@ -299,7 +299,7 @@ export class NotebookCellList extends WorkbenchList implements ID
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
const cell = this.element(i);
- if (this._viewModel!.hasCell(cell)) {
+ if (this._viewModel!.hasCell(cell.handle)) {
hideOutputs.push(...cell?.model.outputs);
} else {
deletedOutputs.push(...cell?.model.outputs);
@@ -316,6 +316,18 @@ export class NotebookCellList extends WorkbenchList implements ID
splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void {
// we need to convert start and delete count based on hidden ranges
super.splice(start, deleteCount, elements);
+
+ const selectionsLeft = [];
+ this._viewModel!.selectionHandles.forEach(handle => {
+ if (this._viewModel!.hasCell(handle)) {
+ selectionsLeft.push(handle);
+ }
+ });
+
+ if (!selectionsLeft.length && this._viewModel!.viewCells) {
+ // after splice, the selected cells are deleted
+ this._viewModel!.selectionHandles = [this._viewModel!.viewCells[0].handle];
+ }
}
getViewIndex(cell: ICellViewModel) {
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts
index 04f8faf627..378936ad6d 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts
@@ -250,7 +250,6 @@ export class StatefullMarkdownCell extends Disposable {
bindEditorListeners(model: ITextModel, dimension?: IDimension) {
this.localDisposables.add(model.onDidChangeContent(() => {
// we don't need to update view cell text anymore as the textbuffer is shared
- // this.viewCell.setText(model.getLinesContent());
this.viewCell.clearHTML();
let clientHeight = this.markdownContainer.clientHeight;
this.markdownContainer.innerHTML = '';
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
index 23bc15e9f8..316978712c 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
@@ -205,25 +205,6 @@ export abstract class BaseCellViewModel extends Disposable {
return this.model.getValue();
}
- getLinesContent(): string[] {
- if (this._textModel) {
- return this._textModel.getLinesContent();
- }
-
- return this.model.textBuffer.getLinesContent();
- }
-
- // setLinesContent(value: string[]) {
- // if (this._textModel) {
- // // TODO @rebornix we should avoid creating a new string here
- // return this._textModel.setValue(value.join('\n'));
- // } else {
- // const range = this.model.getFullModelRange();
- // this.model.textBuffer.
- // this.model.source = value;
- // }
- // }
-
private saveViewState(): void {
if (!this._textEditor) {
return;
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts
index 3246891787..6ef1d5b5fd 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts
@@ -268,7 +268,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
});
diffs.reverse().forEach(diff => {
- this._viewCells.splice(diff[0], diff[1], ...diff[2]);
+ const deletedCells = this._viewCells.splice(diff[0], diff[1], ...diff[2]);
+
+ deletedCells.forEach(cell => {
+ this._handleToViewCellMapping.delete(cell.handle);
+ });
+
diff[2].forEach(cell => {
this._handleToViewCellMapping.set(cell.handle, cell);
this._localStore.add(cell);
@@ -456,8 +461,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return index + 1;
}
- hasCell(cell: ICellViewModel) {
- return this._handleToViewCellMapping.has(cell.handle);
+ hasCell(handle: number) {
+ return this._handleToViewCellMapping.has(handle);
}
getVersionId() {
@@ -586,7 +591,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
this._viewCells.splice(deleteIndex, 1);
this._handleToViewCellMapping.delete(deleteCell.handle);
- this._notebook.removeCell(deleteIndex);
+ this._notebook.removeCell(deleteIndex, 1);
this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] });
}
@@ -638,7 +643,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
this._viewCells.splice(index, 1);
this._handleToViewCellMapping.delete(viewCell.handle);
- this._notebook.removeCell(index);
+ this._notebook.removeCell(index, 1);
let endSelections: number[] = [];
if (this.selectionHandles.length) {
diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
index 997f9727a4..07b780fc95 100644
--- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts
@@ -7,7 +7,8 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
-import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { ITextSnapshot } from 'vs/editor/common/model';
function compareRangesUsingEnds(a: [number, number], b: [number, number]): number {
if (a[1] === b[1]) {
@@ -17,6 +18,49 @@ function compareRangesUsingEnds(a: [number, number], b: [number, number]): numbe
return a[1] - b[1];
}
+export class NotebookTextModelSnapshot implements ITextSnapshot {
+ // private readonly _pieces: Ce[] = [];
+ private _index: number = -1;
+
+ constructor(private _model: NotebookTextModel) {
+ // for (let i = 0; i < this._model.cells.length; i++) {
+ // const cell = this._model.cells[i];
+ // this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true));
+ // }
+ }
+
+ read(): string | null {
+
+ if (this._index === -1) {
+ this._index++;
+ return `{ "metadata": ${JSON.stringify(this._model.metadata)}, "languages": ${JSON.stringify(this._model.languages)}, "cells": [`;
+ }
+
+ if (this._index < this._model.cells.length) {
+ const cell = this._model.cells[this._index];
+
+ const data = {
+ source: cell.getValue(),
+ metadata: cell.metadata,
+ cellKind: cell.cellKind,
+ language: cell.language
+ };
+
+ const rawStr = JSON.stringify(data);
+ const isLastCell = this._index === this._model.cells.length - 1;
+
+ this._index++;
+ return isLastCell ? rawStr : (rawStr + ',');
+ } else if (this._index === this._model.cells.length) {
+ this._index++;
+ return `]}`;
+ } else {
+ return null;
+ }
+ }
+
+}
+
export class NotebookTextModel extends Disposable implements INotebookTextModel {
private static _cellhandlePool: number = 0;
@@ -77,6 +121,18 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata);
}
+ initialize(cells: ICellDto2[]) {
+ this.cells = [];
+ this._versionId = 0;
+
+ const mainCells = cells.map(cell => {
+ const cellHandle = NotebookTextModel._cellhandlePool++;
+ const cellUri = CellUri.generate(this.uri, cellHandle);
+ return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata);
+ });
+ this.insertNewCell(0, mainCells);
+ }
+
applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[]): boolean {
if (modelVersionId !== this._versionId) {
return false;
@@ -127,7 +183,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this.insertNewCell(insertEdit.index, mainCells);
break;
case CellEditType.Delete:
- this.removeCell(operations[i].index);
+ this.removeCell(operations[i].index, operations[i].end - operations[i].start);
break;
}
}
@@ -142,6 +198,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return true;
}
+ createSnapshot(preserveBOM?: boolean): ITextSnapshot {
+ return new NotebookTextModelSnapshot(this);
+ }
+
private _increaseVersionId(): void {
this._versionId = this._versionId + 1;
}
@@ -250,17 +310,19 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return;
}
- removeCell(index: number) {
+ removeCell(index: number, count: number) {
this._isUntitled = false;
- let cell = this.cells[index];
- this._cellListeners.get(cell.handle)?.dispose();
- this._cellListeners.delete(cell.handle);
- this.cells.splice(index, 1);
+ for (let i = index; i < index + count; i++) {
+ let cell = this.cells[i];
+ this._cellListeners.get(cell.handle)?.dispose();
+ this._cellListeners.delete(cell.handle);
+ }
+ this.cells.splice(index, count);
this._onDidChangeContent.fire();
this._increaseVersionId();
- this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] });
+ this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] });
}
moveCellToIdx(index: number, newIdx: number) {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index f83a16980a..9959ce0b81 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -273,7 +273,7 @@ export enum CellEditType {
}
export interface ICellDto2 {
- source: string[];
+ source: string | string[];
language: string;
cellKind: CellKind;
outputs: IOutput[];
@@ -300,6 +300,13 @@ export interface INotebookEditData {
renderers: number[];
}
+export interface NotebookDataDto {
+ readonly cells: ICellDto2[];
+ readonly languages: string[];
+ readonly metadata: NotebookDocumentMetadata;
+}
+
+
export namespace CellUri {
export const scheme = 'vscode-notebook';
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
index 8594f80c79..87811fa8d4 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
@@ -15,6 +15,8 @@ import { URI } from 'vs/base/common/uri';
import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { basename } from 'vs/base/common/resources';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
+import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
+import { DefaultEndOfLine, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
export interface INotebookEditorModelManager {
models: NotebookEditorModel[];
@@ -24,6 +26,13 @@ export interface INotebookEditorModelManager {
get(resource: URI): NotebookEditorModel | undefined;
}
+export interface INotebookRevertOptions {
+ /**
+ * Go to disk bypassing any cache of the model if any.
+ */
+ forceReadFromDisk?: boolean;
+}
+
export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel {
private _dirty = false;
@@ -47,7 +56,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
public readonly resource: URI,
public readonly viewType: string,
@INotebookService private readonly notebookService: INotebookService,
- @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
+ @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
+ @IBackupFileService private readonly backupFileService: IBackupFileService
) {
super();
this._register(this.workingCopyService.registerWorkingCopy(this));
@@ -56,28 +66,91 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
capabilities = 0;
async backup(): Promise {
- return {};
+ return { content: this._notebook.createSnapshot(true) };
}
async revert(options?: IRevertOptions | undefined): Promise {
+ if (options?.soft) {
+ await this.backupFileService.discardBackup(this.resource);
+ return;
+ }
+
+ await this.load({ forceReadFromDisk: true });
+ this._dirty = false;
+ this._onDidChangeDirty.fire();
return;
}
- async load(): Promise {
- const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource);
+ async load(options?: INotebookRevertOptions): Promise {
+ if (options?.forceReadFromDisk) {
+ return this.loadFromProvider(true);
+ }
+ if (this.isResolved()) {
+ return this;
+ }
+
+ const backup = await this.backupFileService.resolve(this.resource);
+
+ if (this.isResolved()) {
+ return this; // Make sure meanwhile someone else did not succeed in loading
+ }
+
+ if (backup) {
+ try {
+ return await this.loadFromBackup(backup.value.create(DefaultEndOfLine.LF));
+ } catch (error) {
+ // this.logService.error('[text file model] load() from backup', error); // ignore error and continue to load as file below
+ }
+ }
+
+ return this.loadFromProvider(false);
+ }
+
+ private async loadFromBackup(content: ITextBuffer): Promise {
+ const fullRange = content.getRangeAt(0, content.getLength());
+ const data = JSON.parse(content.getValueInRange(fullRange, EndOfLinePreference.LF));
+
+ const notebook = await this.notebookService.createNotebookFromBackup(this.viewType!, this.resource, data.metadata, data.languages, data.cells);
this._notebook = notebook!;
this._name = basename(this._notebook!.uri);
this._register(this._notebook.onDidChangeContent(() => {
- this._dirty = true;
- this._onDidChangeDirty.fire();
+ this.setDirty(true);
+ this._onDidChangeContent.fire();
+ }));
+
+ await this.backupFileService.discardBackup(this.resource);
+ this.setDirty(true);
+
+ return this;
+ }
+
+ private async loadFromProvider(forceReloadFromDisk: boolean) {
+ const notebook = await this.notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk);
+ this._notebook = notebook!;
+
+ this._name = basename(this._notebook!.uri);
+
+ this._register(this._notebook.onDidChangeContent(() => {
+ this.setDirty(true);
this._onDidChangeContent.fire();
}));
return this;
}
+ isResolved(): boolean {
+ return !!this._notebook;
+ }
+
+ setDirty(newState: boolean) {
+ if (this._dirty !== newState) {
+ this._dirty = newState;
+ this._onDidChangeDirty.fire();
+ }
+ }
+
isDirty() {
return this._dirty;
}
@@ -89,6 +162,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
this._onDidChangeDirty.fire();
return true;
}
+
+ async saveAs(targetResource: URI): Promise {
+ const tokenSource = new CancellationTokenSource();
+ await this.notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token);
+ this._dirty = false;
+ this._onDidChangeDirty.fire();
+ return true;
+ }
}
export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts
index 2a3b8d5c0a..5ead08c981 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Event } from 'vs/base/common/event';
-import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo, NotebookDocumentMetadata, ICellDto2 } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
@@ -17,12 +17,13 @@ import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/commo
export const INotebookService = createDecorator('notebookService');
export interface IMainNotebookController {
- resolveNotebook(viewType: string, uri: URI): Promise;
+ createNotebook(viewType: string, uri: URI, forBackup: boolean, forceReload: boolean): Promise;
executeNotebook(viewType: string, uri: URI, token: CancellationToken): Promise;
onDidReceiveMessage(uri: URI, message: any): void;
executeNotebookCell(uri: URI, handle: number, token: CancellationToken): Promise;
- destoryNotebookDocument(notebook: INotebookTextModel): Promise;
+ removeNotebookDocument(notebook: INotebookTextModel): Promise;
save(uri: URI, token: CancellationToken): Promise;
+ saveAs(uri: URI, target: URI, token: CancellationToken): Promise;
}
export interface INotebookService {
@@ -35,7 +36,8 @@ export interface INotebookService {
registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
unregisterNotebookRenderer(handle: number): void;
getRendererInfo(handle: number): INotebookRendererInfo | undefined;
- resolveNotebook(viewType: string, uri: URI): Promise;
+ resolveNotebook(viewType: string, uri: URI, forceReload: boolean): Promise;
+ createNotebookFromBackup(viewType: string, uri: URI, metadata: NotebookDocumentMetadata, languages: string[], cells: ICellDto2[]): Promise;
executeNotebook(viewType: string, uri: URI): Promise;
executeNotebookCell(viewType: string, uri: URI, handle: number, token: CancellationToken): Promise;
@@ -45,6 +47,7 @@ export interface INotebookService {
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void;
updateActiveNotebookDocument(viewType: string, resource: URI): void;
save(viewType: string, resource: URI, token: CancellationToken): Promise;
+ saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise;
onDidReceiveMessage(viewType: string, uri: URI, message: any): void;
setToCopy(items: NotebookCellTextModel[]): void;
getToCopy(): NotebookCellTextModel[] | undefined;
diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
index 98ad007a26..9e0ba4e995 100644
--- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
+++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
@@ -47,6 +47,7 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { Emitter, Event } from 'vs/base/common/event';
import { MenuRegistry, MenuId, isIMenuItem } from 'vs/platform/actions/common/actions';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
+import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
const $ = DOM.$;
@@ -400,7 +401,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditorP
}
private createRecordingBadge(container: HTMLElement): HTMLElement {
- const recordingBadge = DOM.append(container, DOM.$('.recording-badge.disabled'));
+ const recordingBadge = DOM.append(container, DOM.$('.recording-badge.monaco-count-badge.long.disabled'));
recordingBadge.textContent = localize('recording', "Recording Keys");
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
@@ -891,7 +892,7 @@ class ActionsColumn extends Column {
private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);
return {
- class: 'codicon-edit',
+ class: preferencesEditIcon.classNames,
enabled: true,
id: 'editKeybinding',
tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"),
diff --git a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css
index 0eb6f25665..705e5af46c 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css
@@ -27,8 +27,6 @@
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge {
margin-right: 8px;
- padding: 0px 8px;
- border-radius: 2px;
}
.keybindings-editor > .keybindings-header.small > .search-container > .keybindings-search-actions-container > .recording-badge,
diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
index 5a80f14c1d..77afaeb9ae 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
@@ -40,12 +40,9 @@
}
.settings-editor > .settings-header > .search-container > .settings-count-widget {
- margin: 6px 0px;
- padding: 0px 8px;
- border-radius: 2px;
position: absolute;
right: 35px;
- top: 0;
+ top: 6px;
}
.settings-editor > .settings-header > .search-container > .settings-clear-widget {
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts
index db54f30caa..10d73a8f20 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts
@@ -26,7 +26,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
-import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
+import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget, preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers';
@@ -748,7 +748,7 @@ class EditSettingRenderer extends Disposable {
const decorations = this.editor.getLineDecorations(line);
if (decorations) {
for (const { options } of decorations) {
- if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME) === -1) {
+ if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(preferencesEditIcon.classNames) === -1) {
return false;
}
}
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
index fd93f800a8..5f9828e972 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
@@ -35,6 +35,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { isEqual } from 'vs/base/common/resources';
+import { registerIcon, Codicon } from 'vs/base/common/codicons';
export class SettingsHeaderWidget extends Widget implements IViewZone {
@@ -736,9 +737,9 @@ export class SearchWidget extends Widget {
}
}
-export class EditPreferenceWidget extends Disposable {
+export const preferencesEditIcon = registerIcon('preferences-edit', Codicon.edit, localize('preferencesEditIcon', 'Icon for the edit action in preferences.'));
- static readonly GLYPH_MARGIN_CLASS_NAME = 'codicon codicon-edit';
+export class EditPreferenceWidget extends Disposable {
private _line: number = -1;
private _preferences: T[] = [];
@@ -775,7 +776,7 @@ export class EditPreferenceWidget extends Disposable {
this._line = line;
newDecoration.push({
options: {
- glyphMarginClassName: EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME,
+ glyphMarginClassName: preferencesEditIcon.classNames,
glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage),
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
},
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
index c5f41febe5..3104faf821 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
@@ -450,7 +450,7 @@ export class SettingsEditor2 extends BaseEditor {
inputBorder: settingsTextInputBorder
}));
- this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget'));
+ this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget.monaco-count-badge.long'));
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => {
const background = colors.badgeBackground ? colors.badgeBackground.toString() : '';
const border = colors.contrastBorder ? colors.contrastBorder.toString() : '';
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts
index e675e2599c..2836308c7b 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts
@@ -21,6 +21,7 @@ import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/comm
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { disposableTimeout } from 'vs/base/common/async';
import { isUndefinedOrNull } from 'vs/base/common/types';
+import { preferencesEditIcon } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
const $ = DOM.$;
export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "The foreground color for a section header or active title."));
@@ -367,7 +368,7 @@ export class ListSettingWidget extends Disposable {
private createEditAction(idx: number): IAction {
return {
- class: 'codicon-edit',
+ class: preferencesEditIcon.classNames,
enabled: true,
id: 'workbench.action.editListItem',
tooltip: this.getLocalizedStrings().editActionTooltip,
diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css
index 4d75fa8379..834c1743b2 100644
--- a/src/vs/workbench/contrib/search/browser/media/searchview.css
+++ b/src/vs/workbench/contrib/search/browser/media/searchview.css
@@ -152,9 +152,7 @@
}
.search-view .message {
- padding-left: 22px;
- padding-right: 22px;
- padding-top: 0px;
+ padding: 0 22px 8px;
}
.search-view .message p:first-child {
diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts
index f7468c4a32..9c22e28728 100644
--- a/src/vs/workbench/contrib/search/browser/search.contribution.ts
+++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts
@@ -826,6 +826,11 @@ configurationRegistry.registerConfiguration({
],
markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double clicking a result in a search editor.")
},
+ 'search.searchEditor.reusePriorSearchConfiguration': {
+ type: 'boolean',
+ default: false,
+ markdownDescription: nls.localize('search.searchEditor.reusePriorSearchConfiguration', "When enabled, new Search Editors will reuse the includes, excludes, and flags of the previously opened Search Editor")
+ },
'search.sortOrder': {
'type': 'string',
'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending],
diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts
index 42ab75bfbd..e5d8038e6d 100644
--- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts
+++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts
@@ -378,7 +378,7 @@ export class SearchDND implements ITreeDragAndDrop {
if (resources.length) {
// Apply some datatransfer types to allow for dragging the element outside of the application
- this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
+ this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, undefined, originalEvent);
}
}
diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts
index 35874a5a51..5c784d0957 100644
--- a/src/vs/workbench/contrib/search/browser/searchView.ts
+++ b/src/vs/workbench/contrib/search/browser/searchView.ts
@@ -1022,6 +1022,7 @@ export class SearchView extends ViewPane {
}
this.viewModel.cancelSearch();
this.updateActions();
+ this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search");
aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared"));
}
@@ -1557,9 +1558,12 @@ export class SearchView extends ViewPane {
this.hasSearchResultsKey.set(fileCount > 0);
const msgWasHidden = this.messagesElement.style.display === 'none';
+
+ const messageEl = this.clearMessage();
+ let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
+ this.tree.ariaLabel = resultMsg + nls.localize('forTerm', " - Search: {0}", this.searchResult.query?.contentPattern.pattern ?? '');
+
if (fileCount > 0) {
- const messageEl = this.clearMessage();
- let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount);
if (disregardExcludesAndIgnores) {
resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled");
}
diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts
index f8fae6dba0..de8c03422e 100644
--- a/src/vs/workbench/contrib/search/common/search.ts
+++ b/src/vs/workbench/contrib/search/common/search.ts
@@ -103,7 +103,7 @@ export function getOutOfWorkspaceEditorResources(accessor: ServicesAccessor): UR
}
// Supports patterns of <#|:|(><#|:|,>
-const LINE_COLON_PATTERN = /\s?[#:\(](\d*)([#:,](\d*))?\)?\s*$/;
+const LINE_COLON_PATTERN = /\s?[#:\(](?:line )?(\d*)(?:[#:,](\d*))?\)?\s*$/;
export interface IFilterAndRange {
filter: string;
@@ -119,8 +119,9 @@ export function extractRangeFromFilter(filter: string, unless?: string[]): IFilt
// Find Line/Column number from search value using RegExp
const patternMatch = LINE_COLON_PATTERN.exec(filter);
- if (patternMatch && patternMatch.length > 1) {
- const startLineNumber = parseInt(patternMatch[1], 10);
+
+ if (patternMatch) {
+ const startLineNumber = parseInt(patternMatch[1] ?? '', 10);
// Line Number
if (isNumber(startLineNumber)) {
@@ -132,16 +133,14 @@ export function extractRangeFromFilter(filter: string, unless?: string[]): IFilt
};
// Column Number
- if (patternMatch.length > 3) {
- const startColumn = parseInt(patternMatch[3], 10);
- if (isNumber(startColumn)) {
- range = {
- startLineNumber: range.startLineNumber,
- startColumn: startColumn,
- endLineNumber: range.endLineNumber,
- endColumn: startColumn
- };
- }
+ const startColumn = parseInt(patternMatch[2] ?? '', 10);
+ if (isNumber(startColumn)) {
+ range = {
+ startLineNumber: range.startLineNumber,
+ startColumn: startColumn,
+ endLineNumber: range.endLineNumber,
+ endColumn: startColumn
+ };
}
}
diff --git a/src/vs/workbench/contrib/search/test/common/extractRange.test.ts b/src/vs/workbench/contrib/search/test/common/extractRange.test.ts
index d97f9fccf1..4bc48e4d8c 100644
--- a/src/vs/workbench/contrib/search/test/common/extractRange.test.ts
+++ b/src/vs/workbench/contrib/search/test/common/extractRange.test.ts
@@ -13,7 +13,7 @@ suite('extractRangeFromFilter', () => {
assert.ok(!extractRangeFromFilter('/some/path'));
assert.ok(!extractRangeFromFilter('/some/path/file.txt'));
- for (const lineSep of [':', '#', '(']) {
+ for (const lineSep of [':', '#', '(', ':line ']) {
for (const colSep of [':', '#', ',']) {
const base = '/some/path/file.txt';
diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts
index 419a2de8d1..17fbce33bd 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts
@@ -5,27 +5,12 @@
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
-export const OpenInEditorCommandId = 'search.action.openInEditor';
-export const OpenNewEditorCommandId = 'search.action.openNewEditor';
-export const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
-export const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
-
-export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
-export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
-export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
-export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
-export const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines';
-export const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines';
-
-export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
-export const CleanSearchEditorStateCommandId = 'cleanSearchEditorState';
-export const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
-
export const InSearchEditor = new RawContextKey('inSearchEditor', false);
export const SearchEditorScheme = 'search-editor';
-export const SearchEditorBodyScheme = 'search-editor-body';
export const SearchEditorFindMatchClass = 'seaarchEditorFindMatch';
export const SearchEditorID = 'workbench.editor.searchEditor';
+
+export const OpenNewEditorCommandId = 'search.action.openNewEditor';
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
index c03135b085..cd12a4369f 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
@@ -8,9 +8,10 @@ import * as objects from 'vs/base/common/objects';
import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { Range } from 'vs/editor/common/core/range';
import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
import { localize } from 'vs/nls';
-import { MenuId, SyncActionDescriptor, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
+import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@@ -20,20 +21,36 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
-import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
-import { Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry, ActiveEditorContext } from 'vs/workbench/common/editor';
+import { ActiveEditorContext, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
+import { IViewsService } from 'vs/workbench/common/views';
+import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions';
+import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
-import { modifySearchEditorContextLinesCommand, OpenSearchEditorAction, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand, createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
-import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
-import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
+import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
-import { Range } from 'vs/editor/common/core/range';
-import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
-import { IViewsService } from 'vs/workbench/common/views';
-import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+
+
+const OpenInEditorCommandId = 'search.action.openInEditor';
+const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
+const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
+
+const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
+const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
+const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
+const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
+const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines';
+const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines';
+
+const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
+const CleanSearchEditorStateCommandId = 'cleanSearchEditorState';
+const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';
+
+
//#region Editor Descriptior
Registry.as(EditorExtensions.Editors).registerEditor(
@@ -128,28 +145,28 @@ Registry.as(EditorInputExtensions.EditorInputFactor
//#region Commands
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
- id: SearchEditorConstants.ToggleSearchEditorCaseSensitiveCommandId,
+ id: ToggleSearchEditorCaseSensitiveCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
handler: toggleSearchEditorCaseSensitiveCommand
}, ToggleCaseSensitiveKeybinding));
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
- id: SearchEditorConstants.ToggleSearchEditorWholeWordCommandId,
+ id: ToggleSearchEditorWholeWordCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
handler: toggleSearchEditorWholeWordCommand
}, ToggleWholeWordKeybinding));
KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
- id: SearchEditorConstants.ToggleSearchEditorRegexCommandId,
+ id: ToggleSearchEditorRegexCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
handler: toggleSearchEditorRegexCommand
}, ToggleRegexKeybinding));
KeybindingsRegistry.registerCommandAndKeybindingRule({
- id: SearchEditorConstants.ToggleSearchEditorContextLinesCommandId,
+ id: ToggleSearchEditorContextLinesCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
handler: toggleSearchEditorContextLinesCommand,
@@ -158,7 +175,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
- id: SearchEditorConstants.IncreaseSearchEditorContextLinesCommandId,
+ id: IncreaseSearchEditorContextLinesCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true),
@@ -166,7 +183,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
- id: SearchEditorConstants.DecreaseSearchEditorContextLinesCommandId,
+ id: DecreaseSearchEditorContextLinesCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false),
@@ -174,7 +191,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
- id: SearchEditorConstants.SelectAllSearchEditorMatchesCommandId,
+ id: SelectAllSearchEditorMatchesCommandId,
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
@@ -182,7 +199,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
CommandsRegistry.registerCommand(
- SearchEditorConstants.CleanSearchEditorStateCommandId,
+ CleanSearchEditorStateCommandId,
(accessor: ServicesAccessor) => {
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
if (activeEditorPane instanceof SearchEditor) {
@@ -192,18 +209,40 @@ CommandsRegistry.registerCommand(
//#endregion
//#region Actions
-const registry = Registry.as(ActionExtensions.WorkbenchActions);
const category = localize('search', "Search Editor");
-// TODO: Not an action2 becuase used in view pane container action bar, which uses actions
-registry.registerWorkbenchAction(
- SyncActionDescriptor.from(OpenSearchEditorAction),
- 'Search Editor: Open New Search Editor', category);
-
registerAction2(class extends Action2 {
constructor() {
super({
- id: SearchEditorConstants.OpenInEditorCommandId,
+ id: SearchEditorConstants.OpenNewEditorCommandId,
+ title: localize('search.openNewSearchEditor', "Open new Search Editor"),
+ category,
+ f1: true,
+ });
+ }
+ async run(accessor: ServicesAccessor, args: Partial) {
+ await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args);
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: OpenNewEditorToSideCommandId,
+ title: localize('search.openNewEditorToSide', "Open new Search Editor to the Side"),
+ category,
+ f1: true,
+ });
+ }
+ async run(accessor: ServicesAccessor, args: Partial) {
+ await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args, true);
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: OpenInEditorCommandId,
title: localize('search.openResultsInEditor', "Open Results in Editor"),
category,
f1: true,
@@ -227,22 +266,7 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
- id: SearchEditorConstants.OpenNewEditorToSideCommandId,
- title: localize('search.openNewEditorToSide', "Open New Search Editor to Side"),
- category,
- f1: true,
- });
- }
- async run(accessor: ServicesAccessor) {
- const instantiationService = accessor.get(IInstantiationService);
- await instantiationService.invokeFunction(openNewSearchEditor, true);
- }
-});
-
-registerAction2(class extends Action2 {
- constructor() {
- super({
- id: SearchEditorConstants.RerunSearchEditorSearchCommandId,
+ id: RerunSearchEditorSearchCommandId,
title: localize('search.rerunSearchInEditor', "Search Again"),
category,
keybinding: {
@@ -274,7 +298,7 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
- id: SearchEditorConstants.FocusQueryEditorWidgetCommandId,
+ id: FocusQueryEditorWidgetCommandId,
title: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"),
category,
menu: {
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts
index 71ae96c9b5..b0679dc802 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts
@@ -13,13 +13,18 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati
import { ILabelService } from 'vs/platform/label/common/label';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel';
-import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants';
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
-import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
+import { getOrMakeSearchEditorInput, SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search';
import { searchNewEditorIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
+import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
+import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
+import { IHistoryService } from 'vs/workbench/services/history/common/history';
+import { Schemas } from 'vs/base/common/network';
+import { withNullAsUndefined } from 'vs/base/common/types';
+import { OpenNewEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants';
export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => {
const editorService = accessor.get(IEditorService);
@@ -71,7 +76,7 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor)
export class OpenSearchEditorAction extends Action {
- static readonly ID: string = Constants.OpenNewEditorCommandId;
+ static readonly ID: string = OpenNewEditorCommandId;
static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor");
constructor(id: string, label: string,
@@ -94,12 +99,23 @@ export class OpenSearchEditorAction extends Action {
}
export const openNewSearchEditor =
- async (accessor: ServicesAccessor, toSide = false) => {
+ async (accessor: ServicesAccessor, args: Partial = {}, toSide = false) => {
const editorService = accessor.get(IEditorService);
const telemetryService = accessor.get(ITelemetryService);
const instantiationService = accessor.get(IInstantiationService);
const configurationService = accessor.get(IConfigurationService);
+ const configurationResolverService = accessor.get(IConfigurationResolverService);
+ const workspaceContextService = accessor.get(IWorkspaceContextService);
+ const historyService = accessor.get(IHistoryService);
+ const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file);
+ const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
+
+ const resolvedArgs: Record = {};
+ Object.entries(args).forEach(([name, value]) => {
+ resolvedArgs[name as any] = (typeof value === 'string') ? configurationResolverService.resolve(lastActiveWorkspaceRoot, value) : value;
+ });
+
const activeEditorControl = editorService.activeTextEditorControl;
let activeModel: ICodeEditor | undefined;
let selected = '';
@@ -124,7 +140,7 @@ export const openNewSearchEditor =
telemetryService.publicLog2('searchEditor/openNewSearchEditor');
- const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected }, text: '' });
+ const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected, ...resolvedArgs }, text: '' });
const editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor;
if (selected && configurationService.getValue('search').searchOnType) {
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts
index f7b4b7936f..44297c4b90 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts
@@ -36,7 +36,7 @@ import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapab
export type SearchConfiguration = {
query: string,
includes: string,
- excludes: string
+ excludes: string,
contextLines: number,
wholeWord: boolean,
caseSensitive: boolean,
@@ -304,7 +304,7 @@ export const getOrMakeSearchEditorInput = (
const storageService = accessor.get(IStorageService);
const configurationService = accessor.get(IConfigurationService);
- const reuseOldSettings = configurationService.getValue('search').searchEditor?.experimental?.reusePriorSearchConfiguration;
+ const reuseOldSettings = configurationService.getValue