mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfbadd1eae | ||
|
|
e27503dc5a | ||
|
|
8f3eff5066 | ||
|
|
baff602394 | ||
|
|
aa1cb31c2d | ||
|
|
f109afb242 | ||
|
|
4cba7ab81a | ||
|
|
c369613782 | ||
|
|
a9107a037b | ||
|
|
4e8b9ec9c1 | ||
|
|
810cd8c33b | ||
|
|
220d2379bc | ||
|
|
047c221834 | ||
|
|
f812e4fa5d | ||
|
|
e0af62ebbe | ||
|
|
2b39be8651 | ||
|
|
6ecaf97000 | ||
|
|
ee918e7b32 | ||
|
|
f50c111f06 |
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@@ -92,6 +92,7 @@
|
||||
"env": {
|
||||
"VSCODE_EXTHOST_WILL_SEND_SOCKET": null
|
||||
},
|
||||
"cleanUp": "wholeBrowser",
|
||||
"breakOnLoad": false,
|
||||
"urlFilter": "*workbench.html*",
|
||||
"runtimeArgs": [
|
||||
|
||||
10
.vscode/notebooks/inbox.github-issues
vendored
10
.vscode/notebooks/inbox.github-issues
vendored
@@ -2,12 +2,14 @@
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "##### `Config`: defines the inbox query"
|
||||
"value": "##### `Config`: defines the inbox query",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item "
|
||||
"value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item ",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
@@ -18,7 +20,7 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$inbox -label:\"needs more info\"",
|
||||
"value": "$inbox -label:\"needs more info\" -label:emmet",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
@@ -31,6 +33,6 @@
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$inbox",
|
||||
"editable": true
|
||||
"editable": false
|
||||
}
|
||||
]
|
||||
2
.vscode/notebooks/my-work.github-issues
vendored
2
.vscode/notebooks/my-work.github-issues
vendored
@@ -20,7 +20,7 @@
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$repos $milestone assignee:@me is:open\n",
|
||||
"value": "$repos $milestone assignee:@me is:open",
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
|
||||
55
.vscode/notebooks/verification.github-issues
vendored
Normal file
55
.vscode/notebooks/verification.github-issues
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
[
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "### Bug Verification Queries\n\nBefore shipping we want to verify _all_ bugs. That means when a bug is fixed we check that the fix actually works. It's always best to start with bugs that you have filed and the proceed with bugs that have been filed from users outside the development team. ",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "#### Config: update list of `repos` and the `milestone`",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"June 2020\"",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "### Bugs You Filed",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate author:@me",
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "### Bugs From Outside",
|
||||
"editable": true
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand",
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"kind": 1,
|
||||
"language": "markdown",
|
||||
"value": "### All"
|
||||
},
|
||||
{
|
||||
"kind": 2,
|
||||
"language": "github-issues",
|
||||
"value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate",
|
||||
"editable": false
|
||||
}
|
||||
]
|
||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@@ -26,8 +26,8 @@
|
||||
"message": 3
|
||||
},
|
||||
"background": {
|
||||
"beginsPattern": "Starting compilation",
|
||||
"endsPattern": "Finished compilation"
|
||||
"beginsPattern": "\\[watch-client\\].*Starting compilation",
|
||||
"endsPattern": "\\[watch-client\\].*Finished compilation"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -108,10 +108,3 @@ jobs:
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
schedules:
|
||||
- cron: "0 5 * * Mon-Fri"
|
||||
displayName: Mon-Fri at 5AM UTC
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
|
||||
@@ -75,6 +75,7 @@ const vscodeResources = [
|
||||
'out-build/paths.js',
|
||||
'out-build/vs/**/*.{svg,png,html}',
|
||||
'!out-build/vs/code/browser/**/*.html',
|
||||
'!out-build/vs/editor/standalone/**/*.svg',
|
||||
'out-build/vs/base/common/performance.js',
|
||||
'out-build/vs/base/node/languagePacks.js',
|
||||
'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}',
|
||||
|
||||
@@ -73,3 +73,5 @@ yarnInstall('test/automation'); // node modules required for smoketest
|
||||
yarnInstall('test/smoke'); // node modules required for smoketest
|
||||
yarnInstall('test/integration/browser'); // node modules required for integration
|
||||
yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron
|
||||
|
||||
cp.execSync('git config pull.rebase true');
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"gulp-bom": "^1.0.0",
|
||||
"gulp-sourcemaps": "^1.11.0",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"iconv-lite-umd": "0.6.5",
|
||||
"iconv-lite-umd": "0.6.7",
|
||||
"mime": "^1.3.4",
|
||||
"minimatch": "3.0.4",
|
||||
"minimist": "^1.2.3",
|
||||
|
||||
@@ -1817,10 +1817,10 @@ https-proxy-agent@^4.0.0:
|
||||
agent-base "5"
|
||||
debug "4"
|
||||
|
||||
iconv-lite-umd@0.6.5:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
|
||||
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
|
||||
iconv-lite-umd@0.6.7:
|
||||
version "0.6.7"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c"
|
||||
integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ==
|
||||
|
||||
iconv-lite@^0.4.4:
|
||||
version "0.4.24"
|
||||
|
||||
@@ -533,7 +533,7 @@
|
||||
"git": {
|
||||
"name": "ripgrep",
|
||||
"repositoryUrl": "https://github.com/BurntSushi/ripgrep",
|
||||
"commitHash": "8a7db1a918e969b85cd933d8ed9fa5285b281ba4"
|
||||
"commitHash": "973de50c9ef451da2cfcdfa86f2b2711d8d6ff48"
|
||||
}
|
||||
},
|
||||
"isOnlyProductionDependency": true,
|
||||
|
||||
@@ -1878,7 +1878,7 @@
|
||||
"dependencies": {
|
||||
"byline": "^5.0.0",
|
||||
"file-type": "^7.2.0",
|
||||
"iconv-lite-umd": "0.6.5",
|
||||
"iconv-lite-umd": "0.6.7",
|
||||
"jschardet": "2.1.1",
|
||||
"vscode-extension-telemetry": "0.1.1",
|
||||
"vscode-nls": "^4.0.0",
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Model } from '../model';
|
||||
import { GitExtension, Repository, API } from './git';
|
||||
import { ApiRepository, ApiImpl } from './api1';
|
||||
import { Event, EventEmitter } from 'vscode';
|
||||
import { latchEvent } from '../util';
|
||||
|
||||
export function deprecated(_target: any, key: string, descriptor: any): void {
|
||||
if (typeof descriptor.value !== 'function') {
|
||||
@@ -26,14 +25,20 @@ export class GitExtensionImpl implements GitExtension {
|
||||
enabled: boolean = false;
|
||||
|
||||
private _onDidChangeEnablement = new EventEmitter<boolean>();
|
||||
readonly onDidChangeEnablement: Event<boolean> = latchEvent(this._onDidChangeEnablement.event);
|
||||
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
|
||||
|
||||
private _model: Model | undefined = undefined;
|
||||
|
||||
set model(model: Model | undefined) {
|
||||
this._model = model;
|
||||
|
||||
this.enabled = !!model;
|
||||
const enabled = !!model;
|
||||
|
||||
if (this.enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = enabled;
|
||||
this._onDidChangeEnablement.fire(this.enabled);
|
||||
}
|
||||
|
||||
@@ -73,4 +78,4 @@ export class GitExtensionImpl implements GitExtension {
|
||||
|
||||
return new ApiImpl(this._model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +176,7 @@ export async function activate(context: ExtensionContext): Promise<GitExtension>
|
||||
return result;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
async function checkGitVersion(info: IGit): Promise<void> {
|
||||
async function checkGitv1(info: IGit): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
|
||||
|
||||
@@ -204,3 +203,27 @@ async function checkGitVersion(info: IGit): Promise<void> {
|
||||
await config.update('ignoreLegacyWarning', true, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkGitWindows(info: IGit): Promise<void> {
|
||||
if (!/^2\.(25|26)\./.test(info.version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = localize('updateGit', "Update Git");
|
||||
const choice = await window.showWarningMessage(
|
||||
localize('git2526', "There are known issues with the installed Git {0}. Please update to Git >= 2.27 for the git features to work correctly.", info.version),
|
||||
update
|
||||
);
|
||||
|
||||
if (choice === update) {
|
||||
commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
|
||||
}
|
||||
}
|
||||
// @ts-expect-error
|
||||
async function checkGitVersion(info: IGit): Promise<void> {
|
||||
await checkGitv1(info);
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
await checkGitWindows(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,7 +865,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
async getInputTemplate(): Promise<string> {
|
||||
const commitMessage = (await Promise.all([this.repository.getMergeMessage(), this.repository.getSquashMessage()])).find(msg => msg !== undefined);
|
||||
const commitMessage = (await Promise.all([this.repository.getMergeMessage(), this.repository.getSquashMessage()])).find(msg => !!msg);
|
||||
|
||||
if (commitMessage) {
|
||||
return commitMessage;
|
||||
|
||||
@@ -44,18 +44,6 @@ export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Even
|
||||
return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
|
||||
export function latchEvent<T>(event: Event<T>): Event<T> {
|
||||
let firstCall = true;
|
||||
let cache: T;
|
||||
|
||||
return filterEvent(event, value => {
|
||||
let shouldEmit = firstCall || value !== cache;
|
||||
firstCall = false;
|
||||
cache = value;
|
||||
return shouldEmit;
|
||||
});
|
||||
}
|
||||
|
||||
export function anyEvent<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
|
||||
const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i))));
|
||||
|
||||
@@ -425,10 +425,10 @@ https-proxy-agent@^2.2.1:
|
||||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
iconv-lite-umd@0.6.5:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
|
||||
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
|
||||
iconv-lite-umd@0.6.7:
|
||||
version "0.6.7"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c"
|
||||
integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "githubBrowser.openRepository",
|
||||
"title": "Open GitHub Repository...",
|
||||
"category": "GitHub Browser"
|
||||
},
|
||||
{
|
||||
"command": "githubBrowser.commit",
|
||||
"title": "Commit",
|
||||
@@ -48,6 +53,10 @@
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "githubBrowser.openRepository",
|
||||
"when": "config.githubBrowser.openRepository"
|
||||
},
|
||||
{
|
||||
"command": "githubBrowser.commit",
|
||||
"when": "false"
|
||||
|
||||
@@ -47,31 +47,17 @@ function fromSerialized(operations: StoredOperation): Operation {
|
||||
return { ...operations, uri: Uri.parse(operations.uri) };
|
||||
}
|
||||
|
||||
interface CreatedFileChangeStoreEvent {
|
||||
type: 'created';
|
||||
export interface ChangeStoreEvent {
|
||||
type: 'created' | 'changed' | 'deleted';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
interface ChangedFileChangeStoreEvent {
|
||||
type: 'changed';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
interface DeletedFileChangeStoreEvent {
|
||||
type: 'deleted';
|
||||
rootUri: Uri;
|
||||
uri: Uri;
|
||||
}
|
||||
|
||||
type ChangeStoreEvent = CreatedFileChangeStoreEvent | ChangedFileChangeStoreEvent | DeletedFileChangeStoreEvent;
|
||||
|
||||
function toChangeStoreEvent(operation: Operation | StoredOperation, rootUri: Uri, uri?: Uri): ChangeStoreEvent {
|
||||
return {
|
||||
type: operation.type,
|
||||
rootUri: rootUri,
|
||||
uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri)
|
||||
uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +68,8 @@ export interface IChangeStore {
|
||||
discard(uri: Uri): Promise<void>;
|
||||
discardAll(rootUri: Uri): Promise<void>;
|
||||
|
||||
hasChanges(rootUri: Uri): boolean;
|
||||
|
||||
getChanges(rootUri: Uri): Operation[];
|
||||
getContent(uri: Uri): string | undefined;
|
||||
|
||||
@@ -116,9 +104,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
|
||||
await this.saveWorkingOperations(rootUri, undefined);
|
||||
|
||||
const events: ChangeStoreEvent[] = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
await this.discardWorkingContent(operation.uri);
|
||||
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
|
||||
events.push(toChangeStoreEvent(operation, rootUri));
|
||||
}
|
||||
|
||||
for (const e of events) {
|
||||
this._onDidChange.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +137,7 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
this._onDidChange.fire({
|
||||
type: operation.type === 'created' ? 'deleted' : operation.type === 'deleted' ? 'created' : 'changed',
|
||||
rootUri: rootUri,
|
||||
uri: uri
|
||||
uri: uri,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,9 +146,15 @@ export class ChangeStore implements IChangeStore, IWritableChangeStore {
|
||||
|
||||
await this.saveWorkingOperations(rootUri, undefined);
|
||||
|
||||
const events: ChangeStoreEvent[] = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
await this.discardWorkingContent(operation.uri);
|
||||
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
|
||||
events.push(toChangeStoreEvent(operation, rootUri));
|
||||
}
|
||||
|
||||
for (const e of events) {
|
||||
this._onDidChange.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { Event, EventEmitter, Memento, Uri } from 'vscode';
|
||||
import { Event, EventEmitter, Memento, Uri, workspace } from 'vscode';
|
||||
|
||||
export const contextKeyPrefix = 'github.context|';
|
||||
export interface WorkspaceFolderContext<T> {
|
||||
context: T;
|
||||
name: string;
|
||||
folderUri: Uri;
|
||||
}
|
||||
|
||||
export class ContextStore<T> {
|
||||
private _onDidChange = new EventEmitter<Uri>();
|
||||
@@ -14,23 +18,36 @@ export class ContextStore<T> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
constructor(private readonly memento: Memento, private readonly scheme: string) { }
|
||||
constructor(
|
||||
private readonly scheme: string,
|
||||
private readonly originalScheme: string,
|
||||
private readonly memento: Memento,
|
||||
) { }
|
||||
|
||||
delete(uri: Uri) {
|
||||
return this.set(uri, undefined);
|
||||
}
|
||||
|
||||
get(uri: Uri): T | undefined {
|
||||
return this.memento.get<T>(`${contextKeyPrefix}${uri.toString()}`);
|
||||
return this.memento.get<T>(`${this.originalScheme}.context|${this.getOriginalResource(uri).toString()}`);
|
||||
}
|
||||
|
||||
getForWorkspace(): WorkspaceFolderContext<T>[] {
|
||||
const folders = workspace.workspaceFolders?.filter(f => f.uri.scheme === this.scheme || f.uri.scheme === this.originalScheme) ?? [];
|
||||
return folders.map(f => ({ context: this.get(f.uri)!, name: f.name, folderUri: f.uri })).filter(c => c.context !== undefined);
|
||||
}
|
||||
|
||||
async set(uri: Uri, context: T | undefined) {
|
||||
if (uri.scheme !== this.scheme) {
|
||||
throw new Error(`Invalid context scheme: ${uri.scheme}`);
|
||||
}
|
||||
|
||||
await this.memento.update(`${contextKeyPrefix}${uri.toString()}`, context);
|
||||
uri = this.getOriginalResource(uri);
|
||||
await this.memento.update(`${this.originalScheme}.context|${uri.toString()}`, context);
|
||||
this._onDidChange.fire(uri);
|
||||
}
|
||||
|
||||
getOriginalResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.originalScheme });
|
||||
}
|
||||
|
||||
getWorkspaceResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.scheme });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,48 +3,50 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, Uri, workspace } from 'vscode';
|
||||
import { commands, ExtensionContext, Uri, window, workspace } from 'vscode';
|
||||
import { ChangeStore } from './changeStore';
|
||||
import { ContextStore } from './contextStore';
|
||||
import { VirtualFS } from './fs';
|
||||
import { GitHubApiContext, GitHubApi } from './github/api';
|
||||
import { GitHubFS } from './github/fs';
|
||||
import { VirtualSCM } from './scm';
|
||||
import { StatusBar } from './statusbar';
|
||||
|
||||
// const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i;
|
||||
const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
const contextStore = new ContextStore<GitHubApiContext>(context.workspaceState, GitHubFS.scheme);
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const contextStore = new ContextStore<GitHubApiContext>('codespace', GitHubFS.scheme, context.workspaceState);
|
||||
const changeStore = new ChangeStore(context.workspaceState);
|
||||
|
||||
const githubApi = new GitHubApi(contextStore);
|
||||
const gitHubFS = new GitHubFS(githubApi);
|
||||
const virtualFS = new VirtualFS('codespace', GitHubFS.scheme, contextStore, changeStore, gitHubFS);
|
||||
const virtualFS = new VirtualFS('codespace', contextStore, changeStore, gitHubFS);
|
||||
|
||||
context.subscriptions.push(
|
||||
githubApi,
|
||||
gitHubFS,
|
||||
virtualFS,
|
||||
new VirtualSCM(GitHubFS.scheme, githubApi, changeStore)
|
||||
new VirtualSCM(GitHubFS.scheme, githubApi, changeStore),
|
||||
new StatusBar(contextStore, changeStore),
|
||||
);
|
||||
|
||||
// commands.registerCommand('githubBrowser.openRepository', async () => {
|
||||
// const value = await window.showInputBox({
|
||||
// placeHolder: 'e.g. https://github.com/microsoft/vscode',
|
||||
// prompt: 'Enter a GitHub repository url',
|
||||
// validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url'
|
||||
// });
|
||||
commands.registerCommand('githubBrowser.openRepository', async () => {
|
||||
const value = await window.showInputBox({
|
||||
placeHolder: 'e.g. https://github.com/microsoft/vscode',
|
||||
prompt: 'Enter a GitHub repository url',
|
||||
validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url'
|
||||
});
|
||||
|
||||
// if (value) {
|
||||
// const match = repositoryRegex.exec(value);
|
||||
// if (match) {
|
||||
// const [, owner, repo] = match;
|
||||
if (value) {
|
||||
const match = repositoryRegex.exec(value);
|
||||
if (match) {
|
||||
const [, owner, repo] = match;
|
||||
|
||||
// const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`);
|
||||
// openWorkspace(uri, repo, 'currentWindow');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`);
|
||||
openWorkspace(uri, repo, 'currentWindow');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getRelativePath(rootUri: Uri, uri: Uri) {
|
||||
@@ -63,11 +65,16 @@ export function isDescendent(folderPath: string, filePath: string) {
|
||||
return folderPath.length === 0 || filePath.startsWith(folderPath.endsWith('/') ? folderPath : `${folderPath}/`);
|
||||
}
|
||||
|
||||
// function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') {
|
||||
// if (location === 'addToCurrentWorkspace') {
|
||||
// const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0;
|
||||
// return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name });
|
||||
// }
|
||||
const shaRegex = /^[0-9a-f]{40}$/;
|
||||
export function isSha(ref: string) {
|
||||
return shaRegex.test(ref);
|
||||
}
|
||||
|
||||
// return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow');
|
||||
// }
|
||||
function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') {
|
||||
if (location === 'addToCurrentWorkspace') {
|
||||
const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0;
|
||||
return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name });
|
||||
}
|
||||
|
||||
return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow');
|
||||
}
|
||||
|
||||
@@ -43,26 +43,22 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
|
||||
constructor(
|
||||
readonly scheme: string,
|
||||
private readonly originalScheme: string,
|
||||
contextStore: ContextStore<GitHubApiContext>,
|
||||
private readonly contextStore: ContextStore<GitHubApiContext>,
|
||||
private readonly changeStore: IWritableChangeStore,
|
||||
private readonly fs: FileSystemProvider & FileSearchProvider & TextSearchProvider
|
||||
) {
|
||||
// TODO@eamodio listen for workspace folder changes
|
||||
for (const folder of workspace.workspaceFolders ?? []) {
|
||||
const uri = this.getOriginalResource(folder.uri);
|
||||
|
||||
for (const context of contextStore.getForWorkspace()) {
|
||||
// If we have a saved context, but no longer have any changes, reset the context
|
||||
// We only do this on startup/reload to keep things consistent
|
||||
if (contextStore.get(uri) !== undefined && !changeStore.hasChanges(folder.uri)) {
|
||||
contextStore.delete(uri);
|
||||
if (!changeStore.hasChanges(context.folderUri)) {
|
||||
console.log('Clear context', context.folderUri.toString());
|
||||
contextStore.delete(context.folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
this.disposable = Disposable.from(
|
||||
workspace.registerFileSystemProvider(scheme, this, {
|
||||
isCaseSensitive: true,
|
||||
}),
|
||||
workspace.registerFileSystemProvider(scheme, this, { isCaseSensitive: true }),
|
||||
workspace.registerFileSearchProvider(scheme, this),
|
||||
workspace.registerTextSearchProvider(scheme, this),
|
||||
changeStore.onDidChange(e => {
|
||||
@@ -86,11 +82,11 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
}
|
||||
|
||||
private getOriginalResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.originalScheme });
|
||||
return this.contextStore.getOriginalResource(uri);
|
||||
}
|
||||
|
||||
private getVirtualResource(uri: Uri): Uri {
|
||||
return uri.with({ scheme: this.scheme });
|
||||
private getWorkspaceResource(uri: Uri): Uri {
|
||||
return this.contextStore.getWorkspaceResource(uri);
|
||||
}
|
||||
|
||||
//#region FileSystemProvider
|
||||
@@ -211,7 +207,7 @@ export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSe
|
||||
return this.fs.provideTextSearchResults(
|
||||
query,
|
||||
{ ...options, folder: this.getOriginalResource(options.folder) },
|
||||
{ report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getVirtualResource(result.uri) }) },
|
||||
{ report: (result: TextSearchResult) => progress.report({ ...result, uri: this.getWorkspaceResource(result.uri) }) },
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
import { authentication, AuthenticationSession, Disposable, Event, EventEmitter, Range, Uri } from 'vscode';
|
||||
import { graphql } from '@octokit/graphql';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { fromGitHubUri } from './fs';
|
||||
import { ContextStore } from '../contextStore';
|
||||
import { fromGitHubUri } from './fs';
|
||||
import { isSha } from '../extension';
|
||||
import { Iterables } from '../iterables';
|
||||
|
||||
export const shaRegex = /^[0-9a-f]{40}$/;
|
||||
|
||||
export interface GitHubApiContext {
|
||||
sha: string;
|
||||
requestRef: string;
|
||||
|
||||
branch: string;
|
||||
sha: string | undefined;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@@ -110,19 +112,12 @@ export class GitHubApi implements Disposable {
|
||||
}
|
||||
|
||||
async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise<string | undefined> {
|
||||
let { owner, repo, ref } = fromGitHubUri(rootUri);
|
||||
const { owner, repo } = fromGitHubUri(rootUri);
|
||||
|
||||
try {
|
||||
if (ref === undefined || ref === 'HEAD') {
|
||||
ref = await this.defaultBranchQuery(rootUri);
|
||||
if (ref === undefined) {
|
||||
throw new Error('Cannot commit — invalid ref');
|
||||
}
|
||||
}
|
||||
|
||||
const context = await this.getContext(rootUri);
|
||||
if (context.sha === undefined) {
|
||||
throw new Error('Cannot commit — invalid context');
|
||||
throw new Error(`Cannot commit to Uri(${rootUri.toString(true)}); Invalid context sha`);
|
||||
}
|
||||
|
||||
const hasDeletes = operations.some(op => op.type === 'deleted');
|
||||
@@ -204,14 +199,14 @@ export class GitHubApi implements Disposable {
|
||||
parents: [context.sha]
|
||||
});
|
||||
|
||||
this.updateContext(rootUri, { sha: resp.data.sha, timestamp: Date.now() });
|
||||
this.updateContext(rootUri, { ...context, sha: resp.data.sha, timestamp: Date.now() });
|
||||
|
||||
// TODO@eamodio need to send a file change for any open files
|
||||
|
||||
await github.git.updateRef({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
ref: `heads/${ref}`,
|
||||
ref: `heads/${context.branch}`,
|
||||
sha: resp.data.sha
|
||||
});
|
||||
|
||||
@@ -256,7 +251,7 @@ export class GitHubApi implements Disposable {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
recursive: '1',
|
||||
tree_sha: context?.sha ?? ref ?? 'HEAD',
|
||||
tree_sha: context?.sha ?? ref,
|
||||
});
|
||||
return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined);
|
||||
} catch (ex) {
|
||||
@@ -283,7 +278,7 @@ export class GitHubApi implements Disposable {
|
||||
}>(query, {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
path: `${context.sha ?? ref ?? 'HEAD'}:${path}`,
|
||||
path: `${context.sha ?? ref}:${path}`,
|
||||
});
|
||||
return rsp?.repository?.object ?? undefined;
|
||||
} catch (ex) {
|
||||
@@ -295,7 +290,7 @@ export class GitHubApi implements Disposable {
|
||||
const { owner, repo, ref } = fromGitHubUri(uri);
|
||||
|
||||
try {
|
||||
if (ref === undefined || ref === 'HEAD') {
|
||||
if (ref === 'HEAD') {
|
||||
const query = `query latest($owner: String!, $repo: String!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
defaultBranchRef {
|
||||
@@ -322,6 +317,7 @@ export class GitHubApi implements Disposable {
|
||||
oid
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const rsp = await this.gqlQuery<{
|
||||
@@ -345,7 +341,7 @@ export class GitHubApi implements Disposable {
|
||||
const { owner, repo, ref } = fromGitHubUri(uri);
|
||||
|
||||
// If we have a specific ref, don't try to search, because GitHub search only works against the default branch
|
||||
if (ref === undefined) {
|
||||
if (ref !== 'HEAD') {
|
||||
return { matches: [], limitHit: true };
|
||||
}
|
||||
|
||||
@@ -436,29 +432,46 @@ export class GitHubApi implements Disposable {
|
||||
private readonly rootUriToContextMap = new Map<string, GitHubApiContext>();
|
||||
|
||||
private async getContextCore(rootUri: Uri): Promise<GitHubApiContext> {
|
||||
let context = this.rootUriToContextMap.get(rootUri.toString());
|
||||
if (context === undefined) {
|
||||
const { ref } = fromGitHubUri(rootUri);
|
||||
if (ref !== undefined && shaRegex.test(ref)) {
|
||||
context = { sha: ref, timestamp: Date.now() };
|
||||
} else {
|
||||
context = this.context.get(rootUri);
|
||||
if (context?.sha === undefined) {
|
||||
const sha = await this.latestCommitQuery(rootUri);
|
||||
if (sha !== undefined) {
|
||||
context = { sha: sha, timestamp: Date.now() };
|
||||
} else {
|
||||
context = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
const key = rootUri.toString();
|
||||
let context = this.rootUriToContextMap.get(key);
|
||||
|
||||
if (context !== undefined) {
|
||||
this.updateContext(rootUri, context);
|
||||
}
|
||||
// Check if we have a cached a context
|
||||
if (context?.sha !== undefined) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return context ?? { sha: rootUri.authority, timestamp: Date.now() };
|
||||
// Check if we have a saved context
|
||||
context = this.context.get(rootUri);
|
||||
if (context?.sha !== undefined) {
|
||||
this.rootUriToContextMap.set(key, context);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
const { ref } = fromGitHubUri(rootUri);
|
||||
|
||||
// If the requested ref looks like a sha, then use it
|
||||
if (isSha(ref)) {
|
||||
context = { requestRef: ref, branch: ref, sha: ref, timestamp: Date.now() };
|
||||
} else {
|
||||
let branch;
|
||||
if (ref === 'HEAD') {
|
||||
branch = await this.defaultBranchQuery(rootUri);
|
||||
if (branch === undefined) {
|
||||
throw new Error(`Cannot get context for Uri(${rootUri.toString(true)}); unable to get default branch`);
|
||||
}
|
||||
} else {
|
||||
branch = ref;
|
||||
}
|
||||
|
||||
// Query for the latest sha for the give ref
|
||||
const sha = await this.latestCommitQuery(rootUri);
|
||||
context = { requestRef: ref, branch: branch, sha: sha, timestamp: Date.now() };
|
||||
}
|
||||
|
||||
this.updateContext(rootUri, context);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private updateContext(rootUri: Uri, context: GitHubApiContext) {
|
||||
|
||||
@@ -299,7 +299,7 @@ function typenameToFileType(typename: string | undefined | null) {
|
||||
}
|
||||
}
|
||||
|
||||
type RepoInfo = { owner: string; repo: string; path: string | undefined; ref?: string };
|
||||
type RepoInfo = { owner: string; repo: string; path: string | undefined; ref: string };
|
||||
export function fromGitHubUri(uri: Uri): RepoInfo {
|
||||
const [, owner, repo, ...rest] = uri.path.split('/');
|
||||
|
||||
@@ -311,7 +311,7 @@ export function fromGitHubUri(uri: Uri): RepoInfo {
|
||||
ref = 'HEAD';
|
||||
}
|
||||
}
|
||||
return { owner: owner, repo: repo, path: rest.join('/'), ref: ref };
|
||||
return { owner: owner, repo: repo, path: rest.join('/'), ref: ref ?? 'HEAD' };
|
||||
}
|
||||
|
||||
function getHashCode(s: string): number {
|
||||
|
||||
@@ -32,17 +32,15 @@ export class VirtualSCM implements Disposable {
|
||||
// TODO@eamodio listen for workspace folder changes
|
||||
for (const folder of workspace.workspaceFolders ?? []) {
|
||||
this.createScmProvider(folder.uri, folder.name);
|
||||
|
||||
for (const operation of changeStore.getChanges(folder.uri)) {
|
||||
this.update(folder.uri, operation.uri);
|
||||
}
|
||||
}
|
||||
|
||||
this.disposable = Disposable.from(
|
||||
changeStore.onDidChange(e => this.update(e.rootUri, e.uri)),
|
||||
);
|
||||
|
||||
for (const { uri } of workspace.workspaceFolders ?? []) {
|
||||
for (const operation of changeStore.getChanges(uri)) {
|
||||
this.update(uri, operation.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -50,7 +48,18 @@ export class VirtualSCM implements Disposable {
|
||||
}
|
||||
|
||||
private registerCommands() {
|
||||
commands.registerCommand('githubBrowser.commit', (...args: any[]) => this.commitChanges(args[0]));
|
||||
commands.registerCommand('githubBrowser.commit', (sourceControl: SourceControl | undefined) => {
|
||||
// TODO@eamodio remove this hack once I figure out why the args are missing
|
||||
if (sourceControl === undefined && this.providers.length === 1) {
|
||||
sourceControl = this.providers[0].sourceControl;
|
||||
}
|
||||
|
||||
if (sourceControl === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.commitChanges(sourceControl);
|
||||
});
|
||||
|
||||
commands.registerCommand('githubBrowser.discardChanges', (resourceState: SourceControlResourceState) =>
|
||||
this.discardChanges(resourceState.resourceUri)
|
||||
|
||||
99
extensions/github-browser/src/statusbar.ts
Normal file
99
extensions/github-browser/src/statusbar.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { Disposable, StatusBarAlignment, StatusBarItem, Uri, window, workspace } from 'vscode';
|
||||
import { ChangeStoreEvent, IChangeStore } from './changeStore';
|
||||
import { GitHubApiContext } from './github/api';
|
||||
import { isSha } from './extension';
|
||||
import { ContextStore, WorkspaceFolderContext } from './contextStore';
|
||||
|
||||
export class StatusBar implements Disposable {
|
||||
private readonly disposable: Disposable;
|
||||
|
||||
private readonly items = new Map<string, StatusBarItem>();
|
||||
|
||||
constructor(
|
||||
private readonly contextStore: ContextStore<GitHubApiContext>,
|
||||
private readonly changeStore: IChangeStore
|
||||
) {
|
||||
this.disposable = Disposable.from(
|
||||
contextStore.onDidChange(this.onContextsChanged, this),
|
||||
changeStore.onDidChange(this.onChanged, this)
|
||||
);
|
||||
|
||||
for (const context of this.contextStore.getForWorkspace()) {
|
||||
this.createOrUpdateStatusBarItem(context);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposable?.dispose();
|
||||
this.items.forEach(i => i.dispose());
|
||||
}
|
||||
|
||||
private createOrUpdateStatusBarItem(wc: WorkspaceFolderContext<GitHubApiContext>) {
|
||||
let item = this.items.get(wc.folderUri.toString());
|
||||
if (item === undefined) {
|
||||
item = window.createStatusBarItem({
|
||||
id: `githubBrowser.branch:${wc.folderUri.toString()}`,
|
||||
name: `GitHub Browser: ${wc.name}`,
|
||||
alignment: StatusBarAlignment.Left,
|
||||
priority: 1000
|
||||
});
|
||||
}
|
||||
|
||||
if (isSha(wc.context.branch)) {
|
||||
item.text = `$(git-commit) ${wc.context.branch.substr(0, 8)}`;
|
||||
item.tooltip = `${wc.name} \u2022 ${wc.context.branch.substr(0, 8)}`;
|
||||
} else {
|
||||
item.text = `$(git-branch) ${wc.context.branch}`;
|
||||
item.tooltip = `${wc.name} \u2022 ${wc.context.branch}${wc.context.sha ? ` @ ${wc.context.sha?.substr(0, 8)}` : ''}`;
|
||||
}
|
||||
|
||||
const hasChanges = this.changeStore.hasChanges(wc.folderUri);
|
||||
if (hasChanges) {
|
||||
item.text += '*';
|
||||
}
|
||||
|
||||
item.show();
|
||||
|
||||
this.items.set(wc.folderUri.toString(), item);
|
||||
}
|
||||
|
||||
private onContextsChanged(uri: Uri) {
|
||||
const folder = workspace.getWorkspaceFolder(this.contextStore.getWorkspaceResource(uri));
|
||||
if (folder === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = this.contextStore.get(uri);
|
||||
if (context === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createOrUpdateStatusBarItem({
|
||||
context: context,
|
||||
name: folder.name,
|
||||
folderUri: folder.uri,
|
||||
});
|
||||
}
|
||||
|
||||
private onChanged(e: ChangeStoreEvent) {
|
||||
const item = this.items.get(e.rootUri.toString());
|
||||
if (item !== undefined) {
|
||||
const hasChanges = this.changeStore.hasChanges(e.rootUri);
|
||||
if (hasChanges) {
|
||||
if (!item.text.endsWith('*')) {
|
||||
item.text += '*';
|
||||
}
|
||||
} else {
|
||||
if (item.text.endsWith('*')) {
|
||||
item.text = item.text.substr(0, item.text.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { API as GitAPI } from './typings/git';
|
||||
import { publishRepository } from './publish';
|
||||
import { combinedDisposable } from './util';
|
||||
|
||||
export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] {
|
||||
const disposables = [];
|
||||
export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
|
||||
disposables.push(vscode.commands.registerCommand('github.publish', async () => {
|
||||
try {
|
||||
@@ -18,5 +19,5 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] {
|
||||
}
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
return combinedDisposable(disposables);
|
||||
}
|
||||
|
||||
@@ -3,23 +3,41 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, ExtensionContext, extensions } from 'vscode';
|
||||
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
|
||||
import { GitExtension } from './typings/git';
|
||||
import { registerCommands } from './commands';
|
||||
import { GithubCredentialProviderManager } from './credentialProvider';
|
||||
import { dispose, combinedDisposable } from './util';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git')!.exports;
|
||||
export function activate(context: ExtensionContext): void {
|
||||
const disposables = new Set<Disposable>();
|
||||
context.subscriptions.push(combinedDisposable(disposables));
|
||||
|
||||
try {
|
||||
const gitAPI = gitExtension.getAPI(1);
|
||||
const init = () => {
|
||||
try {
|
||||
const gitAPI = gitExtension.getAPI(1);
|
||||
|
||||
context.subscriptions.push(...registerCommands(gitAPI));
|
||||
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
|
||||
context.subscriptions.push(new GithubCredentialProviderManager(gitAPI));
|
||||
} catch (err) {
|
||||
console.error('Could not initialize GitHub extension');
|
||||
console.warn(err);
|
||||
}
|
||||
disposables.add(registerCommands(gitAPI));
|
||||
disposables.add(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
|
||||
disposables.add(new GithubCredentialProviderManager(gitAPI));
|
||||
} catch (err) {
|
||||
console.error('Could not initialize GitHub extension');
|
||||
console.warn(err);
|
||||
}
|
||||
};
|
||||
|
||||
const onDidChangeGitExtensionEnablement = (enabled: boolean) => {
|
||||
if (!enabled) {
|
||||
dispose(disposables);
|
||||
disposables.clear();
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const gitExtension = extensions.getExtension<GitExtension>('vscode.git')!.exports;
|
||||
context.subscriptions.push(gitExtension.onDidChangeEnablement(onDidChangeGitExtensionEnablement));
|
||||
onDidChangeGitExtensionEnablement(gitExtension.enabled);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { API as GitAPI, Repository } from './typings/git';
|
||||
import { getOctokit } from './auth';
|
||||
import { TextEncoder } from 'util';
|
||||
import { basename } from 'path';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -28,10 +28,12 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository)
|
||||
return;
|
||||
}
|
||||
|
||||
let folder: vscode.WorkspaceFolder;
|
||||
let folder: vscode.Uri;
|
||||
|
||||
if (vscode.workspace.workspaceFolders.length === 1) {
|
||||
folder = vscode.workspace.workspaceFolders[0];
|
||||
if (repository) {
|
||||
folder = repository.rootUri;
|
||||
} else if (vscode.workspace.workspaceFolders.length === 1) {
|
||||
folder = vscode.workspace.workspaceFolders[0].uri;
|
||||
} else {
|
||||
const picks = vscode.workspace.workspaceFolders.map(folder => ({ label: folder.name, folder }));
|
||||
const placeHolder = localize('pick folder', "Pick a folder to publish to GitHub");
|
||||
@@ -41,14 +43,14 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository)
|
||||
return;
|
||||
}
|
||||
|
||||
folder = pick.folder;
|
||||
folder = pick.folder.uri;
|
||||
}
|
||||
|
||||
let quickpick = vscode.window.createQuickPick<vscode.QuickPickItem & { repo?: string, auth?: 'https' | 'ssh' }>();
|
||||
quickpick.ignoreFocusOut = true;
|
||||
|
||||
quickpick.placeholder = 'Repository Name';
|
||||
quickpick.value = folder.name;
|
||||
quickpick.value = basename(folder.fsPath);
|
||||
quickpick.show();
|
||||
quickpick.busy = true;
|
||||
|
||||
@@ -97,37 +99,49 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository)
|
||||
return;
|
||||
}
|
||||
|
||||
quickpick = vscode.window.createQuickPick();
|
||||
quickpick.placeholder = localize('ignore', "Select which files should be included in the repository.");
|
||||
quickpick.canSelectMany = true;
|
||||
quickpick.show();
|
||||
if (!repository) {
|
||||
const gitignore = vscode.Uri.joinPath(folder, '.gitignore');
|
||||
let shouldGenerateGitignore = false;
|
||||
|
||||
try {
|
||||
quickpick.busy = true;
|
||||
|
||||
const repositoryPath = folder.uri.fsPath;
|
||||
const currentPath = path.join(repositoryPath);
|
||||
const children = await fs.readdir(currentPath);
|
||||
quickpick.items = children.map(name => ({ label: name }));
|
||||
quickpick.selectedItems = quickpick.items;
|
||||
quickpick.busy = false;
|
||||
|
||||
const result = await Promise.race([
|
||||
new Promise<readonly vscode.QuickPickItem[]>(c => quickpick.onDidAccept(() => c(quickpick.selectedItems))),
|
||||
new Promise<undefined>(c => quickpick.onDidHide(() => c(undefined)))
|
||||
]);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
try {
|
||||
await vscode.workspace.fs.stat(gitignore);
|
||||
} catch (err) {
|
||||
shouldGenerateGitignore = true;
|
||||
}
|
||||
|
||||
const ignored = new Set(children);
|
||||
result.forEach(c => ignored.delete(c.label));
|
||||
if (shouldGenerateGitignore) {
|
||||
quickpick = vscode.window.createQuickPick();
|
||||
quickpick.placeholder = localize('ignore', "Select which files should be included in the repository.");
|
||||
quickpick.canSelectMany = true;
|
||||
quickpick.show();
|
||||
|
||||
const raw = [...ignored].map(i => `/${i}`).join('\n');
|
||||
await fs.writeFile(path.join(repositoryPath, '.gitignore'), raw, 'utf8');
|
||||
} finally {
|
||||
quickpick.dispose();
|
||||
try {
|
||||
quickpick.busy = true;
|
||||
|
||||
const children = (await vscode.workspace.fs.readDirectory(folder)).map(([name]) => name);
|
||||
quickpick.items = children.map(name => ({ label: name }));
|
||||
quickpick.selectedItems = quickpick.items;
|
||||
quickpick.busy = false;
|
||||
|
||||
const result = await Promise.race([
|
||||
new Promise<readonly vscode.QuickPickItem[]>(c => quickpick.onDidAccept(() => c(quickpick.selectedItems))),
|
||||
new Promise<undefined>(c => quickpick.onDidHide(() => c(undefined)))
|
||||
]);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ignored = new Set(children);
|
||||
result.forEach(c => ignored.delete(c.label));
|
||||
|
||||
const raw = [...ignored].map(i => `/${i}`).join('\n');
|
||||
const encoder = new TextEncoder();
|
||||
await vscode.workspace.fs.writeFile(gitignore, encoder.encode(raw));
|
||||
} finally {
|
||||
quickpick.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => {
|
||||
@@ -143,7 +157,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository)
|
||||
progress.report({ message: 'Creating first commit', increment: 25 });
|
||||
|
||||
if (!repository) {
|
||||
repository = await gitAPI.init(folder.uri) || undefined;
|
||||
repository = await gitAPI.init(folder) || undefined;
|
||||
|
||||
if (!repository) {
|
||||
return;
|
||||
|
||||
24
extensions/github/src/util.ts
Normal file
24
extensions/github/src/util.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function dispose(arg: vscode.Disposable | Iterable<vscode.Disposable>): void {
|
||||
if (arg instanceof vscode.Disposable) {
|
||||
arg.dispose();
|
||||
} else {
|
||||
for (const disposable of arg) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function combinedDisposable(disposables: Iterable<vscode.Disposable>): vscode.Disposable {
|
||||
return {
|
||||
dispose() {
|
||||
dispose(disposables);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -302,6 +302,14 @@
|
||||
"command": "jupyter.task.openNotebook",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "jupyter.cmd.configurePython",
|
||||
"when": "!notebook:runningOnSAW"
|
||||
},
|
||||
{
|
||||
"command": "jupyter.reinstallDependencies",
|
||||
"when": "!notebook:runningOnSAW"
|
||||
},
|
||||
{
|
||||
"command": "jupyter.cmd.managePackages",
|
||||
"when": "false"
|
||||
@@ -418,7 +426,7 @@
|
||||
"notebook/toolbar": [
|
||||
{
|
||||
"command": "jupyter.cmd.managePackages",
|
||||
"when": "providerId == jupyter && notebook:pythonInstalled"
|
||||
"when": "providerId == jupyter && notebook:pythonInstalled && !notebook:runningOnSAW"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import notebook.notebookapp
|
||||
notebook.notebookapp.main()
|
||||
@@ -348,7 +348,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
|
||||
public async searchJupyterBooks(treeItem?: BookTreeItem): Promise<void> {
|
||||
let folderToSearch: string;
|
||||
if (treeItem && treeItem.book.type !== BookTreeItemType.Notebook) {
|
||||
if (treeItem && treeItem.sections !== undefined) {
|
||||
if (treeItem.uri) {
|
||||
folderToSearch = path.join(treeItem.root, Content, path.dirname(treeItem.uri));
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { CommandContext, BuiltInCommands } from './constants';
|
||||
|
||||
/**
|
||||
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
|
||||
@@ -61,10 +60,6 @@ export class ApiWrapper {
|
||||
azdata.tasks.startBackgroundOperation(operationInfo);
|
||||
}
|
||||
|
||||
public setCommandContext(key: CommandContext | string, value: any): Thenable<any> {
|
||||
return vscode.commands.executeCommand(BuiltInCommands.SetContext, key, value);
|
||||
}
|
||||
|
||||
public getNotebookDocuments() {
|
||||
return azdata.nb.notebookDocuments;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
@@ -15,7 +16,6 @@ import * as tar from 'tar';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import * as constants from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
import { OutputChannel, ConfigurationTarget, window } from 'vscode';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
|
||||
import { IPrompter, IQuestion, confirm } from '../prompts/question';
|
||||
@@ -59,13 +59,13 @@ export interface IJupyterServerInstallation {
|
||||
uninstallPipPackages(packages: PythonPkgDetails[]): Promise<void>;
|
||||
pythonExecutable: string;
|
||||
pythonInstallationPath: string;
|
||||
installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: OutputChannel): Promise<void>;
|
||||
installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: vscode.OutputChannel): Promise<void>;
|
||||
}
|
||||
export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
public apiWrapper: ApiWrapper;
|
||||
public extensionPath: string;
|
||||
public pythonBinPath: string;
|
||||
public outputChannel: OutputChannel;
|
||||
public outputChannel: vscode.OutputChannel;
|
||||
public pythonEnvVarPath: string;
|
||||
public execOptions: ExecOptions;
|
||||
|
||||
@@ -112,14 +112,25 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
private readonly _requiredKernelPackages: Map<string, PythonPkgDetails[]>;
|
||||
private readonly _requiredPackagesSet: Set<string>;
|
||||
|
||||
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
|
||||
private readonly _runningOnSAW: boolean;
|
||||
|
||||
constructor(extensionPath: string, outputChannel: vscode.OutputChannel, apiWrapper: ApiWrapper) {
|
||||
this.extensionPath = extensionPath;
|
||||
this.outputChannel = outputChannel;
|
||||
this.apiWrapper = apiWrapper;
|
||||
this._pythonInstallationPath = pythonInstallationPath || JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
||||
|
||||
this._runningOnSAW = vscode.env.appName.toLowerCase().indexOf('saw') > 0;
|
||||
vscode.commands.executeCommand(constants.BuiltInCommands.SetContext, 'notebook:runningOnSAW', this._runningOnSAW);
|
||||
|
||||
if (this._runningOnSAW) {
|
||||
this._pythonInstallationPath = `${vscode.env.appRoot}\\ads-python`;
|
||||
this._usingExistingPython = true;
|
||||
} else {
|
||||
this._pythonInstallationPath = JupyterServerInstallation.getPythonInstallPath(this.apiWrapper);
|
||||
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
||||
}
|
||||
this._usingConda = false;
|
||||
this._installInProgress = false;
|
||||
this._usingExistingPython = JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper);
|
||||
|
||||
this._prompter = new CodeAdapter();
|
||||
|
||||
@@ -170,7 +181,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
}
|
||||
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
|
||||
window.showInformationMessage(msgInstallPkgStart);
|
||||
vscode.window.showInformationMessage(msgInstallPkgStart);
|
||||
|
||||
this.outputChannel.show(true);
|
||||
this.outputChannel.appendLine(msgInstallPkgProgress);
|
||||
@@ -189,10 +200,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
|
||||
this.outputChannel.appendLine(msgInstallPkgFinish);
|
||||
backgroundOperation.updateStatus(azdata.TaskStatus.Succeeded, msgInstallPkgFinish);
|
||||
window.showInformationMessage(msgInstallPkgFinish);
|
||||
vscode.window.showInformationMessage(msgInstallPkgFinish);
|
||||
}
|
||||
|
||||
public installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: OutputChannel): Promise<void> {
|
||||
public installPythonPackage(backgroundOperation: azdata.BackgroundOperation, usingExistingPython: boolean, pythonInstallationPath: string, outputChannel: vscode.OutputChannel): Promise<void> {
|
||||
if (usingExistingPython) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -435,8 +446,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
this.installDependencies(op, forceInstall, installSettings.specificPackages)
|
||||
.then(async () => {
|
||||
let notebookConfig = this.apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, ConfigurationTarget.Global);
|
||||
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, ConfigurationTarget.Global);
|
||||
await notebookConfig.update(constants.pythonPathConfigKey, this._pythonInstallationPath, vscode.ConfigurationTarget.Global);
|
||||
await notebookConfig.update(constants.existingPythonConfigKey, this._usingExistingPython, vscode.ConfigurationTarget.Global);
|
||||
await this.configurePackagePaths();
|
||||
|
||||
this._installCompletion.resolve();
|
||||
@@ -457,6 +468,9 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
* Opens a dialog for configuring the installation path for the Notebook Python dependencies.
|
||||
*/
|
||||
public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
|
||||
if (this._runningOnSAW) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (this._installInProgress) {
|
||||
this.apiWrapper.showInfoMessage(msgWaitingForInstall);
|
||||
return this._installCompletion.promise;
|
||||
@@ -482,6 +496,9 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
* Prompts user to upgrade certain python packages if they're below the minimum expected version.
|
||||
*/
|
||||
public async promptForPackageUpgrade(kernelName: string): Promise<void> {
|
||||
if (this._runningOnSAW) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (this._installInProgress) {
|
||||
this.apiWrapper.showInfoMessage(msgWaitingForInstall);
|
||||
return this._installCompletion.promise;
|
||||
@@ -835,22 +852,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the folder containing the python executable under the path defined in
|
||||
* "notebook.pythonPath" in the user's settings.
|
||||
* @param apiWrapper An ApiWrapper to use when retrieving user settings info.
|
||||
*/
|
||||
public static getPythonBinPath(apiWrapper: ApiWrapper): string {
|
||||
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
|
||||
|
||||
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
|
||||
|
||||
return path.join(
|
||||
JupyterServerInstallation.getPythonInstallPath(apiWrapper),
|
||||
useExistingInstall ? '' : constants.pythonBundleVersion,
|
||||
pythonBinPathSuffix);
|
||||
}
|
||||
|
||||
public static getPythonExePath(pythonInstallPath: string, useExistingInstall: boolean): string {
|
||||
return path.join(
|
||||
pythonInstallPath,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||
import * as utils from '../common/utils';
|
||||
import { IServerInstance } from './common';
|
||||
import { PerFolderServerInstance, IInstanceOptions } from './serverInstance';
|
||||
import { CommandContext } from '../common/constants';
|
||||
import { CommandContext, BuiltInCommands } from '../common/constants';
|
||||
|
||||
export interface IServerManagerOptions {
|
||||
documentPath: string;
|
||||
@@ -112,7 +112,7 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
|
||||
if (!installation.previewFeaturesEnabled) {
|
||||
await installation.promptForPackageUpgrade(kernelSpec.display_name);
|
||||
}
|
||||
this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true);
|
||||
vscode.commands.executeCommand(BuiltInCommands.SetContext, CommandContext.NotebookPythonInstalled, true);
|
||||
|
||||
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the
|
||||
// notebook to open. This will be the workspace folder if the notebook uri is inside a workspace
|
||||
|
||||
@@ -127,8 +127,11 @@ export class PerFolderServerInstance implements IServerInstance {
|
||||
private childProcess: ChildProcess;
|
||||
private errorHandler: ErrorHandler = new ErrorHandler();
|
||||
|
||||
private readonly notebookScriptPath: string;
|
||||
|
||||
constructor(private options: IInstanceOptions, fsUtils?: ServerInstanceUtils) {
|
||||
this.utils = fsUtils || new ServerInstanceUtils();
|
||||
this.notebookScriptPath = path.join(this.options.install.extensionPath, 'resources', 'pythonScripts', 'startNotebook.py');
|
||||
}
|
||||
|
||||
public get isStarted(): boolean {
|
||||
@@ -162,7 +165,7 @@ export class PerFolderServerInstance implements IServerInstance {
|
||||
}
|
||||
if (this._isStarted) {
|
||||
let install = this.options.install;
|
||||
let stopCommand = `"${install.pythonExecutable}" -m jupyter notebook stop ${this._port}`;
|
||||
let stopCommand = `"${install.pythonExecutable}" "${this.notebookScriptPath}" stop ${this._port}`;
|
||||
await this.utils.executeBufferedCommand(stopCommand, install.execOptions, install.outputChannel);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -243,7 +246,7 @@ export class PerFolderServerInstance implements IServerInstance {
|
||||
let token = await utils.getRandomToken();
|
||||
this._uri = vscode.Uri.parse(`http://localhost:${port}/?token=${token}`);
|
||||
this._port = port.toString();
|
||||
let startCommand = `"${this.options.install.pythonExecutable}" -m jupyter notebook --no-browser --no-mathjax --notebook-dir "${notebookDirectory}" --port=${port} --NotebookApp.token=${token}`;
|
||||
let startCommand = `"${this.options.install.pythonExecutable}" "${this.notebookScriptPath}" --no-browser --no-mathjax --notebook-dir "${notebookDirectory}" --port=${port} --NotebookApp.token=${token}`;
|
||||
this.notifyStarting(this.options.install, startCommand);
|
||||
|
||||
// Execute the command
|
||||
|
||||
@@ -19,7 +19,7 @@ interface TestContext {
|
||||
describe('Manage Packages', () => {
|
||||
let jupyterServerInstallation: JupyterServerInstallation;
|
||||
beforeEach(() => {
|
||||
jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined, undefined, undefined);
|
||||
jupyterServerInstallation = new JupyterServerInstallation(undefined, undefined, undefined);
|
||||
});
|
||||
|
||||
it('Should throw exception given undefined providers', async function (): Promise<void> {
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('Jupyter server instance', function (): void {
|
||||
await serverInstance.stop();
|
||||
|
||||
// Then I expect stop to be called on the child process
|
||||
should(actualCommand.indexOf(`jupyter notebook stop ${serverInstance.port}`)).be.greaterThan(-1);
|
||||
should(actualCommand.includes(`stop ${serverInstance.port}`)).be.true('Command did not contain specified port.');
|
||||
mockUtils.verify(u => u.removeDir(TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"description": "Dependencies shared by all extensions",
|
||||
"dependencies": {
|
||||
"typescript": "3.9.5"
|
||||
"typescript": "3.9.6"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./postinstall"
|
||||
|
||||
@@ -122,5 +122,11 @@
|
||||
"foreground": "#CBEDCB",
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"semanticTokenColors": {
|
||||
"newOperator": "#FFFFFF",
|
||||
"stringLiteral": "#ce9178",
|
||||
"customLiteral": "#DCDCAA",
|
||||
"numberLiteral": "#b5cea8",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"statusBarItem.remoteBackground": "#16825D",
|
||||
"sideBarSectionHeader.background": "#0000",
|
||||
"sideBarSectionHeader.border": "#61616130",
|
||||
"notebook.cellFocusBackground": "#c8ddf150",
|
||||
"notebook.focusedCellBackground": "#c8ddf150",
|
||||
"notebook.cellBorderColor": "#dae3e9",
|
||||
"notebook.outputContainerBackgroundColor": "#c8ddf150",
|
||||
"notebook.focusedCellShadow": "#00315040"
|
||||
|
||||
@@ -76,10 +76,10 @@ rimraf@^3.0.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
typescript@3.9.5:
|
||||
version "3.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
|
||||
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
|
||||
typescript@3.9.6:
|
||||
version "3.9.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
|
||||
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"preinstall": "node build/npm/preinstall.js",
|
||||
"postinstall": "node build/npm/postinstall.js",
|
||||
"compile": "gulp compile --max_old_space_size=4095",
|
||||
"watch": "gulp watch --max_old_space_size=4095",
|
||||
"watch": "concurrently \"npm:watch-client\" \"npm:watch-extensions\"",
|
||||
"watchd": "deemon yarn watch",
|
||||
"watch-webd": "deemon yarn watch-web",
|
||||
"kill-watchd": "deemon --kill yarn watch",
|
||||
@@ -22,6 +22,7 @@
|
||||
"restart-watchd": "deemon --restart yarn watch",
|
||||
"restart-watch-webd": "deemon --restart yarn watch-web",
|
||||
"watch-client": "gulp watch-client --max_old_space_size=4095",
|
||||
"watch-extensions": "gulp watch-extensions --max_old_space_size=4095",
|
||||
"mocha": "mocha test/unit/node/all.js --delay",
|
||||
"precommit": "node build/gulpfile.hygiene.js",
|
||||
"gulp": "gulp --max_old_space_size=8192",
|
||||
@@ -61,7 +62,7 @@
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.3",
|
||||
"iconv-lite-umd": "0.6.5",
|
||||
"iconv-lite-umd": "0.6.7",
|
||||
"jquery": "3.5.0",
|
||||
"jschardet": "2.1.1",
|
||||
"keytar": "^5.5.0",
|
||||
@@ -123,6 +124,7 @@
|
||||
"ansi-colors": "^3.2.3",
|
||||
"asar": "^0.14.0",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"concurrently": "^5.2.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"cson-parser": "^1.3.3",
|
||||
"css-loader": "^3.2.0",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
||||
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
||||
"vscodeVersion": "1.46.0",
|
||||
"vscodeVersion": "1.47.0",
|
||||
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
||||
"date": "2017-12-15T12:00:00.000Z",
|
||||
"recommendedExtensions": [
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.3",
|
||||
"iconv-lite-umd": "0.6.5",
|
||||
"iconv-lite-umd": "0.6.7",
|
||||
"jquery": "3.5.0",
|
||||
"jschardet": "2.1.1",
|
||||
"minimist": "^1.2.5",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"ansi_up": "^3.0.0",
|
||||
"chart.js": "^2.6.0",
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6",
|
||||
"iconv-lite-umd": "0.6.5",
|
||||
"iconv-lite-umd": "0.6.7",
|
||||
"jschardet": "2.1.1",
|
||||
"jquery": "3.5.0",
|
||||
"ng2-charts": "^1.6.0",
|
||||
|
||||
@@ -182,10 +182,10 @@ htmlparser2@^3.10.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
iconv-lite-umd@0.6.5:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
|
||||
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
|
||||
iconv-lite-umd@0.6.7:
|
||||
version "0.6.7"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c"
|
||||
integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ==
|
||||
|
||||
inherits@^2.0.1, inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
|
||||
@@ -376,10 +376,10 @@ https-proxy-agent@^4.0.0:
|
||||
agent-base "5"
|
||||
debug "4"
|
||||
|
||||
iconv-lite-umd@0.6.5:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
|
||||
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
|
||||
iconv-lite-umd@0.6.7:
|
||||
version "0.6.7"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.7.tgz#ee437e34b30f15dc00ec93ea65065e672770777c"
|
||||
integrity sha512-DT90zb7wL1B3I6DmYUMcfJeVdY19XigzDj5AtXbXEw9Jfi0+AVAxfn7ytvY7Xhr+GFn7nd7hPonapC37oo7iAQ==
|
||||
|
||||
inherits@^2.0.1, inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
|
||||
@@ -232,20 +232,23 @@ async function handleRoot(req, res) {
|
||||
if (match) {
|
||||
const qs = new URLSearchParams(match[1]);
|
||||
|
||||
let ghPath = qs.get('gh');
|
||||
if (ghPath) {
|
||||
if (!ghPath.startsWith('/')) {
|
||||
ghPath = '/' + ghPath;
|
||||
let gh = qs.get('gh');
|
||||
if (gh) {
|
||||
if (gh.startsWith('/')) {
|
||||
gh = gh.substr(1);
|
||||
}
|
||||
folderUri = { scheme: 'github', authority: 'HEAD', path: ghPath };
|
||||
} else {
|
||||
|
||||
let csPath = qs.get('cs');
|
||||
if (csPath) {
|
||||
if (!csPath.startsWith('/')) {
|
||||
csPath = '/' + csPath;
|
||||
const [owner, repo, ...branch] = gh.split('/', 3);
|
||||
folderUri = { scheme: 'github', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` };
|
||||
} else {
|
||||
let cs = qs.get('cs');
|
||||
if (cs) {
|
||||
if (cs.startsWith('/')) {
|
||||
cs = cs.substr(1);
|
||||
}
|
||||
folderUri = { scheme: 'codespace', authority: 'HEAD', path: csPath };
|
||||
|
||||
const [owner, repo, ...branch] = cs.split('/');
|
||||
folderUri = { scheme: 'codespace', authority: branch.join('/') || 'HEAD', path: `/${owner}/${repo}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface IQueryEditorConfiguration {
|
||||
readonly encoding: string
|
||||
},
|
||||
readonly saveAsXml: {
|
||||
readonly formatted: string,
|
||||
readonly formatted: boolean,
|
||||
readonly encoding: string
|
||||
},
|
||||
readonly streaming: boolean,
|
||||
|
||||
@@ -34,8 +34,8 @@ export default class DeclarativeTableComponent extends ContainerBase<any> implem
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
|
||||
public data: any[][] = [];
|
||||
public columns: azdata.DeclarativeTableColumn[] = [];
|
||||
private data: any[][] = [];
|
||||
private columns: azdata.DeclarativeTableColumn[] = [];
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@@ -132,6 +132,7 @@ export default class DeclarativeTableComponent extends ContainerBase<any> implem
|
||||
|
||||
private onCellDataChanged(newValue: any, rowIdx: number, colIdx: number): void {
|
||||
this.data[rowIdx][colIdx] = newValue;
|
||||
this.setPropertyFromUI<azdata.DeclarativeTableProperties, any[][]>((props, value) => props.data = value, this.data);
|
||||
let newCellData: azdata.TableCell = {
|
||||
row: rowIdx,
|
||||
column: colIdx,
|
||||
|
||||
@@ -11,6 +11,24 @@ import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbenc
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
// eslint-disable-next-line code-import-patterns
|
||||
import * as vsTreeView from 'vs/workbench/api/browser/mainThreadTreeViews';
|
||||
import { ResolvableTreeItem } from 'vs/workbench/common/views';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
export class ResolvableTreeComponentItem extends ResolvableTreeItem implements ITreeComponentItem {
|
||||
|
||||
checked?: boolean;
|
||||
enabled?: boolean;
|
||||
onCheckedChanged?: (checked: boolean) => void;
|
||||
children?: ITreeComponentItem[];
|
||||
|
||||
constructor(treeItem: ITreeComponentItem, resolve?: (() => Promise<ITreeComponentItem | undefined>)) {
|
||||
super(treeItem, resolve);
|
||||
this.checked = treeItem.checked;
|
||||
this.enabled = treeItem.enabled;
|
||||
this.onCheckedChanged = treeItem.onCheckedChanged;
|
||||
this.children = deepClone(treeItem.children);
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implements IModelViewTreeViewDataProvider {
|
||||
constructor(handle: number, treeViewId: string,
|
||||
@@ -32,4 +50,24 @@ export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implem
|
||||
|
||||
refresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of mapped ResolvableTreeComponentItems
|
||||
* @override
|
||||
* @param elements The elements to map
|
||||
*/
|
||||
protected async postGetChildren(elements: ITreeComponentItem[]): Promise<ResolvableTreeComponentItem[]> {
|
||||
const result: ResolvableTreeComponentItem[] = [];
|
||||
const hasResolve = await this.hasResolve;
|
||||
if (elements) {
|
||||
for (const element of elements) {
|
||||
const resolvable = new ResolvableTreeComponentItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ExecutionPlanOptions } from 'azdata';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
|
||||
const MAX_SIZE = 13;
|
||||
|
||||
@@ -156,7 +157,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
|
||||
}));
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectedKeys.indexOf('sql.showConnectionInfoInTitle') > -1) {
|
||||
if (e.affectedKeys.indexOf('queryEditor') > -1) {
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}));
|
||||
@@ -196,7 +197,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
|
||||
public get resource(): URI { return this._text.resource; }
|
||||
|
||||
public getName(longForm?: boolean): string {
|
||||
if (this.configurationService.getValue('sql.showConnectionInfoInTitle')) {
|
||||
if (this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').showConnectionInfoInTitle) {
|
||||
let profile = this.connectionManagementService.getConnectionProfile(this.uri);
|
||||
let title = '';
|
||||
if (this._description && this._description !== '') {
|
||||
|
||||
32
src/sql/workbench/common/workspaceActions.ts
Normal file
32
src/sql/workbench/common/workspaceActions.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
// eslint-disable-next-line code-layering,code-import-patterns
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
|
||||
export class ShowFileInFolderAction extends Action {
|
||||
|
||||
constructor(private path: string, label: string, @IElectronService private electronService: IElectronService) {
|
||||
super('showItemInFolder.action.id', label);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
return this.electronService.showItemInFolder(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenFileInFolderAction extends Action {
|
||||
|
||||
constructor(private path: string, label: string, @IOpenerService private openerService: IOpenerService) {
|
||||
super('openItemInFolder.action.id', label);
|
||||
}
|
||||
|
||||
run() {
|
||||
return this.openerService.open(URI.file(this.path), { openExternal: true });
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ export interface SqlArgs {
|
||||
user?: string;
|
||||
command?: string;
|
||||
provider?: string;
|
||||
aad?: boolean; // deprecated - used by SSMS - authenticationType should be used instead
|
||||
integrated?: boolean; // deprecated - used by SSMS - authenticationType should be used instead.
|
||||
}
|
||||
|
||||
//#region decorators
|
||||
@@ -278,7 +280,22 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
|
||||
profile.serverName = args.server;
|
||||
profile.databaseName = args.database ?? '';
|
||||
profile.userName = args.user ?? '';
|
||||
profile.authenticationType = args.authenticationType ?? Constants.integrated;
|
||||
|
||||
/*
|
||||
Authentication Type:
|
||||
1. Take --authenticationType, if not
|
||||
2. Take --integrated, if not
|
||||
3. take --aad, if not
|
||||
4. If user exists, and user has @, then it's azureMFA
|
||||
5. If user doesn't exist, or user doesn't have @, then integrated
|
||||
*/
|
||||
profile.authenticationType =
|
||||
args.authenticationType ? args.authenticationType :
|
||||
args.integrated ? Constants.integrated :
|
||||
args.aad ? Constants.azureMFA :
|
||||
(args.user && args.user.length > 0) ? args.user.includes('@') ? Constants.azureMFA : Constants.integrated :
|
||||
Constants.integrated;
|
||||
|
||||
profile.connectionName = '';
|
||||
profile.setOptionValue('applicationName', Constants.applicationName);
|
||||
profile.setOptionValue('databaseDisplayName', profile.databaseName);
|
||||
|
||||
@@ -346,7 +346,7 @@ export class NotebookEditor extends BaseEditor implements IFindNotebookControlle
|
||||
this._findDecorations.getCount(),
|
||||
this._currentMatch
|
||||
);
|
||||
if (this._finder.getDomNode().style.visibility === 'visible') {
|
||||
if (this._finder.getDomNode().style.visibility === 'visible' && this._previousMatch !== this._currentMatch) {
|
||||
this._setCurrentFindMatch(this._currentMatch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { ToggleableAction } from 'sql/workbench/contrib/notebook/browser/notebookActions';
|
||||
import { IInsightOptions } from 'sql/workbench/common/editor/query/chartState';
|
||||
import { NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@Component({
|
||||
selector: GridOutputComponent.SELECTOR,
|
||||
@@ -309,7 +308,7 @@ class DataResourceDataProvider implements IGridDataProvider {
|
||||
return serializer.handleSerialization(this.documentUri, format, (filePath) => this.doSerialize(serializer, filePath, format, selection));
|
||||
}
|
||||
|
||||
private doSerialize(serializer: ResultSerializer, filePath: URI, format: SaveFormat, selection: Slick.Range[]): Promise<SaveResultsResponse | undefined> {
|
||||
private doSerialize(serializer: ResultSerializer, filePath: string, format: SaveFormat, selection: Slick.Range[]): Promise<SaveResultsResponse | undefined> {
|
||||
if (!this.canSerialize) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@@ -347,7 +346,7 @@ class DataResourceDataProvider implements IGridDataProvider {
|
||||
let serializeRequestParams: SerializeDataParams = <SerializeDataParams>assign(serializer.getBasicSaveParameters(format), <Partial<SerializeDataParams>>{
|
||||
saveFormat: format,
|
||||
columns: columns,
|
||||
filePath: filePath.fsPath,
|
||||
filePath: filePath,
|
||||
getRowRange: (rowStart, numberOfRows) => getRows(rowStart, numberOfRows),
|
||||
rowCount: rowLength
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t
|
||||
import { UNSAVED_GROUP_ID, mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { OEManageConnectionAction } from 'sql/workbench/contrib/dashboard/browser/dashboardActions';
|
||||
import { IViewsService, IView, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
|
||||
import { IViewsService, IView, ViewContainerLocation, ViewContainer, IViewPaneContainer } from 'vs/workbench/common/views';
|
||||
import { ConsoleLogService } from 'vs/platform/log/common/log';
|
||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
@@ -111,6 +111,9 @@ suite('SQL Connection Tree Action tests', () => {
|
||||
});
|
||||
|
||||
const viewsService = new class implements IViewsService {
|
||||
getActiveViewPaneContainerWithId(viewContainerId: string): IViewPaneContainer {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getViewProgressIndicator(id: string): IProgressIndicator {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import { GridPanelState, GridTableState } from 'sql/workbench/common/editor/quer
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
|
||||
import { Progress } from 'vs/platform/progress/common/progress';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
|
||||
const ROW_HEIGHT = 29;
|
||||
const HEADER_HEIGHT = 26;
|
||||
@@ -123,7 +124,7 @@ export class GridPanel extends Disposable {
|
||||
this.reset();
|
||||
}));
|
||||
this.addResultSet(this.runner.batchSets.reduce<ResultSetSummary[]>((p, e) => {
|
||||
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
|
||||
if (this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.streaming) {
|
||||
p = p.concat(e.resultSetSummaries);
|
||||
} else {
|
||||
p = p.concat(e.resultSetSummaries.filter(c => c.complete));
|
||||
@@ -157,7 +158,7 @@ export class GridPanel extends Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
|
||||
if (this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.streaming) {
|
||||
this.addResultSet(resultsToAdd);
|
||||
sizeChanges();
|
||||
} else {
|
||||
@@ -183,7 +184,7 @@ export class GridPanel extends Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
|
||||
if (this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.streaming) {
|
||||
for (let set of resultsToUpdate) {
|
||||
let table = find(this.tables, t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
|
||||
if (table) {
|
||||
|
||||
@@ -341,7 +341,7 @@ const queryEditorConfiguration: IConfigurationNode = {
|
||||
'default': 'utf-8'
|
||||
},
|
||||
'queryEditor.results.saveAsXml.formatted': {
|
||||
'type': 'string',
|
||||
'type': 'boolean',
|
||||
'description': localize('queryEditor.results.saveAsXml.formatted', "When true, XML output will be formatted when saving results as XML"),
|
||||
'default': true
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
|
||||
const editorInputFactoryRegistry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
|
||||
@@ -130,7 +131,7 @@ export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
|
||||
serialize(editorInput: UntitledQueryEditorInput): string {
|
||||
const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledTextEditorInput.ID);
|
||||
// only serialize non-dirty files if the user has that setting
|
||||
if (factory && (editorInput.isDirty() || this.configurationService.getValue<boolean>('sql.promptToSaveGeneratedFiles'))) {
|
||||
if (factory && (editorInput.isDirty() || this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').promptToSaveGeneratedFiles)) {
|
||||
return factory.serialize(editorInput.text); // serialize based on the underlying input
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ACCOUNT_VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewC
|
||||
name: localize('accountExplorer.name', "Accounts"),
|
||||
ctorDescriptor: new SyncDescriptor(AccountPaneContainer),
|
||||
storageId: `${VIEWLET_ID}.state`
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
}, ViewContainerLocation.Dialog);
|
||||
|
||||
class AccountPanel extends ViewPane {
|
||||
public index: number;
|
||||
|
||||
@@ -147,9 +147,6 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
private getAllServerGroups(providers?: string[]): IConnectionProfileGroup[] {
|
||||
let connectionGroupRoot = this._connectionManagementService.getConnectionGroups(providers);
|
||||
let allGroups: IConnectionProfileGroup[] = [];
|
||||
if (connectionGroupRoot && connectionGroupRoot.length > 0) {
|
||||
this.flattenGroups(connectionGroupRoot[0], allGroups);
|
||||
}
|
||||
let defaultGroupId: string;
|
||||
if (connectionGroupRoot && connectionGroupRoot.length > 0 && ConnectionProfileGroup.isRoot(connectionGroupRoot[0].name)) {
|
||||
defaultGroupId = connectionGroupRoot[0].id;
|
||||
@@ -158,6 +155,9 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
}
|
||||
allGroups.push(assign({}, this._connectionWidget.DefaultServerGroup, { id: defaultGroupId }));
|
||||
allGroups.push(this._connectionWidget.NoneServerGroup);
|
||||
if (connectionGroupRoot && connectionGroupRoot.length > 0) {
|
||||
this.flattenGroups(connectionGroupRoot[0], allGroups);
|
||||
}
|
||||
connectionGroupRoot.forEach(cpg => cpg.dispose());
|
||||
return allGroups;
|
||||
}
|
||||
|
||||
@@ -41,17 +41,30 @@ import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { ViewPane, IViewPaneOptions, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { attachPanelStyler, attachModalDialogStyler } from 'sql/workbench/common/styler';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
|
||||
const labelDisplay = nls.localize("insights.item", "Item");
|
||||
const valueDisplay = nls.localize("insights.value", "Value");
|
||||
const iconClass = 'codicon';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.insightdetails';
|
||||
|
||||
export class InsightsDetailPaneContainer extends ViewPaneContainer { }
|
||||
|
||||
export const INSIGHTS_DETAIL_VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: VIEWLET_ID,
|
||||
name: nls.localize('insightsDetailView.name', "Insight Details"),
|
||||
ctorDescriptor: new SyncDescriptor(InsightsDetailPaneContainer),
|
||||
storageId: `${VIEWLET_ID}.state`
|
||||
}, ViewContainerLocation.Dialog);
|
||||
|
||||
class InsightTableView extends ViewPane {
|
||||
private _table: Table<ListResource>;
|
||||
public get table(): Table<ListResource> {
|
||||
@@ -217,17 +230,29 @@ export class InsightsDialogView extends Modal {
|
||||
|
||||
this._splitView = new SplitView(container);
|
||||
|
||||
const itemsViewId = 'insights.top';
|
||||
const itemDetailsViewId = 'insights.bottom';
|
||||
const itemsHeaderTitle = nls.localize("insights.dialog.items", "Items");
|
||||
const itemsDetailHeaderTitle = nls.localize("insights.dialog.itemDetails", "Item Details");
|
||||
|
||||
this._topTableData = new TableDataView<ListResource>();
|
||||
this._bottomTableData = new TableDataView<ListResource>();
|
||||
let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle });
|
||||
let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: itemsViewId, title: itemsHeaderTitle });
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
id: itemsViewId,
|
||||
name: itemsHeaderTitle,
|
||||
ctorDescriptor: new SyncDescriptor(InsightTableView),
|
||||
}], INSIGHTS_DETAIL_VIEW_CONTAINER);
|
||||
topTableView.render();
|
||||
attachPanelStyler(topTableView, this._themeService);
|
||||
this._topTable = topTableView.table;
|
||||
this._topTable.setSelectionModel(new RowSelectionModel<ListResource>());
|
||||
let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle });
|
||||
let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: itemDetailsViewId, title: itemsDetailHeaderTitle });
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
id: itemDetailsViewId,
|
||||
name: itemsDetailHeaderTitle,
|
||||
ctorDescriptor: new SyncDescriptor(InsightTableView),
|
||||
}], INSIGHTS_DETAIL_VIEW_CONTAINER);
|
||||
bottomTableView.render();
|
||||
attachPanelStyler(bottomTableView, this._themeService);
|
||||
this._bottomTable = bottomTableView.table;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { BatchSummary, IQueryMessage, ResultSetSummary, QueryExecuteSubsetParams, CompleteBatchSummary, IResultMessage, ResultSetSubset, BatchStartSummary } from './query';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
|
||||
/*
|
||||
* Query Runner class which handles running a query, reports the results to the content manager,
|
||||
@@ -442,7 +443,7 @@ export default class QueryRunner extends Disposable {
|
||||
|
||||
private sendBatchTimeMessage(batchId: number, executionTime: string): void {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let showBatchTime = this.configurationService.getValue<boolean>('sql.showBatchTime');
|
||||
let showBatchTime = this.configurationService.getValue<IQueryEditorConfiguration>('queryEditor').messages.showBatchTime;
|
||||
if (showBatchTime) {
|
||||
let message: IQueryMessage = {
|
||||
batchId: batchId,
|
||||
@@ -538,13 +539,13 @@ export function shouldIncludeHeaders(includeHeaders: boolean, configurationServi
|
||||
return includeHeaders;
|
||||
}
|
||||
// else get config option from vscode config
|
||||
includeHeaders = configurationService.getValue<boolean>('sql.copyIncludeHeaders');
|
||||
includeHeaders = configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.copyIncludeHeaders;
|
||||
return !!includeHeaders;
|
||||
}
|
||||
|
||||
export function shouldRemoveNewLines(configurationService: IConfigurationService): boolean {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let removeNewLines = configurationService.getValue<boolean>('sql.copyRemoveNewLine');
|
||||
let removeNewLines = configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.copyRemoveNewLine;
|
||||
return !!removeNewLines;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,16 @@ import * as nls from 'vs/nls';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService, INotification } from 'vs/platform/notification/common/notification';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getRootPath, resolveCurrentDirectory } from 'sql/platform/common/pathUtilities';
|
||||
import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
let prevSavePath: URI;
|
||||
let prevSavePath: string;
|
||||
|
||||
export interface ISaveRequest {
|
||||
format: SaveFormat;
|
||||
@@ -32,19 +36,6 @@ export interface SaveResultsResponse {
|
||||
messages?: string;
|
||||
}
|
||||
|
||||
interface ICsvConfig {
|
||||
includeHeaders: boolean;
|
||||
delimiter: string;
|
||||
lineSeperator: string;
|
||||
textIdentifier: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
interface IXmlConfig {
|
||||
formatted: boolean;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
export enum SaveFormat {
|
||||
CSV = 'csv',
|
||||
JSON = 'json',
|
||||
@@ -53,6 +44,7 @@ export enum SaveFormat {
|
||||
}
|
||||
|
||||
const msgSaveFailed = nls.localize('msgSaveFailed', "Failed to save results. ");
|
||||
const msgSaveSucceeded = nls.localize('msgSaveSucceeded', "Successfully saved results to ");
|
||||
|
||||
/**
|
||||
* Handles save results request from the context menu of slickGrid
|
||||
@@ -66,7 +58,8 @@ export class ResultSerializer {
|
||||
@IEditorService private _editorService: IEditorService,
|
||||
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@INotificationService private _notificationService: INotificationService
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
/**
|
||||
@@ -76,6 +69,9 @@ export class ResultSerializer {
|
||||
const self = this;
|
||||
return this.promptForFilepath(saveRequest.format, uri).then(filePath => {
|
||||
if (filePath) {
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = resolveFilePath(uri, filePath, this.rootPath)!;
|
||||
}
|
||||
let saveResultsParams = this.getParameters(uri, filePath, saveRequest.batchIndex, saveRequest.resultSetNumber, saveRequest.format, saveRequest.selection ? saveRequest.selection[0] : undefined);
|
||||
let sendRequest = () => this.sendSaveRequestToService(saveResultsParams);
|
||||
return self.doSave(filePath, saveRequest.format, sendRequest);
|
||||
@@ -95,11 +91,14 @@ export class ResultSerializer {
|
||||
/**
|
||||
* Handle save request by getting filename from user and sending request to service
|
||||
*/
|
||||
public handleSerialization(uri: string, format: SaveFormat, sendRequest: ((filePath: URI) => Promise<SaveResultsResponse | undefined>)): Thenable<void> {
|
||||
public handleSerialization(uri: string, format: SaveFormat, sendRequest: ((filePath: string) => Promise<SaveResultsResponse | undefined>)): Thenable<void> {
|
||||
const self = this;
|
||||
return this.promptForFilepath(format, uri).then(filePath => {
|
||||
if (filePath) {
|
||||
return self.doSave(filePath, format, () => sendRequest(filePath));
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = resolveFilePath(uri, filePath, this.rootPath)!;
|
||||
}
|
||||
return self.doSave(filePath, format, () => sendRequest(filePath!));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
@@ -109,8 +108,8 @@ export class ResultSerializer {
|
||||
return getRootPath(this._contextService);
|
||||
}
|
||||
|
||||
private promptForFilepath(format: SaveFormat, resourceUri: string): Promise<URI | undefined> {
|
||||
let filepathPlaceHolder = prevSavePath ? path.dirname(prevSavePath.fsPath) : resolveCurrentDirectory(resourceUri, this.rootPath);
|
||||
private promptForFilepath(format: SaveFormat, resourceUri: string): Promise<string | undefined> {
|
||||
let filepathPlaceHolder = prevSavePath ? path.dirname(prevSavePath) : resolveCurrentDirectory(resourceUri, this.rootPath);
|
||||
if (filepathPlaceHolder) {
|
||||
filepathPlaceHolder = path.join(filepathPlaceHolder, this.getResultsDefaultFilename(format));
|
||||
}
|
||||
@@ -121,8 +120,8 @@ export class ResultSerializer {
|
||||
filters: this.getResultsFileExtension(format)
|
||||
}).then(filePath => {
|
||||
if (filePath) {
|
||||
prevSavePath = filePath;
|
||||
return filePath;
|
||||
prevSavePath = filePath.fsPath;
|
||||
return filePath.fsPath;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -199,7 +198,7 @@ export class ResultSerializer {
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.CSV as string };
|
||||
|
||||
// get save results config from vscode config
|
||||
let saveConfig = this._configurationService.getValue<ICsvConfig>('sql.saveAsCsv');
|
||||
let saveConfig = this._configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.saveAsCsv;
|
||||
// if user entered config, set options
|
||||
if (saveConfig) {
|
||||
if (saveConfig.includeHeaders !== undefined) {
|
||||
@@ -245,7 +244,7 @@ export class ResultSerializer {
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.XML as string };
|
||||
|
||||
// get save results config from vscode config
|
||||
let saveConfig = this._configurationService.getValue<IXmlConfig>('sql.saveAsXml');
|
||||
let saveConfig = this._configurationService.getValue<IQueryEditorConfiguration>('queryEditor').results.saveAsXml;
|
||||
// if user entered config, set options
|
||||
if (saveConfig) {
|
||||
if (saveConfig.formatted !== undefined) {
|
||||
@@ -260,9 +259,9 @@ export class ResultSerializer {
|
||||
}
|
||||
|
||||
|
||||
private getParameters(uri: string, filePath: URI, batchIndex: number, resultSetNo: number, format: string, selection?: Slick.Range): SaveResultsRequestParams {
|
||||
private getParameters(uri: string, filePath: string, batchIndex: number, resultSetNo: number, format: string, selection?: Slick.Range): SaveResultsRequestParams {
|
||||
let saveResultsParams = this.getBasicSaveParameters(format);
|
||||
saveResultsParams.filePath = filePath.fsPath;
|
||||
saveResultsParams.filePath = filePath;
|
||||
saveResultsParams.ownerUri = uri;
|
||||
saveResultsParams.resultSetIndex = resultSetNo;
|
||||
saveResultsParams.batchIndex = batchIndex;
|
||||
@@ -282,10 +281,35 @@ export class ResultSerializer {
|
||||
return !!(selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
|
||||
}
|
||||
|
||||
|
||||
private promptFileSavedNotification(savedFilePath: string) {
|
||||
let label = getBaseLabel(path.dirname(savedFilePath));
|
||||
|
||||
this._notificationService.prompt(
|
||||
Severity.Info,
|
||||
msgSaveSucceeded + savedFilePath,
|
||||
[{
|
||||
label: nls.localize('openLocation', "Open file location"),
|
||||
run: () => {
|
||||
let action = this._instantiationService.createInstance(ShowFileInFolderAction, savedFilePath, label || path.sep);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('openFile', "Open file"),
|
||||
run: () => {
|
||||
let action = this._instantiationService.createInstance(OpenFileInFolderAction, savedFilePath, label || path.sep);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to sql tools service to save a result set
|
||||
*/
|
||||
private async doSave(filePath: URI, format: string, sendRequest: () => Promise<SaveResultsResponse | undefined>): Promise<void> {
|
||||
private async doSave(filePath: string, format: string, sendRequest: () => Promise<SaveResultsResponse | undefined>): Promise<void> {
|
||||
|
||||
const saveNotification: INotification = {
|
||||
severity: Severity.Info,
|
||||
@@ -305,6 +329,7 @@ export class ResultSerializer {
|
||||
message: msgSaveFailed + (result ? result.messages : '')
|
||||
});
|
||||
} else {
|
||||
this.promptFileSavedNotification(filePath);
|
||||
this.openSavedFile(filePath, format);
|
||||
}
|
||||
// TODO telemetry for save results
|
||||
@@ -323,9 +348,10 @@ export class ResultSerializer {
|
||||
/**
|
||||
* Open the saved file in a new vscode editor pane
|
||||
*/
|
||||
private openSavedFile(filePath: URI, format: string): void {
|
||||
private openSavedFile(filePath: string, format: string): void {
|
||||
if (format !== SaveFormat.EXCEL) {
|
||||
this._editorService.openEditor({ resource: filePath }).then((result) => {
|
||||
let uri = URI.file(filePath);
|
||||
this._editorService.openEditor({ resource: uri }).then((result) => {
|
||||
|
||||
}, (error: any) => {
|
||||
this._notificationService.notify({
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
||||
import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { IQueryEditorConfiguration } from 'sql/platform/query/common/query';
|
||||
|
||||
const defaults: INewSqlEditorOptions = {
|
||||
open: true
|
||||
@@ -55,7 +56,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
let untitledEditorModel = await fileInput.resolve() as UntitledTextEditorModel;
|
||||
if (options.initalContent) {
|
||||
untitledEditorModel.textEditorModel.setValue(options.initalContent);
|
||||
if (options.dirty === false || (options.dirty === undefined && !this._configurationService.getValue<boolean>('sql.promptToSaveGeneratedFiles'))) {
|
||||
if (options.dirty === false || (options.dirty === undefined && !this._configurationService.getValue<IQueryEditorConfiguration>('queryEditor').promptToSaveGeneratedFiles)) {
|
||||
untitledEditorModel.setDirty(false);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -854,6 +854,7 @@ export interface IListOptions<T> {
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
|
||||
const HIDE_TIMEOUT = 500;
|
||||
const SCROLL_WHEEL_SENSITIVITY = 50;
|
||||
@@ -130,13 +131,18 @@ export class MouseWheelClassifier {
|
||||
// }
|
||||
}
|
||||
|
||||
if (Math.abs(item.deltaX - Math.round(item.deltaX)) > 0 || Math.abs(item.deltaY - Math.round(item.deltaY)) > 0) {
|
||||
if (!this._isAlmostInt(item.deltaX) || !this._isAlmostInt(item.deltaY)) {
|
||||
// non-integer deltas => indicator that this is not a physical mouse wheel
|
||||
score += 0.25;
|
||||
}
|
||||
|
||||
return Math.min(Math.max(score, 0), 1);
|
||||
}
|
||||
|
||||
private _isAlmostInt(value: number): boolean {
|
||||
const delta = Math.abs(Math.round(value) - value);
|
||||
return (delta < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractScrollableElement extends Widget {
|
||||
@@ -343,10 +349,11 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
const classifier = MouseWheelClassifier.INSTANCE;
|
||||
if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) {
|
||||
if (platform.isWindows) {
|
||||
// On Windows, the incoming delta events are multiplied with the device pixel ratio,
|
||||
// so to get a better classification, simply undo that.
|
||||
classifier.accept(Date.now(), e.deltaX / window.devicePixelRatio, e.deltaY / window.devicePixelRatio);
|
||||
const osZoomFactor = window.devicePixelRatio / getZoomFactor();
|
||||
if (platform.isWindows || platform.isLinux) {
|
||||
// On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor.
|
||||
// The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account.
|
||||
classifier.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor);
|
||||
} else {
|
||||
classifier.accept(Date.now(), e.deltaX, e.deltaY);
|
||||
}
|
||||
|
||||
@@ -951,6 +951,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
||||
readonly filterOnType?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly expandOnlyOnDoubleClick?: boolean;
|
||||
}
|
||||
|
||||
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
|
||||
@@ -1094,7 +1095,10 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
return super.onViewPointer(e);
|
||||
}
|
||||
|
||||
const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie');
|
||||
const target = e.browserEvent.target as HTMLElement;
|
||||
const onTwistie = hasClass(target, 'monaco-tl-twistie')
|
||||
|| (hasClass(target, 'monaco-icon-label') && hasClass(target, 'folder-icon') && e.browserEvent.offsetX < 16);
|
||||
|
||||
let expandOnlyOnTwistieClick = false;
|
||||
|
||||
if (typeof this.tree.expandOnlyOnTwistieClick === 'function') {
|
||||
@@ -1107,6 +1111,10 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
|
||||
return super.onViewPointer(e);
|
||||
}
|
||||
|
||||
if (this.tree.expandOnlyOnDoubleClick && e.browserEvent.detail !== 2 && !onTwistie) {
|
||||
return super.onViewPointer(e);
|
||||
}
|
||||
|
||||
if (node.collapsible) {
|
||||
const model = ((this.tree as any).model as ITreeModel<T, TFilterData, TRef>); // internal
|
||||
const location = model.getNodeLocation(node);
|
||||
@@ -1244,6 +1252,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
get filterOnType(): boolean { return !!this._options.filterOnType; }
|
||||
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
|
||||
|
||||
get expandOnlyOnDoubleClick(): boolean { return this._options.expandOnlyOnDoubleClick ?? false; }
|
||||
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
|
||||
|
||||
private readonly _onDidUpdateOptions = new Emitter<IAbstractTreeOptions<T, TFilterData>>();
|
||||
|
||||
@@ -472,6 +472,8 @@ export namespace Codicon {
|
||||
export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' });
|
||||
export const playCircle = new Codicon('play-circle', { character: '\\eba6' });
|
||||
export const record = new Codicon('record', { character: '\\eba7' });
|
||||
export const debugAltSmall = new Codicon('debug-alt-small', { character: '\\eba8' });
|
||||
export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,8 +36,7 @@ export interface IPopupOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
positioningItem?: number;
|
||||
onHide?: () => void;
|
||||
}
|
||||
|
||||
export const CONTEXT_MENU_CHANNEL = 'vscode:contextmenu';
|
||||
export const CONTEXT_MENU_CLOSE_CHANNEL = 'vscode:onCloseContextMenu';
|
||||
export const CONTEXT_MENU_CLOSE_CHANNEL = 'vscode:onCloseContextMenu';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IContextMenuItem, ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHAN
|
||||
|
||||
let contextMenuIdPool = 0;
|
||||
|
||||
export function popup(items: IContextMenuItem[], options?: IPopupOptions): void {
|
||||
export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide?: () => void): void {
|
||||
const processedItems: IContextMenuItem[] = [];
|
||||
|
||||
const contextMenuId = contextMenuIdPool++;
|
||||
@@ -28,8 +28,8 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void
|
||||
|
||||
ipcRenderer.removeListener(onClickChannel, onClickChannelHandler);
|
||||
|
||||
if (options?.onHide) {
|
||||
options.onHide();
|
||||
if (onHide) {
|
||||
onHide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1230,10 +1230,10 @@ export class QuickInputController extends Disposable {
|
||||
this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined;
|
||||
}, true));
|
||||
this._register(focusTracker.onDidBlur(() => {
|
||||
this.previousFocusElement = undefined;
|
||||
if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
|
||||
this.hide(true);
|
||||
this.hide();
|
||||
}
|
||||
this.previousFocusElement = undefined;
|
||||
}));
|
||||
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => {
|
||||
inputBox.setFocus();
|
||||
@@ -1574,13 +1574,14 @@ export class QuickInputController extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
hide(focusLost?: boolean) {
|
||||
hide() {
|
||||
const controller = this.controller;
|
||||
if (controller) {
|
||||
const focusChanged = !this.ui?.container.contains(document.activeElement);
|
||||
this.controller = null;
|
||||
this.onHideEmitter.fire();
|
||||
this.getUI().container.style.display = 'none';
|
||||
if (!focusLost) {
|
||||
if (!focusChanged) {
|
||||
if (this.previousFocusElement && this.previousFocusElement.offsetParent) {
|
||||
this.previousFocusElement.focus();
|
||||
this.previousFocusElement = undefined;
|
||||
|
||||
@@ -277,6 +277,9 @@ class WorkspaceProvider implements IWorkspaceProvider {
|
||||
|
||||
(function () {
|
||||
|
||||
// Mark start of workbench
|
||||
performance.mark('workbench-start');
|
||||
|
||||
// Find config by checking for DOM
|
||||
const configElement = document.getElementById('vscode-workbench-web-configuration');
|
||||
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
|
||||
|
||||
@@ -55,6 +55,8 @@ bootstrapWindow.load([
|
||||
'vs/css!vs/workbench/workbench.desktop.main'
|
||||
],
|
||||
function (workbench, configuration) {
|
||||
|
||||
// Mark start of workbench
|
||||
perf.mark('didLoadWorkbenchMain');
|
||||
performance.mark('workbench-start');
|
||||
|
||||
|
||||
@@ -280,6 +280,12 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
|
||||
if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) {
|
||||
let selectionSearchString = await this.getGlobalBufferTerm();
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
// the editor has lost its model in the meantime
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionSearchString) {
|
||||
stateChanges.searchString = selectionSearchString;
|
||||
}
|
||||
@@ -364,13 +370,14 @@ export class CommonFindController extends Disposable implements IEditorContribut
|
||||
return '';
|
||||
}
|
||||
|
||||
public async setGlobalBufferTerm(text: string) {
|
||||
public setGlobalBufferTerm(text: string): void {
|
||||
if (this._editor.getOption(EditorOption.find).globalFindClipboard
|
||||
&& this._clipboardService
|
||||
&& this._editor.hasModel()
|
||||
&& !this._editor.getModel().isTooLargeForSyncing()
|
||||
) {
|
||||
await this._clipboardService.writeFindText(text);
|
||||
// intentionally not awaited
|
||||
this._clipboardService.writeFindText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,10 +431,12 @@ export class FindController extends CommonFindController implements IFindControl
|
||||
|
||||
await super._start(opts);
|
||||
|
||||
if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {
|
||||
this._widget!.focusReplaceInput();
|
||||
} else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) {
|
||||
this._widget!.focusFindInput();
|
||||
if (this._widget) {
|
||||
if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {
|
||||
this._widget.focusReplaceInput();
|
||||
} else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) {
|
||||
this._widget.focusFindInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,10 +479,10 @@ export class StartFindAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise<void> {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
controller.start({
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard,
|
||||
@@ -508,7 +517,7 @@ export class StartFindWithSelectionAction extends EditorAction {
|
||||
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
controller.start({
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: true,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -518,15 +527,15 @@ export class StartFindWithSelectionAction extends EditorAction {
|
||||
loop: editor.getOption(EditorOption.find).loop
|
||||
});
|
||||
|
||||
return controller.setGlobalBufferTerm(controller.getState().searchString);
|
||||
controller.setGlobalBufferTerm(controller.getState().searchString);
|
||||
}
|
||||
}
|
||||
}
|
||||
export abstract class MatchFindAction extends EditorAction {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise<void> {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller && !this._run(controller)) {
|
||||
controller.start({
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromGlobalClipboard: true,
|
||||
@@ -629,7 +638,7 @@ export class PreviousMatchFindAction2 extends MatchFindAction {
|
||||
}
|
||||
|
||||
export abstract class SelectionMatchFindAction extends EditorAction {
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise<void> {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
@@ -639,7 +648,7 @@ export abstract class SelectionMatchFindAction extends EditorAction {
|
||||
controller.setSearchString(selectionSearchString);
|
||||
}
|
||||
if (!this._run(controller)) {
|
||||
controller.start({
|
||||
await controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -720,7 +729,7 @@ export class StartFindReplaceAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void {
|
||||
public async run(accessor: ServicesAccessor | null, editor: ICodeEditor): Promise<void> {
|
||||
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly)) {
|
||||
return;
|
||||
}
|
||||
@@ -745,7 +754,7 @@ export class StartFindReplaceAction extends EditorAction {
|
||||
|
||||
|
||||
if (controller) {
|
||||
controller.start({
|
||||
await controller.start({
|
||||
forceRevealReplace: true,
|
||||
seedSearchStringFromSelection: seedSearchStringFromSelection,
|
||||
seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, NextSelectionMatchFindAction, StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/findController';
|
||||
import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
|
||||
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
@@ -55,7 +55,7 @@ function fromSelection(slc: Selection): number[] {
|
||||
return [slc.startLineNumber, slc.startColumn, slc.endLineNumber, slc.endColumn];
|
||||
}
|
||||
|
||||
suite.skip('FindController', () => {
|
||||
suite.skip('FindController', async () => {
|
||||
let queryState: { [key: string]: any; } = {};
|
||||
let clipboardState = '';
|
||||
let serviceCollection = new ServiceCollection();
|
||||
@@ -77,13 +77,13 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
}
|
||||
|
||||
/* test('stores to the global clipboard buffer on start find action', () => {
|
||||
withTestCodeEditor([
|
||||
/* test('stores to the global clipboard buffer on start find action', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
if (!platform.isMacintosh) {
|
||||
assert.ok(true);
|
||||
@@ -101,13 +101,13 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('reads from the global clipboard buffer on next find action if buffer exists', () => {
|
||||
withTestCodeEditor([
|
||||
test('reads from the global clipboard buffer on next find action if buffer exists', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = 'ABC';
|
||||
|
||||
if (!platform.isMacintosh) {
|
||||
@@ -128,13 +128,13 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('writes to the global clipboard buffer when text changes', () => {
|
||||
withTestCodeEditor([
|
||||
test('writes to the global clipboard buffer when text changes', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
if (!platform.isMacintosh) {
|
||||
assert.ok(true);
|
||||
@@ -152,13 +152,13 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
}); */
|
||||
|
||||
test('issue #1857: F3, Find Next, acts like "Find Under Cursor"', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #1857: F3, Find Next, acts like "Find Under Cursor"', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
// The cursor is at the very top, of the file, at the first ABC
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
@@ -167,7 +167,7 @@ suite.skip('FindController', () => {
|
||||
let nextMatchFindAction = new NextMatchFindAction();
|
||||
|
||||
// I hit Ctrl+F to show the Find dialog
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
// I type ABC.
|
||||
findState.change({ searchString: 'A' }, true);
|
||||
@@ -201,7 +201,7 @@ suite.skip('FindController', () => {
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 4, 1, 4]);
|
||||
|
||||
// I hit F3 to "Find Next" to find the next occurrence of ABC, but instead it searches for XYZ.
|
||||
nextMatchFindAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
|
||||
assert.equal(findState.searchString, 'ABC');
|
||||
assert.equal(findController.hasFocus, false);
|
||||
@@ -210,10 +210,10 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #3090: F3 does not loop with two matches on a single line', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #3090: F3 does not loop with two matches on a single line', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'import nls = require(\'vs/nls\');'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let nextMatchFindAction = new NextMatchFindAction();
|
||||
@@ -223,22 +223,22 @@ suite.skip('FindController', () => {
|
||||
column: 9
|
||||
});
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 26, 1, 29]);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 8, 1, 11]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #6149: Auto-escape highlighted text for search and replace regex mode', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #6149: Auto-escape highlighted text for search and replace regex mode', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let startFindAction = new StartFindAction();
|
||||
@@ -247,22 +247,22 @@ suite.skip('FindController', () => {
|
||||
editor.setSelection(new Selection(1, 9, 1, 13));
|
||||
|
||||
findController.toggleRegex();
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [2, 9, 2, 13]);
|
||||
|
||||
nextMatchFindAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
assert.deepEqual(fromSelection(editor.getSelection()!), [1, 9, 1, 13]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #41027: Don\'t replace find input value on replace action if find input is active', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #41027: Don\'t replace find input value on replace action if find input is active', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'test',
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
let testRegexString = 'tes.';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let nextMatchFindAction = new NextMatchFindAction();
|
||||
@@ -270,7 +270,7 @@ suite.skip('FindController', () => {
|
||||
|
||||
findController.toggleRegex();
|
||||
findController.setSearchString(testRegexString);
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -279,8 +279,8 @@ suite.skip('FindController', () => {
|
||||
updateSearchScope: false,
|
||||
loop: true
|
||||
});
|
||||
nextMatchFindAction.run(null, editor);
|
||||
startFindReplaceAction.run(null, editor);
|
||||
await nextMatchFindAction.run(null, editor);
|
||||
await startFindReplaceAction.run(null, editor);
|
||||
|
||||
assert.equal(findController.getState().searchString, testRegexString);
|
||||
|
||||
@@ -288,15 +288,15 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #9043: Clear search scope when find widget is hidden', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #9043: Clear search scope when find widget is hidden', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -319,15 +319,15 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #18111: Regex replace with single space replaces with no space', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #18111: Regex replace with single space replaces with no space', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'HRESULT OnAmbientPropertyChange(DISPID dispid);'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
let startFindAction = new StartFindAction();
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
findController.getState().change({ searchString: '\\b\\s{3}\\b', replaceString: ' ', isRegex: true }, false);
|
||||
findController.moveToNextMatch();
|
||||
@@ -344,17 +344,17 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #24714: Regular expression with ^ in search & replace', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #24714: Regular expression with ^ in search & replace', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'',
|
||||
'line2',
|
||||
'line3'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
let startFindAction = new StartFindAction();
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
findController.getState().change({ searchString: '^', replaceString: 'x', isRegex: true }, false);
|
||||
findController.moveToNextMatch();
|
||||
@@ -371,12 +371,12 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #38232: Find Next Selection, regex enabled', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #38232: Find Next Selection, regex enabled', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'([funny]',
|
||||
'',
|
||||
'([funny]'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let nextSelectionMatchFindAction = new NextSelectionMatchFindAction();
|
||||
@@ -388,7 +388,7 @@ suite.skip('FindController', () => {
|
||||
editor.setSelection(new Selection(1, 1, 1, 9));
|
||||
|
||||
// cmd+f3
|
||||
nextSelectionMatchFindAction.run(null, editor);
|
||||
await nextSelectionMatchFindAction.run(null, editor);
|
||||
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[3, 1, 3, 9]
|
||||
@@ -398,19 +398,19 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #38232: Find Next Selection, regex enabled, find widget open', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #38232: Find Next Selection, regex enabled, find widget open', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'([funny]',
|
||||
'',
|
||||
'([funny]'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
clipboardState = '';
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
let startFindAction = new StartFindAction();
|
||||
let nextSelectionMatchFindAction = new NextSelectionMatchFindAction();
|
||||
|
||||
// cmd+f - open find widget
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
// toggle regex
|
||||
findController.getState().change({ isRegex: true }, false);
|
||||
@@ -419,7 +419,7 @@ suite.skip('FindController', () => {
|
||||
editor.setSelection(new Selection(1, 1, 1, 9));
|
||||
|
||||
// cmd+f3
|
||||
nextSelectionMatchFindAction.run(null, editor);
|
||||
await nextSelectionMatchFindAction.run(null, editor);
|
||||
|
||||
assert.deepEqual(editor.getSelections()!.map(fromSelection), [
|
||||
[3, 1, 3, 9]
|
||||
@@ -430,7 +430,7 @@ suite.skip('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
suite.skip('FindController query options persistence', () => {
|
||||
suite.skip('FindController query options persistence', async () => {
|
||||
let queryState: { [key: string]: any; } = {};
|
||||
queryState['editor.isRegex'] = false;
|
||||
queryState['editor.matchCase'] = false;
|
||||
@@ -447,13 +447,13 @@ suite.skip('FindController query options persistence', () => {
|
||||
remove: () => undefined
|
||||
} as any);
|
||||
|
||||
test('matchCase', () => {
|
||||
withTestCodeEditor([
|
||||
test('matchCase', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'abc',
|
||||
'ABC',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
queryState = { 'editor.isRegex': false, 'editor.matchCase': true, 'editor.wholeWord': false };
|
||||
// The cursor is at the very top, of the file, at the first ABC
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
@@ -461,7 +461,7 @@ suite.skip('FindController query options persistence', () => {
|
||||
let startFindAction = new StartFindAction();
|
||||
|
||||
// I hit Ctrl+F to show the Find dialog
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
// I type ABC.
|
||||
findState.change({ searchString: 'ABC' }, true);
|
||||
@@ -474,13 +474,13 @@ suite.skip('FindController query options persistence', () => {
|
||||
|
||||
queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true };
|
||||
|
||||
test('wholeWord', () => {
|
||||
withTestCodeEditor([
|
||||
test('wholeWord', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'AB',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true };
|
||||
// The cursor is at the very top, of the file, at the first ABC
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
@@ -488,7 +488,7 @@ suite.skip('FindController query options persistence', () => {
|
||||
let startFindAction = new StartFindAction();
|
||||
|
||||
// I hit Ctrl+F to show the Find dialog
|
||||
startFindAction.run(null, editor);
|
||||
await startFindAction.run(null, editor);
|
||||
|
||||
// I type AB.
|
||||
findState.change({ searchString: 'AB' }, true);
|
||||
@@ -499,13 +499,13 @@ suite.skip('FindController query options persistence', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('toggling options is saved', () => {
|
||||
withTestCodeEditor([
|
||||
test('toggling options is saved', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'ABC',
|
||||
'AB',
|
||||
'XYZ',
|
||||
'ABC'
|
||||
], { serviceCollection: serviceCollection }, (editor) => {
|
||||
], { serviceCollection: serviceCollection }, async (editor) => {
|
||||
queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true };
|
||||
// The cursor is at the very top, of the file, at the first ABC
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
@@ -516,17 +516,17 @@ suite.skip('FindController query options persistence', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #27083: Update search scope once find widget becomes visible', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #27083: Update search scope once find widget becomes visible', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 1, 2, 1));
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -540,17 +540,17 @@ suite.skip('FindController query options persistence', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #58604: Do not update searchScope if it is empty', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #58604: Do not update searchScope if it is empty', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 2, 1, 2));
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -564,17 +564,17 @@ suite.skip('FindController query options persistence', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #58604: Update searchScope if it is not empty', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #58604: Update searchScope if it is not empty', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, (editor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'always', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 2, 1, 3));
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
@@ -589,17 +589,17 @@ suite.skip('FindController query options persistence', () => {
|
||||
});
|
||||
|
||||
|
||||
test('issue #27083: Find in selection when multiple lines are selected', () => {
|
||||
withTestCodeEditor([
|
||||
test('issue #27083: Find in selection when multiple lines are selected', async () => {
|
||||
await withAsyncTestCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, (editor) => {
|
||||
], { serviceCollection: serviceCollection, find: { autoFindInSelection: 'multiline', globalFindClipboard: false } }, async (editor) => {
|
||||
// clipboardState = '';
|
||||
editor.setSelection(new Range(1, 6, 2, 1));
|
||||
let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController);
|
||||
|
||||
findController.start({
|
||||
await findController.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromSelection: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
|
||||
@@ -96,6 +96,24 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test
|
||||
editor.dispose();
|
||||
}
|
||||
|
||||
export async function withAsyncTestCodeEditor(text: string | string[] | null, options: TestCodeEditorCreationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel) => Promise<void>): Promise<void> {
|
||||
// create a model if necessary and remember it in order to dispose it.
|
||||
if (!options.model) {
|
||||
if (typeof text === 'string') {
|
||||
options.model = createTextModel(text);
|
||||
} else if (text) {
|
||||
options.model = createTextModel(text.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
const editor = createTestCodeEditor(options);
|
||||
const viewModel = editor.getViewModel()!;
|
||||
viewModel.setHasFocus(true);
|
||||
await callback(<ITestCodeEditor>editor, editor.getViewModel()!);
|
||||
|
||||
editor.dispose();
|
||||
}
|
||||
|
||||
export function createTestCodeEditor(options: TestCodeEditorCreationOptions): ITestCodeEditor {
|
||||
|
||||
const model = options.model;
|
||||
|
||||
@@ -870,6 +870,12 @@ var AMDLoader;
|
||||
}
|
||||
var cachedData = script.createCachedData();
|
||||
if (cachedData.length === 0 || cachedData.length === lastSize || iteration >= 5) {
|
||||
// done
|
||||
return;
|
||||
}
|
||||
if (cachedData.length < lastSize) {
|
||||
// less data than before: skip, try again next round
|
||||
createLoop();
|
||||
return;
|
||||
}
|
||||
lastSize = cachedData.length;
|
||||
|
||||
@@ -23,4 +23,3 @@ export class ThemableCheckboxActionViewItem extends CheckboxActionViewItem {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
|
||||
suite('ConfigurationService', () => {
|
||||
|
||||
let fileService: IFileService;
|
||||
let settingsResource: URI;
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
|
||||
setup(async () => {
|
||||
fileService = disposables.add(new FileService(new NullLogService()));
|
||||
const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
settingsResource = URI.file('settings.json');
|
||||
});
|
||||
|
||||
teardown(() => disposables.clear());
|
||||
|
||||
test('simple', async () => {
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }'));
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await testObject.initialize();
|
||||
const config = testObject.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
});
|
||||
|
||||
test('config gets flattened', async () => {
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }'));
|
||||
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await testObject.initialize();
|
||||
const config = testObject.getValue<{
|
||||
testworkbench: {
|
||||
editor: {
|
||||
tabs: boolean;
|
||||
};
|
||||
};
|
||||
}>();
|
||||
|
||||
assert.ok(config);
|
||||
assert.ok(config.testworkbench);
|
||||
assert.ok(config.testworkbench.editor);
|
||||
assert.equal(config.testworkbench.editor.tabs, true);
|
||||
});
|
||||
|
||||
test('error case does not explode', async () => {
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,'));
|
||||
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await testObject.initialize();
|
||||
const config = testObject.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
|
||||
assert.ok(config);
|
||||
});
|
||||
|
||||
test('missing file does not explode', async () => {
|
||||
const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService));
|
||||
await testObject.initialize();
|
||||
|
||||
const config = testObject.getValue<{ foo: string }>();
|
||||
|
||||
assert.ok(config);
|
||||
});
|
||||
|
||||
test('trigger configuration change event when file does not exist', async () => {
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await testObject.initialize();
|
||||
return new Promise(async (c, e) => {
|
||||
disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => {
|
||||
assert.equal(testObject.getValue('foo'), 'bar');
|
||||
c();
|
||||
}));
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('trigger configuration change event when file exists', async () => {
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }'));
|
||||
await testObject.initialize();
|
||||
|
||||
return new Promise((c, e) => {
|
||||
disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => {
|
||||
assert.equal(testObject.getValue('foo'), 'barz');
|
||||
c();
|
||||
}));
|
||||
fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "barz" }'));
|
||||
});
|
||||
});
|
||||
|
||||
test('reloadConfiguration', async () => {
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }'));
|
||||
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
await testObject.initialize();
|
||||
let config = testObject.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "changed" }'));
|
||||
|
||||
// force a reload to get latest
|
||||
await testObject.reloadConfiguration();
|
||||
config = testObject.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'changed');
|
||||
});
|
||||
|
||||
test('model defaults', async () => {
|
||||
interface ITestSetting {
|
||||
configuration: {
|
||||
service: {
|
||||
testSetting: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_test',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'configuration.service.testSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService));
|
||||
await testObject.initialize();
|
||||
let setting = testObject.getValue<ITestSetting>();
|
||||
|
||||
assert.ok(setting);
|
||||
assert.equal(setting.configuration.service.testSetting, 'isSet');
|
||||
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }'));
|
||||
testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
|
||||
setting = testObject.getValue<ITestSetting>();
|
||||
|
||||
assert.ok(setting);
|
||||
assert.equal(setting.configuration.service.testSetting, 'isSet');
|
||||
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "configuration.service.testSetting": "isChanged" }'));
|
||||
|
||||
await testObject.reloadConfiguration();
|
||||
setting = testObject.getValue<ITestSetting>();
|
||||
assert.ok(setting);
|
||||
assert.equal(setting.configuration.service.testSetting, 'isChanged');
|
||||
});
|
||||
|
||||
test('lookup', async () => {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_test',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'lookup.service.testSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
testObject.initialize();
|
||||
|
||||
let res = testObject.inspect('something.missing');
|
||||
assert.strictEqual(res.value, undefined);
|
||||
assert.strictEqual(res.defaultValue, undefined);
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
res = testObject.inspect('lookup.service.testSetting');
|
||||
assert.strictEqual(res.defaultValue, 'isSet');
|
||||
assert.strictEqual(res.value, 'isSet');
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "lookup.service.testSetting": "bar" }'));
|
||||
|
||||
await testObject.reloadConfiguration();
|
||||
res = testObject.inspect('lookup.service.testSetting');
|
||||
assert.strictEqual(res.defaultValue, 'isSet');
|
||||
assert.strictEqual(res.userValue, 'bar');
|
||||
assert.strictEqual(res.value, 'bar');
|
||||
|
||||
});
|
||||
|
||||
test('lookup with null', async () => {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_testNull',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'lookup.service.testNullSetting': {
|
||||
'type': 'null',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
|
||||
testObject.initialize();
|
||||
|
||||
let res = testObject.inspect('lookup.service.testNullSetting');
|
||||
assert.strictEqual(res.defaultValue, null);
|
||||
assert.strictEqual(res.value, null);
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "lookup.service.testNullSetting": null }'));
|
||||
|
||||
await testObject.reloadConfiguration();
|
||||
|
||||
res = testObject.inspect('lookup.service.testNullSetting');
|
||||
assert.strictEqual(res.defaultValue, null);
|
||||
assert.strictEqual(res.value, null);
|
||||
assert.strictEqual(res.userValue, null);
|
||||
});
|
||||
});
|
||||
@@ -1,304 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { testFile } from 'vs/base/test/node/utils';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
suite('ConfigurationService - Node', () => {
|
||||
|
||||
let fileService: IFileService;
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
setup(() => {
|
||||
const logService = new NullLogService();
|
||||
fileService = new FileService(logService);
|
||||
disposables.push(fileService);
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
|
||||
disposables.push(diskFileSystemProvider);
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
});
|
||||
|
||||
test('simple', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
fs.writeFileSync(res.testFile, '{ "foo": "bar" }');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
await service.initialize();
|
||||
const config = service.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
service.dispose();
|
||||
|
||||
return res.cleanUp();
|
||||
});
|
||||
|
||||
test('config gets flattened', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
|
||||
fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
await service.initialize();
|
||||
const config = service.getValue<{
|
||||
testworkbench: {
|
||||
editor: {
|
||||
tabs: boolean;
|
||||
};
|
||||
};
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.ok(config.testworkbench);
|
||||
assert.ok(config.testworkbench.editor);
|
||||
assert.equal(config.testworkbench.editor.tabs, true);
|
||||
|
||||
service.dispose();
|
||||
return res.cleanUp();
|
||||
});
|
||||
|
||||
test('error case does not explode', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
|
||||
fs.writeFileSync(res.testFile, ',,,,');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
await service.initialize();
|
||||
const config = service.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
|
||||
service.dispose();
|
||||
return res.cleanUp();
|
||||
});
|
||||
|
||||
test('missing file does not explode', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'config', id);
|
||||
const testFile = path.join(newDir, 'config.json');
|
||||
|
||||
const service = new ConfigurationService(URI.file(testFile), fileService);
|
||||
await service.initialize();
|
||||
|
||||
const config = service.getValue<{ foo: string }>();
|
||||
assert.ok(config);
|
||||
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
test('trigger configuration change event when file does not exist', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
const settingsFile = URI.file(res.testFile);
|
||||
const service = new ConfigurationService(settingsFile, fileService);
|
||||
await service.initialize();
|
||||
return new Promise(async (c, e) => {
|
||||
const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => {
|
||||
disposable.dispose();
|
||||
assert.equal(service.getValue('foo'), 'bar');
|
||||
service.dispose();
|
||||
await res.cleanUp();
|
||||
c();
|
||||
});
|
||||
await fileService.writeFile(settingsFile, VSBuffer.fromString('{ "foo": "bar" }'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('trigger configuration change event when file exists', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
fs.writeFileSync(res.testFile, '{ "foo": "bar" }');
|
||||
await service.initialize();
|
||||
return new Promise((c, e) => {
|
||||
const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => {
|
||||
disposable.dispose();
|
||||
assert.equal(service.getValue('foo'), 'barz');
|
||||
service.dispose();
|
||||
await res.cleanUp();
|
||||
c();
|
||||
});
|
||||
fs.writeFileSync(res.testFile, '{ "foo": "barz" }');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('reloadConfiguration', async () => {
|
||||
const res = await testFile('config', 'config.json');
|
||||
|
||||
fs.writeFileSync(res.testFile, '{ "foo": "bar" }');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
await service.initialize();
|
||||
let config = service.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
fs.writeFileSync(res.testFile, '{ "foo": "changed" }');
|
||||
|
||||
// still outdated
|
||||
config = service.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'bar');
|
||||
|
||||
// force a reload to get latest
|
||||
await service.reloadConfiguration();
|
||||
config = service.getValue<{
|
||||
foo: string;
|
||||
}>();
|
||||
assert.ok(config);
|
||||
assert.equal(config.foo, 'changed');
|
||||
|
||||
service.dispose();
|
||||
return res.cleanUp();
|
||||
});
|
||||
|
||||
test('model defaults', async () => {
|
||||
interface ITestSetting {
|
||||
configuration: {
|
||||
service: {
|
||||
testSetting: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_test',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'configuration.service.testSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let serviceWithoutFile = new ConfigurationService(URI.file('__testFile'), fileService);
|
||||
await serviceWithoutFile.initialize();
|
||||
let setting = serviceWithoutFile.getValue<ITestSetting>();
|
||||
|
||||
assert.ok(setting);
|
||||
assert.equal(setting.configuration.service.testSetting, 'isSet');
|
||||
|
||||
return testFile('config', 'config.json').then(async res => {
|
||||
fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }');
|
||||
|
||||
const service = new ConfigurationService(URI.file(res.testFile), fileService);
|
||||
|
||||
let setting = service.getValue<ITestSetting>();
|
||||
|
||||
assert.ok(setting);
|
||||
assert.equal(setting.configuration.service.testSetting, 'isSet');
|
||||
|
||||
fs.writeFileSync(res.testFile, '{ "configuration.service.testSetting": "isChanged" }');
|
||||
|
||||
await service.reloadConfiguration();
|
||||
let setting_1 = service.getValue<ITestSetting>();
|
||||
assert.ok(setting_1);
|
||||
assert.equal(setting_1.configuration.service.testSetting, 'isChanged');
|
||||
service.dispose();
|
||||
serviceWithoutFile.dispose();
|
||||
return res.cleanUp();
|
||||
});
|
||||
});
|
||||
|
||||
test('lookup', async () => {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_test',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'lookup.service.testSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const r = await testFile('config', 'config.json');
|
||||
const service = new ConfigurationService(URI.file(r.testFile), fileService);
|
||||
service.initialize();
|
||||
|
||||
let res = service.inspect('something.missing');
|
||||
assert.strictEqual(res.value, undefined);
|
||||
assert.strictEqual(res.defaultValue, undefined);
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
res = service.inspect('lookup.service.testSetting');
|
||||
assert.strictEqual(res.defaultValue, 'isSet');
|
||||
assert.strictEqual(res.value, 'isSet');
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
fs.writeFileSync(r.testFile, '{ "lookup.service.testSetting": "bar" }');
|
||||
|
||||
await service.reloadConfiguration();
|
||||
res = service.inspect('lookup.service.testSetting');
|
||||
assert.strictEqual(res.defaultValue, 'isSet');
|
||||
assert.strictEqual(res.userValue, 'bar');
|
||||
assert.strictEqual(res.value, 'bar');
|
||||
|
||||
service.dispose();
|
||||
return r.cleanUp();
|
||||
});
|
||||
|
||||
test('lookup with null', async () => {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_testNull',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'lookup.service.testNullSetting': {
|
||||
'type': 'null',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const r = await testFile('config', 'config.json');
|
||||
const service = new ConfigurationService(URI.file(r.testFile), fileService);
|
||||
service.initialize();
|
||||
|
||||
let res = service.inspect('lookup.service.testNullSetting');
|
||||
assert.strictEqual(res.defaultValue, null);
|
||||
assert.strictEqual(res.value, null);
|
||||
assert.strictEqual(res.userValue, undefined);
|
||||
|
||||
fs.writeFileSync(r.testFile, '{ "lookup.service.testNullSetting": null }');
|
||||
|
||||
await service.reloadConfiguration();
|
||||
|
||||
res = service.inspect('lookup.service.testNullSetting');
|
||||
assert.strictEqual(res.defaultValue, null);
|
||||
assert.strictEqual(res.value, null);
|
||||
assert.strictEqual(res.userValue, null);
|
||||
|
||||
service.dispose();
|
||||
return r.cleanUp();
|
||||
});
|
||||
});
|
||||
@@ -56,7 +56,10 @@ export async function readFromStdin(targetPath: string, verbose: boolean): Promi
|
||||
const decoder = iconv.getDecoder(encoding);
|
||||
process.stdin.on('data', chunk => stdinFileStream.write(decoder.write(chunk)));
|
||||
process.stdin.on('end', () => {
|
||||
stdinFileStream.write(decoder.end());
|
||||
const end = decoder.end();
|
||||
if (typeof end === 'string') {
|
||||
stdinFileStream.write(end);
|
||||
}
|
||||
stdinFileStream.end();
|
||||
});
|
||||
process.stdin.on('error', error => stdinFileStream.destroy(error));
|
||||
|
||||
@@ -63,8 +63,6 @@ export class IndexedDB {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class IndexedDBFileSystemProvider extends KeyValueFileSystemProvider {
|
||||
|
||||
constructor(scheme: string, private readonly database: IDBDatabase, private readonly store: string) {
|
||||
|
||||
@@ -26,19 +26,19 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
private readonly BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
constructor(@ILogService private logService: ILogService) {
|
||||
constructor(@ILogService private readonly logService: ILogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
//#region File System Provider
|
||||
|
||||
private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
|
||||
private readonly _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
|
||||
readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event;
|
||||
|
||||
private _onWillActivateFileSystemProvider = this._register(new Emitter<IFileSystemProviderActivationEvent>());
|
||||
private readonly _onWillActivateFileSystemProvider = this._register(new Emitter<IFileSystemProviderActivationEvent>());
|
||||
readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event;
|
||||
|
||||
private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter<IFileSystemProviderCapabilitiesChangeEvent>());
|
||||
private readonly _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter<IFileSystemProviderCapabilitiesChangeEvent>());
|
||||
readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event;
|
||||
|
||||
private readonly provider = new Map<string, IFileSystemProvider>();
|
||||
@@ -146,10 +146,10 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
//#endregion
|
||||
|
||||
private _onDidRunOperation = this._register(new Emitter<FileOperationEvent>());
|
||||
private readonly _onDidRunOperation = this._register(new Emitter<FileOperationEvent>());
|
||||
readonly onDidRunOperation = this._onDidRunOperation.event;
|
||||
|
||||
private _onError = this._register(new Emitter<Error>());
|
||||
private readonly _onError = this._register(new Emitter<Error>());
|
||||
readonly onError = this._onError.event;
|
||||
|
||||
//#region File Metadata Resolving
|
||||
@@ -881,10 +881,10 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
//#region File Watching
|
||||
|
||||
private _onDidFilesChange = this._register(new Emitter<FileChangesEvent>());
|
||||
private readonly _onDidFilesChange = this._register(new Emitter<FileChangesEvent>());
|
||||
readonly onDidFilesChange = this._onDidFilesChange.event;
|
||||
|
||||
private activeWatchers = new Map<string, { disposable: IDisposable, count: number }>();
|
||||
private readonly activeWatchers = new Map<string, { disposable: IDisposable, count: number }>();
|
||||
|
||||
watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable {
|
||||
let watchDisposed = false;
|
||||
@@ -950,7 +950,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
//#region Helpers
|
||||
|
||||
private writeQueues: Map<string, Queue<void>> = new Map();
|
||||
private readonly writeQueues: Map<string, Queue<void>> = new Map();
|
||||
|
||||
private ensureWriteQueue(provider: IFileSystemProvider, resource: URI): Queue<void> {
|
||||
const { extUri } = this.getExtUri(provider);
|
||||
|
||||
@@ -15,7 +15,7 @@ export class DiskFileSystemProvider extends NodeDiskFileSystemProvider {
|
||||
|
||||
constructor(
|
||||
logService: ILogService,
|
||||
private electronService: IElectronService,
|
||||
private readonly electronService: IElectronService,
|
||||
options?: IDiskFileSystemProviderOptions
|
||||
) {
|
||||
super(logService, options);
|
||||
|
||||
@@ -46,7 +46,10 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
private readonly BUFFER_SIZE = this.options?.bufferSize || 64 * 1024;
|
||||
|
||||
constructor(private logService: ILogService, private options?: IDiskFileSystemProviderOptions) {
|
||||
constructor(
|
||||
private readonly logService: ILogService,
|
||||
private readonly options?: IDiskFileSystemProviderOptions
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -198,9 +201,9 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
}
|
||||
}
|
||||
|
||||
private mapHandleToPos: Map<number, number> = new Map();
|
||||
private readonly mapHandleToPos: Map<number, number> = new Map();
|
||||
|
||||
private writeHandles: Set<number> = new Set();
|
||||
private readonly writeHandles: Set<number> = new Set();
|
||||
private canFlush: boolean = true;
|
||||
|
||||
async open(resource: URI, opts: FileOpenOptions): Promise<number> {
|
||||
@@ -502,14 +505,14 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
//#region File Watching
|
||||
|
||||
private _onDidWatchErrorOccur = this._register(new Emitter<string>());
|
||||
private readonly _onDidWatchErrorOccur = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur = this._onDidWatchErrorOccur.event;
|
||||
|
||||
private _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidChangeFile = this._onDidChangeFile.event;
|
||||
|
||||
private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined;
|
||||
private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
|
||||
private readonly recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
|
||||
private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));
|
||||
|
||||
private recursiveWatcherLogLevelListener: IDisposable | undefined;
|
||||
|
||||
@@ -179,6 +179,8 @@ function toWorkbenchListOptions<T>(options: IListOptions<T>, configurationServic
|
||||
}
|
||||
};
|
||||
|
||||
result.smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
|
||||
return [result, disposables];
|
||||
}
|
||||
|
||||
@@ -193,7 +195,6 @@ export interface IWorkbenchListOptions<T> extends IWorkbenchListOptionsUpdate, I
|
||||
export class WorkbenchList<T> extends List<T> {
|
||||
|
||||
readonly contextKeyService: IContextKeyService;
|
||||
private readonly configurationService: IConfigurationService;
|
||||
private readonly themeService: IThemeService;
|
||||
|
||||
private listHasSelectionOrFocus: IContextKey<boolean>;
|
||||
@@ -231,7 +232,6 @@ export class WorkbenchList<T> extends List<T> {
|
||||
this.disposables.add(workbenchListOptionsDisposable);
|
||||
|
||||
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
|
||||
this.configurationService = configurationService;
|
||||
this.themeService = themeService;
|
||||
|
||||
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
|
||||
@@ -265,8 +265,25 @@ export class WorkbenchList<T> extends List<T> {
|
||||
|
||||
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
|
||||
}));
|
||||
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
|
||||
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
let options: IListOptionsUpdate = {};
|
||||
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
options = { ...options, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
options = { ...options, smoothScrolling };
|
||||
}
|
||||
if (Object.keys(options).length > 0) {
|
||||
this.updateOptions(options);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
updateOptions(options: IWorkbenchListOptionsUpdate): void {
|
||||
@@ -292,18 +309,6 @@ export class WorkbenchList<T> extends List<T> {
|
||||
this._styler = attachListStyler(this, this.themeService, styles);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
|
||||
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
|
||||
}
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = this.configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
this.updateOptions({ horizontalScrolling });
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get useAltAsMultipleSelectionModifier(): boolean {
|
||||
return this._useAltAsMultipleSelectionModifier;
|
||||
}
|
||||
@@ -316,7 +321,6 @@ export interface IWorkbenchPagedListOptions<T> extends IWorkbenchListOptionsUpda
|
||||
export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
|
||||
readonly contextKeyService: IContextKeyService;
|
||||
private readonly configurationService: IConfigurationService;
|
||||
|
||||
private readonly disposables: DisposableStore;
|
||||
|
||||
@@ -350,7 +354,6 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
this.disposables.add(workbenchListOptionsDisposable);
|
||||
|
||||
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
|
||||
this.configurationService = configurationService;
|
||||
this.horizontalScrolling = options.horizontalScrolling;
|
||||
|
||||
const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
|
||||
@@ -365,17 +368,23 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.disposables.add(this.configurationService.onDidChangeConfiguration(e => {
|
||||
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
|
||||
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
|
||||
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
|
||||
}
|
||||
|
||||
let options: IListOptionsUpdate = {};
|
||||
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = this.configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
this.updateOptions({ horizontalScrolling });
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
options = { ...options, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
options = { ...options, smoothScrolling };
|
||||
}
|
||||
if (Object.keys(options).length > 0) {
|
||||
this.updateOptions(options);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -836,7 +845,8 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
|
||||
horizontalScrolling,
|
||||
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService),
|
||||
additionalScrollHeight,
|
||||
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements
|
||||
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
|
||||
expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick'
|
||||
} as TOptions
|
||||
};
|
||||
}
|
||||
@@ -933,6 +943,9 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
newOptions = { ...newOptions, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(openModeSettingKey)) {
|
||||
newOptions = { ...newOptions, expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick' };
|
||||
}
|
||||
if (Object.keys(newOptions).length > 0) {
|
||||
tree.updateOptions(newOptions);
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
|
||||
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
if (this.workspaceStoragePath === SQLiteStorageDatabase.IN_MEMORY_PATH) {
|
||||
return Promise.resolve(); // no migration needed if running in memory
|
||||
return; // no migration needed if running in memory
|
||||
}
|
||||
|
||||
// Close workspace DB to be able to copy
|
||||
|
||||
@@ -219,8 +219,14 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
|
||||
this.telemetryService.publicLog2<{ code: string }, AutoSyncErrorClassification>(`autosync/error`, { code: userDataSyncError.code });
|
||||
}
|
||||
|
||||
// Turned off from another device or session got expired
|
||||
if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) {
|
||||
// Session got expired
|
||||
if (userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) {
|
||||
await this.turnOff(false, true /* force soft turnoff on error */);
|
||||
this.logService.info('Auto Sync: Turned off sync because current session is expired');
|
||||
}
|
||||
|
||||
// Turned off from another device
|
||||
if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff) {
|
||||
await this.turnOff(false, true /* force soft turnoff on error */);
|
||||
this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud');
|
||||
}
|
||||
|
||||
@@ -156,8 +156,7 @@ export class WebviewProtocolProvider extends Disposable {
|
||||
rewriteUri = (uri) => {
|
||||
if (metadata.remoteConnectionData) {
|
||||
if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === REMOTE_HOST_SCHEME)) {
|
||||
const scheme = metadata.remoteConnectionData.host === 'localhost' || metadata.remoteConnectionData.host === '127.0.0.1' ? 'http' : 'https';
|
||||
return URI.parse(`${scheme}://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({
|
||||
return URI.parse(`http://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({
|
||||
path: '/vscode-remote-resource',
|
||||
query: `tkn=${metadata.remoteConnectionData.connectionToken}&path=${encodeURIComponent(uri.path)}`,
|
||||
});
|
||||
|
||||
8
src/vs/vscode.d.ts
vendored
8
src/vs/vscode.d.ts
vendored
@@ -1734,8 +1734,8 @@ declare module 'vscode' {
|
||||
/**
|
||||
* Dialog title.
|
||||
*
|
||||
* Depending on the underlying operating system this parameter might be ignored, since some
|
||||
* systems do not present title on open dialogs.
|
||||
* This parameter might be ignored, as not all operating systems display a title on open dialogs
|
||||
* (for example, macOS).
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
@@ -1769,8 +1769,8 @@ declare module 'vscode' {
|
||||
/**
|
||||
* Dialog title.
|
||||
*
|
||||
* Depending on the underlying operating system this parameter might be ignored, since some
|
||||
* systems do not present title on save dialogs.
|
||||
* This parameter might be ignored, as not all operating systems display a title on save dialogs
|
||||
* (for example, macOS).
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
13
src/vs/vscode.proposed.d.ts
vendored
13
src/vs/vscode.proposed.d.ts
vendored
@@ -830,12 +830,11 @@ declare module 'vscode' {
|
||||
noDebug?: boolean;
|
||||
|
||||
/**
|
||||
* Controls if the debug session created will be compacted with the parent in the CALL STACK view.
|
||||
* Compact with the parent is only done if the session is the only child of it's parent session.
|
||||
* Default is to compact.
|
||||
*
|
||||
* Controls if the debug session's parent session is shown in the CALL STACK view even if it has only a single child.
|
||||
* By default, the debug session will never hide its parent.
|
||||
* If compact is true, debug sessions with a single child are hidden in the CALL STACK view to make the tree more compact.
|
||||
*/
|
||||
noCompact?: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
// deprecated debug API
|
||||
@@ -1053,7 +1052,9 @@ declare module 'vscode' {
|
||||
|
||||
export interface TerminalLinkProvider<T extends TerminalLink = TerminalLink> {
|
||||
/**
|
||||
* Provide terminal links for the given context.
|
||||
* Provide terminal links for the given context. Note that this can be called multiple times
|
||||
* even before previous calls resolve, make sure to not share global objects (eg. `RegExp`)
|
||||
* that could have problems when asynchronous usage may overlap.
|
||||
* @param context Information about what links are being provided for.
|
||||
*/
|
||||
provideTerminalLinks(context: TerminalLinkContext): ProviderResult<T[]>
|
||||
|
||||
@@ -232,7 +232,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
noDebug: options.noDebug,
|
||||
parentSession: this.getSession(options.parentSessionID),
|
||||
repl: options.repl,
|
||||
noCompact: options.noCompact
|
||||
compact: options.compact
|
||||
};
|
||||
return this.debugService.startDebugging(launch, nameOrConfig, debugOptions).then(success => {
|
||||
return success;
|
||||
|
||||
@@ -30,12 +30,14 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
|
||||
$setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined, accessibilityInformation: IAccessibilityInformation): void {
|
||||
// if there are icons in the text use the tooltip for the aria label
|
||||
let ariaLabel: string;
|
||||
let role: string | undefined = undefined;
|
||||
if (accessibilityInformation) {
|
||||
ariaLabel = accessibilityInformation.label;
|
||||
role = accessibilityInformation.role;
|
||||
} else {
|
||||
ariaLabel = text ? text.replace(MainThreadStatusBar.CODICON_REGEXP, (_match, codiconName) => codiconName) : '';
|
||||
}
|
||||
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel };
|
||||
const entry: IStatusbarEntry = { text, tooltip, command, color, ariaLabel, role };
|
||||
|
||||
if (typeof priority === 'undefined') {
|
||||
priority = 0;
|
||||
|
||||
@@ -368,7 +368,7 @@ namespace TaskDTO {
|
||||
|
||||
const label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
|
||||
const definition = TaskDefinitionDTO.to(task.definition, executeOnly)!;
|
||||
const id = `${task.source.extensionId}.${definition._key}`;
|
||||
const id = (CustomExecutionDTO.is(task.execution!) && task._id) ? task._id : `${task.source.extensionId}.${definition._key}`;
|
||||
const result: ContributedTask = new ContributedTask(
|
||||
id, // uuidMap.getUUID(identifier)
|
||||
source,
|
||||
@@ -534,20 +534,47 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $executeTask(value: TaskDTO): Promise<TaskExecutionDTO> {
|
||||
// Passing in a TaskHandleDTO will cause the task to get re-resolved, which is important for tasks are coming from the core,
|
||||
// such as those gotten from a fetchTasks, since they can have missing configuration properties.
|
||||
public $executeTask(value: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO> {
|
||||
return new Promise<TaskExecutionDTO>((resolve, reject) => {
|
||||
const task = TaskDTO.to(value, this._workspaceContextServer, true)!;
|
||||
this._taskService.run(task).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
const result: TaskExecutionDTO = {
|
||||
id: task._id,
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
resolve(result);
|
||||
if (TaskHandleDTO.is(value)) {
|
||||
const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder));
|
||||
if (workspaceFolder) {
|
||||
this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => {
|
||||
if (!task) {
|
||||
reject(new Error('Task not found'));
|
||||
} else {
|
||||
this._taskService.run(task).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
const result: TaskExecutionDTO = {
|
||||
id: value.id,
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
resolve(result);
|
||||
}
|
||||
}, (_error) => {
|
||||
reject(new Error('Task not found'));
|
||||
});
|
||||
} else {
|
||||
reject(new Error('No workspace folder'));
|
||||
}
|
||||
} else {
|
||||
const task = TaskDTO.to(value, this._workspaceContextServer, true)!;
|
||||
this._taskService.run(task).then(undefined, reason => {
|
||||
// eat the error, it has already been surfaced to the user and we don't care about it here
|
||||
});
|
||||
const result: TaskExecutionDTO = {
|
||||
id: task._id,
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public $customExecutionComplete(id: string, result?: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this._taskService.getActiveTasks().then((tasks) => {
|
||||
|
||||
@@ -164,8 +164,8 @@ export type TreeItemHandle = string;
|
||||
// {{SQL CARBON EDIT}}
|
||||
export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
private readonly itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
|
||||
private hasResolve: Promise<boolean>;
|
||||
protected readonly itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>(); // {{SQL CARBON EDIT}} For use by Component Tree View
|
||||
protected hasResolve: Promise<boolean>; // {{SQL CARBON EDIT}} For use by Component Tree View
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
constructor(protected readonly treeViewId: string,
|
||||
@@ -219,16 +219,23 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
return this.itemsMap.size === 0;
|
||||
}
|
||||
|
||||
private async postGetChildren(elements: ITreeItem[]): Promise<ResolvableTreeItem[]> {
|
||||
const result: ResolvableTreeItem[] = [];
|
||||
protected async postGetChildren(elements: ITreeItem[]): Promise<ITreeItem[]> { // {{SQL CARBON EDIT}} For use by Component Tree View
|
||||
const result: ITreeItem[] = []; //{{SQL CARBON EDIT}}
|
||||
const hasResolve = await this.hasResolve;
|
||||
if (elements) {
|
||||
for (const element of elements) {
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
// {{SQL CARBON EDIT}} We rely on custom properties on the tree items in a number of places so creating a new item here was
|
||||
// {{SQL CARBON EDIT}} clearing those. Revert to old behavior if the provider doesn't support a resolve (our normal case)
|
||||
if (hasResolve) {
|
||||
const resolvable = new ResolvableTreeItem(element, hasResolve ? () => {
|
||||
return this._proxy.$resolve(this.treeViewId, element.handle);
|
||||
} : undefined);
|
||||
this.itemsMap.set(element.handle, resolvable);
|
||||
result.push(resolvable);
|
||||
} else {
|
||||
this.itemsMap.set(element.handle, element);
|
||||
result.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user