diff --git a/README.md b/README.md index 5b1b1ad4..43504c51 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,14 @@ Please use a dedicated extension like [blamer-vs](https://marketplace.visualstud ## Experimental -### * SVN Status in File Explorer (See #34) +### * SVN Status in File Explorer (See [#34](https://github.com/JohnstonCode/svn-scm/issues/34)) How to enable: * Open the file: `\resources\app\product.json` * Find `extensionAllowedProposedApi` * Append `"johnstoncode.svn-scm"` in the array Example: -```json +```js // FROM { "extensionAllowedProposedApi": [ @@ -91,6 +91,7 @@ Here is a table of settings with their default values. To change any of these, a |Config|Description|Default| |-|-|-| |`svn.enabled`|Whether svn is enabled|`true`| +|`svn.enableProposedApi`|Allow usage of proposed APIs of VSCode. set 'product' to auto-edit product.json, set 'argument' to allow with start argument, set 'none' to not prompt|`null`| |`svn.autorefresh`|Whether auto refreshing is enabled|`true`| |`svn.decorations.enabled`|Controls if SVN contributes colors and badges to the explorer and the open (VSCode \>= 1.18 with proposed-api)|`true`| |`svn.path`|Path to the svn executable|`null`| @@ -126,3 +127,5 @@ Here is a table of settings with their default values. To change any of these, a |`svn.remoteChanges.checkFrequency`|Set the interval in seconds to check changed files on remote repository and show in statusbar. 0 to disable|`300`| |`svn.sourceControl.hideUnversioned`|Hide unversioned files in Source Control UI|`false`| |`svn.refresh.remoteChanges`|Refresh remote changes on refresh command|`false`| +|`svn.sourceControl.changesLeftClick`|Set left click functionality on changes resource state|`"open diff"`| +|`svn.gravatars.enabled`|Use garavatar icons in log viewers|`true`| diff --git a/package.json b/package.json index 11757d0c..bf64165f 100644 --- a/package.json +++ b/package.json @@ -913,6 +913,20 @@ "description": "Whether svn is enabled", "default": true }, + "svn.enableProposedApi": { + "type": [ + "string", + "null" + ], + "enum": [ + null, + "product", + "argument", + "none" + ], + "description": "Allow usage of proposed APIs of VSCode. set 'product' to auto-edit product.json, set 'argument' to allow with start argument, set 'none' to not prompt", + "default": null + }, "svn.autorefresh": { "type": "boolean", "description": "Whether auto refreshing is enabled", @@ -1152,4 +1166,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 28e6b6df..7ba4b422 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { ItemLogProvider } from "./historyView/itemLogProvider"; import { RepoLogProvider } from "./historyView/repoLogProvider"; import * as messages from "./messages"; import { Model } from "./model"; +import { checkProposedApi } from "./proposed"; import { Svn } from "./svn"; import { SvnContentProvider } from "./svnContentProvider"; import { SvnFinder } from "./svnFinder"; @@ -79,6 +80,8 @@ async function init( toDisposable(() => svn.onOutput.removeListener("log", onOutput)) ); disposables.push(toDisposable(messages.dispose)); + + checkProposedApi(); } async function _activate(context: ExtensionContext, disposables: Disposable[]) { diff --git a/src/fs/access.ts b/src/fs/access.ts new file mode 100644 index 00000000..41491c9b --- /dev/null +++ b/src/fs/access.ts @@ -0,0 +1,10 @@ +import { access as fsAccess } from "original-fs"; + +export function access( + path: string, + mode: number | undefined +): Promise { + return new Promise((resolve, _reject) => { + fsAccess(path, mode, err => (err ? resolve(false) : resolve(true))); + }); +} diff --git a/src/fs/index.ts b/src/fs/index.ts index 67465859..168bda99 100644 --- a/src/fs/index.ts +++ b/src/fs/index.ts @@ -1,3 +1,4 @@ +export { access } from "./access"; export { exists } from "./exists"; export { lstat } from "./lstat"; export { mkdir } from "./mkdir"; diff --git a/src/helpers/configuration.ts b/src/helpers/configuration.ts index aff459a5..8044bb6f 100644 --- a/src/helpers/configuration.ts +++ b/src/helpers/configuration.ts @@ -2,6 +2,7 @@ import { ConfigurationChangeEvent, + ConfigurationTarget, Event, EventEmitter, workspace, @@ -37,8 +38,12 @@ class Configuration { return this.configuration.get(section, defaultValue!); } - public update(section: string, value: any): Thenable { - return this.configuration.update(section, value); + public update( + section: string, + value: any, + configurationTarget?: ConfigurationTarget | boolean + ): Thenable { + return this.configuration.update(section, value, configurationTarget); } public inspect(section: string) { diff --git a/src/model.ts b/src/model.ts index a5e00e8a..cb89c03a 100644 --- a/src/model.ts +++ b/src/model.ts @@ -87,6 +87,13 @@ export class Model implements IDisposable { })() as unknown) as Model; } + public openRepositoriesSorted(): IOpenRepository[] { + // Sort by path length (First external and ignored over root) + return this.openRepositories.sort( + (a, b) => b.repository.workspaceRoot.length - a.repository.workspaceRoot.length + ); + } + private onDidChangeConfiguration(): void { const enabled = configuration.get("enabled") === true; @@ -332,7 +339,7 @@ export class Model implements IDisposable { } if (hint instanceof Uri) { - return this.openRepositories.find(liveRepository => { + return this.openRepositoriesSorted().find(liveRepository => { if ( !isDescendant(liveRepository.repository.workspaceRoot, hint.fsPath) ) { @@ -379,12 +386,7 @@ export class Model implements IDisposable { public async getRepositoryFromUri(uri: Uri): Promise { - // Sort by path length (First external and ignored over root) - const open = this.openRepositories.sort( - (a, b) => b.repository.workspaceRoot.length - a.repository.workspaceRoot.length - ); - - for (const liveRepository of open) { + for (const liveRepository of this.openRepositoriesSorted()) { const repository = liveRepository.repository; // Ignore path is not child (fix for multiple externals) diff --git a/src/proposed.ts b/src/proposed.ts new file mode 100644 index 00000000..d18fac43 --- /dev/null +++ b/src/proposed.ts @@ -0,0 +1,123 @@ +import * as fs from "original-fs"; +import { ConfigurationTarget, env, window } from "vscode"; +import { access, exists, writeFile } from "./fs"; +import { configuration } from "./helpers/configuration"; +import { hasSupportToDecorationProvider } from "./util"; + +enum ProposedType { + PRODUCT = "product", + ARGUMENT = "argument", + NONE = "none", +} + +export async function checkProposedApi() { + + if (hasSupportToDecorationProvider()) { + return; + } + + let status: ProposedType | null | undefined = null; + status = configuration.get("enableProposedApi", null); + + if (!status) { + status = await promptProposedApi(); + } + + try { + setProposedApi(status); + } catch (error) { + console.error(error); + await window.showErrorMessage("Failed to configure proposed features for SVN"); + } +} + +async function promptProposedApi() { + const product = "Yes, edit product.json"; + const argument = "Yes, with start argument"; + const none = "No"; + const choice = await window.showWarningMessage( + `Would you like to enable proposed features for SVN? + More info [here](https://github.com/JohnstonCode/svn-scm#experimental)`, + product, + argument, + none + ); + + switch (choice) { + case product: + return ProposedType.PRODUCT; + case argument: + return ProposedType.ARGUMENT; + case none: + return ProposedType.NONE; + } + + return undefined; +} + +export async function setProposedApi(status?: ProposedType) { + switch (status) { + case ProposedType.PRODUCT: + enableProposedProduct(); + break; + case ProposedType.ARGUMENT: + enableProposedArgument(); + break; + case ProposedType.NONE: + break; + } + + if (status) { + configuration.update("enableProposedApi", status, ConfigurationTarget.Global); + } +} + +async function enableProposedProduct() { + const productPath = env.appRoot + "/product.json"; + + if (!await exists(productPath)) { + window.showErrorMessage(`Can't find the "product.json" of VSCode.`); + return; + } + if (!await access(productPath, fs.constants.W_OK)) { + window.showErrorMessage(`The "product.json" of VSCode is not writable. + Please, append "johnstoncode.svn-scm" on "extensionAllowedProposedApi" array`); + return; + } + + const productJson = require(productPath) as { + extensionAllowedProposedApi: string[], + [key: string]: any; + }; + + productJson.extensionAllowedProposedApi = productJson.extensionAllowedProposedApi || []; + + if (productJson.extensionAllowedProposedApi.includes("johnstoncode.svn-scm")) { + return; + } + productJson.extensionAllowedProposedApi.push("johnstoncode.svn-scm"); + + await writeFile(productPath, JSON.stringify(productJson, null, 2)); + + const message = "SVN proposed features enabled, please restart VSCode"; + + window.showInformationMessage(message); +} + +async function enableProposedArgument() { + const packagePath = __dirname + "/../package.json"; + + const packageJson = require(packagePath); + + if (!packageJson || packageJson.enableProposedApi !== false) { + return; + } + + packageJson.enableProposedApi = true; + await writeFile(packagePath, JSON.stringify(packageJson, null, 2)); + + const message = `SVN proposed features enabled, + please close the VSCode and run with: --enable-proposed-api johnstoncode.svn-scm`; + + window.showInformationMessage(message); +}