diff --git a/.vscode/launch.json b/.vscode/launch.json index a08a3231806..e35d52a2644 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,11 +7,17 @@ "name": "Unit Tests", "protocol": "legacy", "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeExecutable": "${execPath}", + "runtimeExecutable": "${workspaceRoot}/.build/electron/Code - OSS.app/Contents/MacOS/Electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/.build/electron/Code - OSS.exe" + }, + "linux": { + "runtimeExecutable": "${workspaceRoot}/.build/electron/code-oss" + }, "stopOnEntry": false, "args": [ "--timeout", - "999999" + "2000" ], "cwd": "${workspaceRoot}", "env": { @@ -20,7 +26,8 @@ "sourceMaps": true, "outFiles": [ "${workspaceRoot}/out/**/*.js" - ] + ], + "preLaunchTask": "electron" }, { "type": "node", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 88e68fde6d5..a52055b9636 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -47,7 +47,7 @@ ], "severity": "warning", "pattern": { - "regexp": "(.*)\\[(\\d+),\\s(\\d+)\\]:\\s(.*)$", // (.*)\[(\d+), (\d+)\]: (.*) + "regexp": "(.*)\\[(\\d+),\\s(\\d+)\\]:\\s(.*)$", // (.*)\[(\d+), (\d+)\]: (.*) "file": 1, "line": 2, "column": 3, @@ -62,6 +62,13 @@ ], "showOutput": "always", "isTestCommand": true + }, + { + "taskName": "electron", + "args": [ + "--no-color" + ], + "showOutput": "never" } ] } \ No newline at end of file diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 731724b8066..390a3f0814b 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -40,8 +40,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.10.6' }, - { name: 'ms-vscode.node-debug2', version: '1.9.13' } + { name: 'ms-vscode.node-debug', version: '1.10.12' }, + { name: 'ms-vscode.node-debug2', version: '1.10.0' } ]; const vscodeEntryPoints = _.flatten([ diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index cb232748f21..427e4e52538 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -44,6 +44,7 @@ function buildWin32Setup(cb) { NameVersion: product.win32NameVersion, ExeBasename: product.nameShort, RegValueName: product.win32RegValueName, + ShellNameShort: product.win32ShellNameShort, AppMutex: product.win32MutexName, AppId: product.win32AppId, AppUserId: product.win32AppUserModelId, diff --git a/build/win32/code.iss b/build/win32/code.iss index bdd32915aa9..1eb3241d9f4 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -772,16 +772,16 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: s Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin')) -Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufiles Root: HKCU; Subkey: "SOFTWARE\Classes\*\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: addcontextmenufiles -Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\directory\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\directory\background\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#NameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open with {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: HKCU; Subkey: "SOFTWARE\Classes\Drive\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders diff --git a/extensions/git/package.json b/extensions/git/package.json index 0301d7f471d..5c229c116f6 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -21,6 +21,15 @@ }, "contributes": { "commands": [ + { + "command": "git.init", + "title": "%command.init%", + "category": "Git", + "icon": { + "light": "resources/icons/light/git.svg", + "dark": "resources/icons/dark/git.svg" + } + }, { "command": "git.refresh", "title": "%command.refresh%", @@ -191,189 +200,296 @@ } ], "menus": { + "commandPalette": [ + { + "command": "git.init", + "when": "scmProvider == git && scmProviderState == norepo" + }, + { + "command": "git.refresh", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.openChange", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.openFile", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.stageAll", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.unstage", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.unstageAll", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.cleanAll", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.commit", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.commitStaged", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.commitStagedSigned", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.commitAll", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.commitAllSigned", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.undoCommit", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.checkout", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.branch", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.pull", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.pullRebase", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.push", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.pushTo", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.sync", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.publish", + "when": "scmProvider == git && scmProviderState == idle" + }, + { + "command": "git.showOutput", + "when": "scmProvider == git && scmProviderState == idle" + } + ], "scm/title": [ + { + "command": "git.init", + "group": "navigation", + "when": "scmProvider == git && scmProviderState == norepo" + }, { "command": "git.commit", "group": "navigation", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.refresh", "group": "navigation", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.sync", "group": "1_sync", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.pull", "group": "1_sync", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.pullRebase", "group": "1_sync", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.push", "group": "1_sync", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.pushTo", "group": "1_sync", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.publish", "group": "2_publish", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.commitStaged", "group": "3_commit", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.commitStagedSigned", "group": "3_commit", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.commitAll", "group": "3_commit", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.commitAllSigned", "group": "3_commit", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.undoCommit", "group": "3_commit", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.unstageAll", "group": "4_stage", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.cleanAll", "group": "4_stage", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" }, { "command": "git.showOutput", "group": "5_output", - "when": "scmProvider == git" + "when": "scmProvider == git && scmProviderState == idle" } ], "scm/resourceGroup/context": [ { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == merge", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == merge", "group": "1_modification" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == merge", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == merge", "group": "inline" }, { "command": "git.unstageAll", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "1_modification" }, { "command": "git.unstageAll", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "inline" }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "inline" }, { "command": "git.stageAll", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "inline" } ], "scm/resource/context": [ { "command": "git.stage", - "when": "scmProvider == git && scmResourceGroup == merge", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == merge", "group": "1_modification" }, { "command": "git.stage", - "when": "scmProvider == git && scmResourceGroup == merge", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == merge", "group": "inline" }, { "command": "git.openChange", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "navigation" }, { "command": "git.openFile", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "navigation" }, { "command": "git.unstage", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "1_modification" }, { "command": "git.unstage", - "when": "scmProvider == git && scmResourceGroup == index", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == index", "group": "inline" }, { "command": "git.openChange", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "navigation" }, { "command": "git.openFile", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "navigation" }, { "command": "git.stage", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.clean", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.clean", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "inline" }, { "command": "git.stage", - "when": "scmProvider == git && scmResourceGroup == workingTree", + "when": "scmProvider == git && scmProviderState == idle && scmResourceGroup == workingTree", "group": "inline" } ], diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 97bad65ecde..45468212857 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -1,4 +1,5 @@ { + "command.init": "Initialize Git Repository", "command.refresh": "Refresh", "command.openChange": "Open Change", "command.openFile": "Open File", diff --git a/extensions/git/resources/icons/dark/git.svg b/extensions/git/resources/icons/dark/git.svg new file mode 100644 index 00000000000..c08b1c2e403 --- /dev/null +++ b/extensions/git/resources/icons/dark/git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/git/resources/icons/light/git.svg b/extensions/git/resources/icons/light/git.svg new file mode 100644 index 00000000000..d1049a44d0d --- /dev/null +++ b/extensions/git/resources/icons/light/git.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 10af98114e3..b2657bb8b9d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -113,13 +113,13 @@ export class CommandCenter { const right = this.getRightResource(resource); const title = this.getTitle(resource); - if (!left) { - if (!right) { - // TODO - console.error('oh no'); - return; - } + if (!right) { + // TODO + console.error('oh no'); + return; + } + if (!left) { return await commands.executeCommand('vscode.open', right); } @@ -130,11 +130,11 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: - return resource.uri.with({ scheme: 'git', query: 'HEAD' }); + return resource.original.with({ scheme: 'git', query: 'HEAD' }); case Status.MODIFIED: - const uriString = resource.uri.toString(); - const [indexStatus] = this.model.indexGroup.resources.filter(r => r.uri.toString() === uriString); + const uriString = resource.original.toString(); + const [indexStatus] = this.model.indexGroup.resources.filter(r => r.original.toString() === uriString); if (indexStatus) { return resource.uri.with({ scheme: 'git' }); @@ -149,6 +149,8 @@ export class CommandCenter { case Status.INDEX_MODIFIED: case Status.INDEX_ADDED: case Status.INDEX_COPIED: + return resource.uri.with({ scheme: 'git' }); + case Status.INDEX_RENAMED: return resource.uri.with({ scheme: 'git' }); @@ -159,6 +161,15 @@ export class CommandCenter { case Status.MODIFIED: case Status.UNTRACKED: case Status.IGNORED: + const uriString = resource.uri.toString(); + const [indexStatus] = this.model.indexGroup.resources.filter(r => r.uri.toString() === uriString); + + if (indexStatus && indexStatus.rename) { + return indexStatus.rename; + } + + return resource.uri; + case Status.BOTH_MODIFIED: return resource.uri; } @@ -179,6 +190,11 @@ export class CommandCenter { return ''; } + @command('git.init') + async init(): Promise { + await this.model.init(); + } + @command('git.openFile') async openFile(uri: Uri): Promise { const scmResource = resolveGitResource(uri); diff --git a/extensions/git/src/commit.ts b/extensions/git/src/commit.ts index bcc06c3d7c3..a879a8ed414 100644 --- a/extensions/git/src/commit.ts +++ b/extensions/git/src/commit.ts @@ -6,7 +6,6 @@ 'use strict'; import { workspace, window, languages, Disposable, Uri, HoverProvider, Hover, TextEditor, Position, TextDocument, Range, TextEditorDecorationType, WorkspaceEdit } from 'vscode'; -import { Model } from './model'; import { filterEvent } from './util'; import * as nls from 'vscode-nls'; @@ -22,7 +21,7 @@ interface Diagnostic { message: string; } -// TODO@Joao: hover dissapears if editor is scrolled +// TODO@Joao: hover disappears if editor is scrolled export class CommitController implements HoverProvider { private visibleTextEditorsDisposable: Disposable; @@ -53,7 +52,7 @@ export class CommitController implements HoverProvider { workspace.applyEdit(edit); } - constructor(private model: Model) { + constructor() { this.visibleTextEditorsDisposable = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditors, this); this.onVisibleTextEditors(window.visibleTextEditors); diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index a0dfec226bf..2e6614936fc 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -6,8 +6,7 @@ 'use strict'; import { workspace, Uri, Disposable, Event, EventEmitter } from 'vscode'; -import * as path from 'path'; -import { Git } from './git'; +import { Model } from './model'; export class GitContentProvider { @@ -18,7 +17,7 @@ export class GitContentProvider { private uris = new Set(); - constructor(private git: Git, private rootPath: string, onGitChange: Event) { + constructor(private model: Model, onGitChange: Event) { this.disposables.push( onGitChange(this.fireChangeEvents, this), workspace.registerTextDocumentContentProvider('git', this) @@ -32,19 +31,10 @@ export class GitContentProvider { } async provideTextDocumentContent(uri: Uri): Promise { - const treeish = uri.query; - const relativePath = path.relative(this.rootPath, uri.fsPath).replace(/\\/g, '/'); - try { - const result = await this.git.exec(this.rootPath, ['show', `${treeish}:${relativePath}`]); - - if (result.exitCode !== 0) { - this.uris.delete(uri); - return ''; - } - + const result = await this.model.show(uri.query, uri); this.uris.add(uri); - return result.stdout; + return result; } catch (err) { this.uris.delete(uri); return ''; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 1adc670f6ae..94a1ff9e339 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -289,6 +289,11 @@ export class Git { return new Repository(this, repository, env); } + async getRepositoryRoot(path: string): Promise { + const result = await this.exec(path, ['rev-parse', '--show-toplevel']); + return result.stdout.trim(); + } + async exec(cwd: string, args: string[], options: any = {}): Promise { options = assign({ cwd }, options || {}); return await this._exec(args, options); @@ -380,7 +385,7 @@ export class Repository { constructor( private _git: Git, - private repository: string, + private repositoryRoot: string, private env: any = {} ) { } @@ -388,8 +393,8 @@ export class Repository { return this._git; } - get path(): string { - return this.repository; + get root(): string { + return this.repositoryRoot; } // TODO@Joao: rename to exec @@ -397,14 +402,14 @@ export class Repository { options.env = assign({}, options.env || {}); options.env = assign(options.env, this.env); - return await this.git.exec(this.repository, args, options); + return await this.git.exec(this.repositoryRoot, args, options); } stream(args: string[], options: any = {}): cp.ChildProcess { options.env = assign({}, options.env || {}); options.env = assign(options.env, this.env); - return this.git.stream(this.repository, args, options); + return this.git.stream(this.repositoryRoot, args, options); } spawn(args: string[], options: any = {}): cp.ChildProcess { @@ -709,11 +714,6 @@ export class Repository { await this.push(); } - async getRoot(): Promise { - const result = await this.run(['rev-parse', '--show-toplevel']); - return result.stdout.trim(); - } - async getStatus(): Promise { const executionResult = await this.run(['status', '-z', '-u']); const status = executionResult.stdout; @@ -861,7 +861,7 @@ export class Repository { .replace(/^~([^\/]*)\//, (_, user) => `${user ? path.join(path.dirname(homedir), user) : homedir}/`); if (!path.isAbsolute(templatePath)) { - templatePath = path.join(this.repository, templatePath); + templatePath = path.join(this.repositoryRoot, templatePath); } const raw = await readfile(templatePath, 'utf8'); diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index a282582e529..9c90d2e1cf0 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -41,17 +41,15 @@ async function init(disposables: Disposable[]): Promise { const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint); const git = new Git({ gitPath: info.path, version: info.version }); - const repository = git.open(rootPath); - const repositoryRoot = await repository.getRoot(); - const model = new Model(repositoryRoot, repository, onWorkspaceChange); + const model = new Model(git, rootPath, onWorkspaceChange); outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path)); git.onOutput(str => outputChannel.append(str), null, disposables); - const commitHandler = new CommitController(model); + const commitHandler = new CommitController(); const commandCenter = new CommandCenter(model, outputChannel); const provider = new GitSCMProvider(model, commandCenter); - const contentProvider = new GitContentProvider(git, rootPath, onGitChange); + const contentProvider = new GitContentProvider(model, onGitChange); const checkoutStatusBar = new CheckoutStatusBar(model); const syncStatusBar = new SyncStatusBar(model); const autoFetcher = new AutoFetcher(model); @@ -66,7 +64,8 @@ async function init(disposables: Disposable[]): Promise { checkoutStatusBar, syncStatusBar, autoFetcher, - mergeDecorator + mergeDecorator, + model ); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index f894ad1998f..41f8a0e88dd 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,8 +6,8 @@ 'use strict'; import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable, window, workspace } from 'vscode'; -import { Repository, Ref, Branch, Remote, PushOptions, Commit } from './git'; -import { anyEvent, eventToPromise, filterEvent, mapEvent } from './util'; +import { Git, Repository, Ref, Branch, Remote, PushOptions, Commit, GitErrorCodes, GitError } from './git'; +import { anyEvent, eventToPromise, filterEvent, mapEvent, EmptyDisposable, combinedDisposable } from './util'; import { memoize, throttle, debounce } from './decorators'; import { watch } from './watch'; import * as path from 'path'; @@ -20,6 +20,12 @@ function getIconUri(iconName: string, theme: string): Uri { return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`)); } +export enum State { + Uninitialized, + Idle, + NotAGitRepository +} + export enum Status { INDEX_MODIFIED, INDEX_ADDED, @@ -43,8 +49,17 @@ export enum Status { export class Resource implements SCMResource { - get uri(): Uri { return this._uri; } + get uri(): Uri { + if (this.rename && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED)) { + return this.rename; + } + + return this._uri; + } + get type(): Status { return this._type; } + get original(): Uri { return this._uri; } + get rename(): Uri | undefined { return this._rename; } private static Icons = { light: { @@ -110,8 +125,8 @@ export class Resource implements SCMResource { return { strikeThrough: this.strikeThrough, light, dark }; } - constructor(private _uri: Uri, private _type: Status) { - + constructor(private _uri: Uri, private _type: Status, private _rename?: Uri) { + // console.log(this); } } @@ -130,7 +145,7 @@ export class MergeGroup extends ResourceGroup { static readonly ID = 'merge'; - constructor(resources: Resource[]) { + constructor(resources: Resource[] = []) { super(MergeGroup.ID, localize('merge changes', "Merge Changes"), resources); } } @@ -139,7 +154,7 @@ export class IndexGroup extends ResourceGroup { static readonly ID = 'index'; - constructor(resources: Resource[]) { + constructor(resources: Resource[] = []) { super(IndexGroup.ID, localize('staged changes', "Staged Changes"), resources); } } @@ -148,7 +163,7 @@ export class WorkingTreeGroup extends ResourceGroup { static readonly ID = 'workingTree'; - constructor(resources: Resource[]) { + constructor(resources: Resource[] = []) { super(WorkingTreeGroup.ID, localize('changes', "Changes"), resources); } } @@ -166,6 +181,8 @@ export enum Operation { Pull = 1 << 9, Push = 1 << 10, Sync = 1 << 11, + Init = 1 << 12, + Show = 1 << 13 } export interface Operations { @@ -202,10 +219,18 @@ export interface CommitOptions { signoff?: boolean; } -export class Model { +export class Model implements Disposable { - private _onDidChange = new EventEmitter(); - readonly onDidChange: Event = this._onDidChange.event; + private _onDidChangeState = new EventEmitter(); + readonly onDidChangeState: Event = this._onDidChangeState.event; + + private _onDidChangeResources = new EventEmitter(); + readonly onDidChangeResources: Event = this._onDidChangeResources.event; + + @memoize + get onDidChange(): Event { + return anyEvent(this.onDidChangeState, this.onDidChangeResources); + } private _onRunOperation = new EventEmitter(); readonly onRunOperation: Event = this._onRunOperation.event; @@ -243,38 +268,6 @@ export class Model { return result; } - private _operations = new OperationsImpl(); - get operations(): Operations { return this._operations; } - - private disposables: Disposable[] = []; - - constructor( - private _repositoryRoot: string, - private repository: Repository, - onWorkspaceChange: Event - ) { - /* We use the native Node `watch` for faster, non debounced events. - * That way we hopefully get the events during the operations we're - * performing, thus sparing useless `git status` calls to refresh - * the model's state. - */ - const gitPath = path.join(_repositoryRoot, '.git'); - const { event, disposable } = watch(gitPath); - const onGitChange = mapEvent(event, ({ filename }) => Uri.file(path.join(gitPath, filename))); - const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.fsPath)); - onRelevantGitChange(this.onFSChange, this, this.disposables); - this.disposables.push(disposable); - - const onNonGitChange = filterEvent(onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath)); - onNonGitChange(this.onFSChange, this, this.disposables); - - this.status(); - } - - get repositoryRoot(): string { - return this._repositoryRoot; - } - private _HEAD: Branch | undefined; get HEAD(): Branch | undefined { return this._HEAD; @@ -290,6 +283,46 @@ export class Model { return this._remotes; } + private _operations = new OperationsImpl(); + get operations(): Operations { return this._operations; } + + private repository: Repository; + + private _state = State.Uninitialized; + get state(): State { return this._state; } + set state(state: State) { + this._state = state; + this._onDidChangeState.fire(state); + + this._HEAD = undefined; + this._refs = []; + this._remotes = []; + this._mergeGroup = new MergeGroup(); + this._indexGroup = new IndexGroup(); + this._workingTreeGroup = new WorkingTreeGroup(); + this._onDidChangeResources.fire(this.resources); + } + + private repositoryDisposable: Disposable = EmptyDisposable; + + constructor( + private git: Git, + private rootPath: string, + private onWorkspaceChange: Event + ) { + this.status(); + } + + @throttle + async init(): Promise { + if (this.state !== State.NotAGitRepository) { + return; + } + + await this.repository.init(); + await this.status(); + } + @throttle async status(): Promise { await this.run(Operation.Status); @@ -389,14 +422,40 @@ export class Model { await this.run(Operation.Sync, () => this.repository.sync()); } - private async run(operation: Operation, fn: () => Promise = () => Promise.resolve()): Promise { + @throttle + async show(ref: string, uri: Uri): Promise { + return await this.run(Operation.Show, async () => { + const relativePath = path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/'); + const result = await this.repository.git.exec(this.repository.root, ['show', `${ref}:${relativePath}`]); + + if (result.exitCode !== 0) { + throw new GitError({ + message: localize('cantshow', "Could not show object"), + exitCode: result.exitCode + }); + } + + return result.stdout; + }); + } + + private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { return window.withScmProgress(async () => { this._operations = this._operations.start(operation); this._onRunOperation.fire(operation); try { - await fn(); + await this.assertIdleState(); + const result = await runOperation(); await this.update(); + return result; + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + this.repositoryDisposable.dispose(); + this.state = State.NotAGitRepository; + } + + throw err; } finally { this._operations = this._operations.end(operation); this._onDidRunOperation.fire(operation); @@ -404,6 +463,37 @@ export class Model { }); } + /* We use the native Node `watch` for faster, non debounced events. + * That way we hopefully get the events during the operations we're + * performing, thus sparing useless `git status` calls to refresh + * the model's state. + */ + private async assertIdleState(): Promise { + if (this.state === State.Idle) { + return; + } + + this.repositoryDisposable.dispose(); + + const disposables: Disposable[] = []; + const repositoryRoot = await this.git.getRepositoryRoot(this.rootPath); + this.repository = this.git.open(repositoryRoot); + + const dotGitPath = path.join(repositoryRoot, '.git'); + const { event, disposable: watcher } = watch(dotGitPath); + disposables.push(watcher); + + const onGitChange = mapEvent(event, ({ filename }) => Uri.file(path.join(dotGitPath, filename))); + const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.fsPath)); + onRelevantGitChange(this.onFSChange, this, disposables); + + const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath)); + onNonGitChange(this.onFSChange, this, disposables); + + this.repositoryDisposable = combinedDisposable(disposables); + this.state = State.Idle; + } + @throttle private async update(): Promise { const status = await this.repository.getStatus(); @@ -434,7 +524,8 @@ export class Model { const merge: Resource[] = []; status.forEach(raw => { - const uri = Uri.file(path.join(this.repositoryRoot, raw.path)); + const uri = Uri.file(path.join(this.repository.root, raw.path)); + const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined; switch (raw.x + raw.y) { case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED)); @@ -454,21 +545,20 @@ export class Model { case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break; case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break; - case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break; + case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED, renameUri)); break; case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break; } switch (raw.y) { - case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break; - case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break; + case 'M': workingTree.push(new Resource(uri, Status.MODIFIED, renameUri)); break; + case 'D': workingTree.push(new Resource(uri, Status.DELETED, renameUri)); break; } }); this._mergeGroup = new MergeGroup(merge); this._indexGroup = new IndexGroup(index); this._workingTreeGroup = new WorkingTreeGroup(workingTree); - - this._onDidChange.fire(this.resources); + this._onDidChangeResources.fire(this.resources); } private onFSChange(uri: Uri): void { @@ -503,4 +593,8 @@ export class Model { await eventToPromise(this.onDidRunOperation); } } + + dispose(): void { + this.repositoryDisposable.dispose(); + } } \ No newline at end of file diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index 3a764d6259c..71dda0dec4c 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -6,17 +6,31 @@ 'use strict'; import { scm, Uri, Disposable, SCMProvider, SCMResourceGroup, Event, ProviderResult, workspace } from 'vscode'; -import { Model, Resource, ResourceGroup } from './model'; +import { Model, Resource, ResourceGroup, State } from './model'; import { CommandCenter } from './commands'; +import { mapEvent } from './util'; export class GitSCMProvider implements SCMProvider { private disposables: Disposable[] = []; get resources(): SCMResourceGroup[] { return this.model.resources; } - get onDidChange(): Event { return this.model.onDidChange; } + + get onDidChange(): Event { + return mapEvent(this.model.onDidChange, () => this.model.resources); + } + get label(): string { return 'Git'; } + get state(): string { + switch (this.model.state) { + case State.Uninitialized: return 'uninitialized'; + case State.Idle: return 'idle'; + case State.NotAGitRepository: return 'norepo'; + default: return ''; + } + } + get count(): number { const countBadge = workspace.getConfiguration('git').get('countBadge'); diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index f3171809317..00dda46a5ad 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -30,9 +30,7 @@ export class CheckoutStatusBar { const HEAD = this.model.HEAD; if (!HEAD) { - this.raw.command = ''; - this.raw.color = 'rgb(100, 100, 100)'; - this.raw.text = 'unknown'; + this.raw.hide(); return; } @@ -48,6 +46,7 @@ export class CheckoutStatusBar { (this.model.workingTreeGroup.resources.length > 0 ? '*' : '') + (this.model.indexGroup.resources.length > 0 ? '+' : '') + (this.model.mergeGroup.resources.length > 0 ? '!' : ''); + this.raw.show(); } dispose(): void { diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index e4940255602..8cc69d1f43c 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -28,6 +28,8 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { return toDisposable(() => dispose(disposables)); } +export const EmptyDisposable = toDisposable(() => null); + export function mapEvent(event: Event, map: (i: I) => O): Event { return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables); } diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 25eef45db19..8664839b234 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -57,7 +57,7 @@ export function activate(context: ExtensionContext) { }; // Create the language client and start the client. - let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions, true); + let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions); let disposable = client.start(); context.subscriptions.push(disposable); client.onReady().then(() => { diff --git a/extensions/json/server/src/jsonServerMain.ts b/extensions/json/server/src/jsonServerMain.ts index 3b06405dcb4..8e1d15f3a55 100644 --- a/extensions/json/server/src/jsonServerMain.ts +++ b/extensions/json/server/src/jsonServerMain.ts @@ -17,7 +17,6 @@ import URI from './utils/uri'; import * as URL from 'url'; import Strings = require('./utils/strings'); import { JSONDocument, JSONSchema, LanguageSettings, getLanguageService } from 'vscode-json-languageservice'; -import { ProjectJSONContribution } from './jsoncontributions/projectJSONContribution'; import { getLanguageModelCache } from './languageModelCache'; import * as nls from 'vscode-nls'; @@ -119,9 +118,7 @@ let schemaRequestService = (uri: string): Thenable => { let languageService = getLanguageService({ schemaRequestService, workspaceContext, - contributions: [ - new ProjectJSONContribution() - ] + contributions: [] }); // The settings interface describes the server relevant settings part diff --git a/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts b/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts deleted file mode 100644 index 427e49efae2..00000000000 --- a/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts +++ /dev/null @@ -1,281 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { MarkedString, CompletionItemKind, CompletionItem, InsertTextFormat } from 'vscode-languageserver'; -import Strings = require('../utils/strings'); -import { XHRResponse, getErrorStatusDescription, xhr } from 'request-light'; -import { JSONWorkerContribution, JSONPath, CompletionsCollector } from 'vscode-json-languageservice'; - -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); - - -const FEED_INDEX_URL = 'https://api.nuget.org/v3/index.json'; -const LIMIT = 30; -const RESOLVE_ID = 'ProjectJSONContribution-'; - -const CACHE_EXPIRY = 1000 * 60 * 5; // 5 minutes - -interface NugetServices { - 'SearchQueryService'?: string; - 'SearchAutocompleteService'?: string; - 'PackageBaseAddress/3.0.0'?: string; - [key: string]: string; -} - -export class ProjectJSONContribution implements JSONWorkerContribution { - - private cachedProjects: { [id: string]: { version: string, description: string, time: number } } = {}; - private cacheSize: number = 0; - private nugetIndexPromise: Thenable; - - public constructor() { - } - - private isProjectJSONFile(resource: string): boolean { - return Strings.endsWith(resource, '/project.json'); - } - - private completeWithCache(id: string, item: CompletionItem): boolean { - let entry = this.cachedProjects[id]; - if (entry) { - if (new Date().getTime() - entry.time > CACHE_EXPIRY) { - delete this.cachedProjects[id]; - this.cacheSize--; - return false; - } - let insertTextValue = item.insertText; - item.detail = entry.version; - item.documentation = entry.description; - item.insertText = insertTextValue.replace(/\$1/, '${1:' + entry.version + '}'); - return true; - } - return false; - } - - private addCached(id: string, version: string, description: string) { - this.cachedProjects[id] = { version, description, time: new Date().getTime() }; - this.cacheSize++; - if (this.cacheSize > 50) { - let currentTime = new Date().getTime(); - for (let id in this.cachedProjects) { - let entry = this.cachedProjects[id]; - if (currentTime - entry.time > CACHE_EXPIRY) { - delete this.cachedProjects[id]; - this.cacheSize--; - } - } - } - } - - private getNugetIndex(): Thenable { - if (!this.nugetIndexPromise) { - this.nugetIndexPromise = this.makeJSONRequest(FEED_INDEX_URL).then(indexContent => { - let services: NugetServices = {}; - if (indexContent && Array.isArray(indexContent.resources)) { - let resources = indexContent.resources; - for (let i = resources.length - 1; i >= 0; i--) { - let type = resources[i]['@type']; - let id = resources[i]['@id']; - if (type && id) { - services[type] = id; - } - } - } - return services; - }); - } - return this.nugetIndexPromise; - } - - private getNugetService(serviceType: string): Thenable { - return this.getNugetIndex().then(services => { - let serviceURL = services[serviceType]; - if (!serviceURL) { - return Promise.reject(localize('json.nugget.error.missingservice', 'NuGet index document is missing service {0}', serviceType)); - } - return serviceURL; - }); - } - - public collectDefaultCompletions(resource: string, result: CompletionsCollector): Thenable { - if (this.isProjectJSONFile(resource)) { - let insertText = JSON.stringify({ - 'version': '${1:1.0.0-*}', - 'dependencies': {}, - 'frameworks': { - 'net461': {}, - 'netcoreapp1.0': {} - } - }, null, '\t'); - result.add({ kind: CompletionItemKind.Class, label: localize('json.project.default', 'Default project.json'), insertText, insertTextFormat: InsertTextFormat.Snippet, documentation: '' }); - } - return null; - } - - private makeJSONRequest(url: string): Thenable { - return xhr({ - url: url - }).then(success => { - if (success.status === 200) { - try { - return JSON.parse(success.responseText); - } catch (e) { - return Promise.reject(localize('json.nugget.error.invalidformat', '{0} is not a valid JSON document', url)); - } - } - return Promise.reject(localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText)); - }, (error: XHRResponse) => { - return Promise.reject(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status))); - }); - } - - public collectPropertyCompletions(resource: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable { - if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['frameworks', '*', 'frameworkAssemblies']))) { - - return this.getNugetService('SearchAutocompleteService').then(service => { - let queryUrl: string; - if (currentWord.length > 0) { - queryUrl = service + '?q=' + encodeURIComponent(currentWord) + '&take=' + LIMIT; - } else { - queryUrl = service + '?take=' + LIMIT; - } - return this.makeJSONRequest(queryUrl).then(resultObj => { - if (Array.isArray(resultObj.data)) { - let results = resultObj.data; - for (let i = 0; i < results.length; i++) { - let name = results[i]; - let insertText = JSON.stringify(name); - if (addValue) { - insertText += ': "$1"'; - if (!isLast) { - insertText += ','; - } - } - let item: CompletionItem = { kind: CompletionItemKind.Property, label: name, insertText: insertText, insertTextFormat: InsertTextFormat.Snippet, filterText: JSON.stringify(name) }; - if (!this.completeWithCache(name, item)) { - item.data = RESOLVE_ID + name; - } - result.add(item); - } - if (results.length === LIMIT) { - result.setAsIncomplete(); - } - } - }, error => { - result.error(error); - }); - }, error => { - result.error(error); - }); - }; - return null; - } - - public collectValueCompletions(resource: string, location: JSONPath, currentKey: string, result: CompletionsCollector): Thenable { - if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies']) || matches(location, ['frameworks', '*', 'dependencies']) || matches(location, ['frameworks', '*', 'frameworkAssemblies']))) { - return this.getNugetService('PackageBaseAddress/3.0.0').then(service => { - let queryUrl = service + currentKey + '/index.json'; - return this.makeJSONRequest(queryUrl).then(obj => { - if (Array.isArray(obj.versions)) { - let results = obj.versions; - for (let i = 0; i < results.length; i++) { - let curr = results[i]; - let name = JSON.stringify(curr); - let label = name; - let documentation = ''; - result.add({ kind: CompletionItemKind.Class, label: label, insertText: name, documentation: documentation }); - } - if (results.length === LIMIT) { - result.setAsIncomplete(); - } - } - }, error => { - result.error(error); - }); - }, error => { - result.error(error); - }); - } - return null; - } - - public getInfoContribution(resource: string, location: JSONPath): Thenable { - if (this.isProjectJSONFile(resource) && (matches(location, ['dependencies', '*']) || matches(location, ['frameworks', '*', 'dependencies', '*']) || matches(location, ['frameworks', '*', 'frameworkAssemblies', '*']))) { - let pack = location[location.length - 1]; - - return this.getNugetService('SearchQueryService').then(service => { - let queryUrl = service + '?q=' + encodeURIComponent(pack) + '&take=' + 5; - return this.makeJSONRequest(queryUrl).then(resultObj => { - let htmlContent: MarkedString[] = []; - htmlContent.push(localize('json.nugget.package.hover', '{0}', pack)); - if (Array.isArray(resultObj.data)) { - let results = resultObj.data; - for (let i = 0; i < results.length; i++) { - let res = results[i]; - this.addCached(res.id, res.version, res.description); - if (res.id === pack) { - if (res.description) { - htmlContent.push(MarkedString.fromPlainText(res.description)); - } - if (res.version) { - htmlContent.push(MarkedString.fromPlainText(localize('json.nugget.version.hover', 'Latest version: {0}', res.version))); - } - break; - } - } - } - return htmlContent; - }, (error) => { - return null; - }); - }, (error) => { - return null; - }); - } - return null; - } - - public resolveSuggestion(item: CompletionItem): Thenable { - if (item.data && Strings.startsWith(item.data, RESOLVE_ID)) { - let pack = item.data.substring(RESOLVE_ID.length); - if (this.completeWithCache(pack, item)) { - return Promise.resolve(item); - } - return this.getNugetService('SearchQueryService').then(service => { - let queryUrl = service + '?q=' + encodeURIComponent(pack) + '&take=' + 10; - return this.makeJSONRequest(queryUrl).then(resultObj => { - let itemResolved = false; - if (Array.isArray(resultObj.data)) { - let results = resultObj.data; - for (let i = 0; i < results.length; i++) { - let curr = results[i]; - this.addCached(curr.id, curr.version, curr.description); - if (curr.id === pack) { - this.completeWithCache(pack, item); - itemResolved = true; - } - } - } - return itemResolved ? item : null; - }); - }); - }; - return null; - } -} - -function matches(segments: JSONPath, pattern: string[]) { - let k = 0; - for (let i = 0; k < pattern.length && i < segments.length; i++) { - if (pattern[k] === segments[i] || pattern[k] === '*') { - k++; - } else if (pattern[k] !== '**') { - return false; - } - } - return k === pattern.length; -} \ No newline at end of file diff --git a/extensions/markdown/package.nls.json b/extensions/markdown/package.nls.json index 45eecf6d299..253ab8910ef 100644 --- a/extensions/markdown/package.nls.json +++ b/extensions/markdown/package.nls.json @@ -1,5 +1,5 @@ { - "markdown.preview.doubleClickToSwitchToEditor.desc": "Enable double clicking in the markdown preview switches to the editor.", + "markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the markdown preview to switch to the editor.", "markdown.preview.fontFamily.desc": "Controls the font family used in the markdown preview.", "markdown.preview.fontSize.desc": "Controls the font size in pixels used in the markdown preview.", "markdown.preview.lineHeight.desc": "Controls the line height used in the markdown preview. This number is relative to the font size.", diff --git a/extensions/markdown/src/documentSymbolProvider.ts b/extensions/markdown/src/documentSymbolProvider.ts index c3af584ba1f..207fabb2cc7 100644 --- a/extensions/markdown/src/documentSymbolProvider.ts +++ b/extensions/markdown/src/documentSymbolProvider.ts @@ -8,14 +8,14 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from './markdownEngine'; -import { TableOfContentProvider } from './tableOfContentsProvider'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider { constructor(private engine: MarkdownEngine) { } provideDocumentSymbols(document: vscode.TextDocument): vscode.ProviderResult { - const toc = new TableOfContentProvider(this.engine, document); + const toc = new TableOfContentsProvider(this.engine, document); return toc.getToc().map(entry => { return new vscode.SymbolInformation(entry.text, vscode.SymbolKind.Module, '', entry.location); }); diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index 9d660ba1238..e3e72654850 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -12,7 +12,7 @@ import { MarkdownEngine } from './markdownEngine'; import DocumentLinkProvider from './documentLinkProvider'; import MDDocumentSymbolProvider from './documentSymbolProvider'; import { MDDocumentContentProvider, getMarkdownUri, isMarkdownFile } from './previewContentProvider'; -import { TableOfContentProvider } from './tableOfContentsProvider'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; interface IPackageInfo { name: string; @@ -78,7 +78,7 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('_markdown.openDocumentLink', (args: OpenDocumentLinkArgs) => { const tryRevealLine = (editor: vscode.TextEditor) => { if (editor && args.fragment) { - const toc = new TableOfContentProvider(engine, editor.document); + const toc = new TableOfContentsProvider(engine, editor.document); const line = toc.lookup(args.fragment); if (!isNaN(line)) { return editor.revealRange( diff --git a/extensions/markdown/src/markdownEngine.ts b/extensions/markdown/src/markdownEngine.ts index 6e878f554aa..602aea8ea23 100644 --- a/extensions/markdown/src/markdownEngine.ts +++ b/extensions/markdown/src/markdownEngine.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import { TableOfContentsProvider } from './tableOfContentsProvider'; export interface IToken { type: string; @@ -44,7 +45,9 @@ export class MarkdownEngine { } return `
${this.engine.utils.escapeHtml(str)}
`; } - }).use(mdnh, {}); + }).use(mdnh, { + slugify: (header: string) => TableOfContentsProvider.slugify(header) + }); for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'blockquote_open', 'list_item_open']) { this.addLineNumberRenderer(this.md, renderName); diff --git a/extensions/markdown/src/previewContentProvider.ts b/extensions/markdown/src/previewContentProvider.ts index 7ef099a8624..6a65997b19f 100644 --- a/extensions/markdown/src/previewContentProvider.ts +++ b/extensions/markdown/src/previewContentProvider.ts @@ -20,14 +20,12 @@ export function getMarkdownUri(uri: vscode.Uri) { export class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { private _onDidChange = new vscode.EventEmitter(); - private _waiting: boolean; + private _waiting: boolean = false; constructor( private engine: MarkdownEngine, private context: vscode.ExtensionContext - ) { - this._waiting = false; - } + ) { } private getMediaPath(mediaFile: string): string { return vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))).toString(); @@ -38,27 +36,28 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv } private fixHref(resource: vscode.Uri, href: string): string { - if (href) { - // Use href if it is already an URL - if (vscode.Uri.parse(href).scheme) { - return href; - } - - // Use href as file URI if it is absolute - if (this.isAbsolute(href)) { - return vscode.Uri.file(href).toString(); - } - - // use a workspace relative path if there is a workspace - let rootPath = vscode.workspace.rootPath; - if (rootPath) { - return vscode.Uri.file(path.join(rootPath, href)).toString(); - } - - // otherwise look relative to the markdown file - return vscode.Uri.file(path.join(path.dirname(resource.fsPath), href)).toString(); + if (!href) { + return href; } - return href; + + // Use href if it is already an URL + if (vscode.Uri.parse(href).scheme) { + return href; + } + + // Use href as file URI if it is absolute + if (this.isAbsolute(href)) { + return vscode.Uri.file(href).toString(); + } + + // use a workspace relative path if there is a workspace + let rootPath = vscode.workspace.rootPath; + if (rootPath) { + return vscode.Uri.file(path.join(rootPath, href)).toString(); + } + + // otherwise look relative to the markdown file + return vscode.Uri.file(path.join(path.dirname(resource.fsPath), href)).toString(); } private computeCustomStyleSheetIncludes(uri: vscode.Uri): string { diff --git a/extensions/markdown/src/tableOfContentsProvider.ts b/extensions/markdown/src/tableOfContentsProvider.ts index d2864ba66df..831e1772b42 100644 --- a/extensions/markdown/src/tableOfContentsProvider.ts +++ b/extensions/markdown/src/tableOfContentsProvider.ts @@ -16,7 +16,7 @@ export interface TocEntry { location: vscode.Location; } -export class TableOfContentProvider { +export class TableOfContentsProvider { private toc: TocEntry[]; public constructor( @@ -35,7 +35,7 @@ export class TableOfContentProvider { } public lookup(fragment: string): number { - const slug = TableOfContentProvider.slugify(fragment); + const slug = TableOfContentsProvider.slugify(fragment); for (const entry of this.getToc()) { if (entry.slug === slug) { return entry.line; @@ -51,11 +51,11 @@ export class TableOfContentProvider { for (const heading of tokens.filter(token => token.type === 'heading_open')) { const lineNumber = heading.map[0]; const line = document.lineAt(lineNumber); - const href = TableOfContentProvider.slugify(line.text); + const href = TableOfContentsProvider.slugify(line.text); if (href) { toc.push({ slug: href, - text: TableOfContentProvider.getHeaderText(line.text), + text: TableOfContentsProvider.getHeaderText(line.text), line: lineNumber, location: new vscode.Location(document.uri, line.range) }); @@ -69,9 +69,12 @@ export class TableOfContentProvider { } public static slugify(header: string): string { - return encodeURI(TableOfContentProvider.getHeaderText(header) + return encodeURI(header.trim() .toLowerCase() - .replace(/\s/g, '-')); + .replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '') + .replace(/\s+/g, '-') + .replace(/^\-+/, '') + .replace(/\-+$/, '')); } } diff --git a/extensions/perl/OSSREADME.json b/extensions/perl/OSSREADME.json index 18c0332c143..f7bae4f235b 100644 --- a/extensions/perl/OSSREADME.json +++ b/extensions/perl/OSSREADME.json @@ -4,6 +4,7 @@ "version": "0.0.0", "license": "TextMate Bundle License", "repositoryURL": "https://github.com/textmate/perl.tmbundle", + "description": "The files syntaxes/perl.tmLanguage.json and syntaxes/perl6.tmLanguage.json were derived from Perl.plist and Perl 6.tmLanguage from https://github.com/textmate/perl.tmbundle.", "licenseDetail": [ "Copyright (c) textmate-perl.tmbundle project authors", "", diff --git a/extensions/perl/package.json b/extensions/perl/package.json index 9b141930dd0..9197f588182 100644 --- a/extensions/perl/package.json +++ b/extensions/perl/package.json @@ -3,6 +3,9 @@ "version": "0.1.0", "publisher": "vscode", "engines": { "vscode": "*" }, + "scripts": { + "update-grammar": "node ../../build/npm/update-grammar.js textmate/perl.tmbundle Syntaxes/Perl.plist ./syntaxes/perl.tmLanguage.json Syntaxes/Perl%206.tmLanguage ./syntaxes/perl6.tmLanguage.json" + }, "contributes": { "languages": [{ "id": "perl", @@ -20,11 +23,11 @@ "grammars": [{ "language": "perl", "scopeName": "source.perl", - "path": "./syntaxes/Perl.plist" + "path": "./syntaxes/perl.tmLanguage.json" }, { "language": "perl6", "scopeName": "source.perl.6", - "path": "./syntaxes/Perl 6.tmLanguage" + "path": "./syntaxes/perl6.tmLanguage.json" }] } } \ No newline at end of file diff --git a/extensions/perl/syntaxes/Perl 6.tmLanguage b/extensions/perl/syntaxes/Perl 6.tmLanguage deleted file mode 100644 index a70f908c2bc..00000000000 --- a/extensions/perl/syntaxes/Perl 6.tmLanguage +++ /dev/null @@ -1,490 +0,0 @@ - - - - - fileTypes - - p6 - pl6 - pm6 - nqp - - firstLineMatch - (^#!.*\b(perl6|nqp)\b)|use\s+v6 - keyEquivalent - ^~P - name - Perl 6 - patterns - - - begin - ^=begin - end - ^=end - name - comment.block.perl - - - begin - (^[ \t]+)?(?=#) - beginCaptures - - 1 - - name - punctuation.whitespace.comment.leading.perl - - - end - (?!\G) - patterns - - - begin - # - beginCaptures - - 0 - - name - punctuation.definition.comment.perl - - - end - \n - name - comment.line.number-sign.perl - - - - - captures - - 1 - - name - storage.type.class.perl.6 - - 3 - - name - entity.name.type.class.perl.6 - - - match - (class|enum|grammar|knowhow|module|package|role|slang|subset)(\s+)(((?:::|')?(?:([a-zA-Z_\x{C0}-\x{FF}\$])([a-zA-Z0-9_\x{C0}-\x{FF}\\$]|[\-'][a-zA-Z0-9_\x{C0}-\x{FF}\$])*))+) - name - meta.class.perl.6 - - - begin - (?<=\s)' - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - ' - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.single.perl - patterns - - - match - \\['\\] - name - constant.character.escape.perl - - - - - begin - " - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - " - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.double.perl - patterns - - - match - \\[abtnfre"\\] - name - constant.character.escape.perl - - - - - begin - q(q|to|heredoc)*\s*:?(q|to|heredoc)*\s*/(.+)/ - end - \3 - name - string.quoted.single.heredoc.perl - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*{{ - end - }} - name - string.quoted.double.heredoc.brace.perl - patterns - - - include - #qq_brace_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*\(\( - end - \)\) - name - string.quoted.double.heredoc.paren.perl - patterns - - - include - #qq_paren_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*\[\[ - end - \]\] - name - string.quoted.double.heredoc.bracket.perl - patterns - - - include - #qq_bracket_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*{ - end - } - name - string.quoted.single.heredoc.brace.perl - patterns - - - include - #qq_brace_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*/ - end - / - name - string.quoted.single.heredoc.slash.perl - patterns - - - include - #qq_slash_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*\( - end - \) - name - string.quoted.single.heredoc.paren.perl - patterns - - - include - #qq_paren_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*\[ - end - \] - name - string.quoted.single.heredoc.bracket.perl - patterns - - - include - #qq_bracket_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*' - end - ' - name - string.quoted.single.heredoc.single.perl - patterns - - - include - #qq_single_string_content - - - - - begin - (q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\s*" - end - " - name - string.quoted.single.heredoc.double.perl - patterns - - - include - #qq_double_string_content - - - - - match - \b\$\w+\b - name - variable.other.perl - - - match - \b(macro|sub|submethod|method|multi|proto|only|rule|token|regex|category)\b - name - storage.type.declare.routine.perl - - - match - \b(self)\b - name - variable.language.perl - - - match - \b(use|require)\b - name - keyword.other.include.perl - - - match - \b(if|else|elsif|unless)\b - name - keyword.control.conditional.perl - - - match - \b(let|my|our|state|temp|has|constant)\b - name - storage.type.variable.perl - - - match - \b(for|loop|repeat|while|until|gather|given)\b - name - keyword.control.repeat.perl - - - match - \b(take|do|when|next|last|redo|return|contend|maybe|defer|default|exit|make|continue|break|goto|leave|async|lift)\b - name - keyword.control.flowcontrol.perl - - - match - \b(is|as|but|trusts|of|returns|handles|where|augment|supersede)\b - name - storage.modifier.type.constraints.perl - - - match - \b(BEGIN|CHECK|INIT|START|FIRST|ENTER|LEAVE|KEEP|UNDO|NEXT|LAST|PRE|POST|END|CATCH|CONTROL|TEMP)\b - name - meta.function.perl - - - match - \b(die|fail|try|warn)\b - name - keyword.control.control-handlers.perl - - - match - \b(prec|irs|ofs|ors|export|deep|binary|unary|reparsed|rw|parsed|cached|readonly|defequiv|will|ref|copy|inline|tighter|looser|equiv|assoc|required)\b - name - storage.modifier.perl - - - match - \b(NaN|Inf)\b - name - constant.numeric.perl - - - match - \b(oo|fatal)\b - name - keyword.other.pragma.perl - - - match - \b(Object|Any|Junction|Whatever|Capture|MatchSignature|Proxy|Matcher|Package|Module|ClassGrammar|Scalar|Array|Hash|KeyHash|KeySet|KeyBagPair|List|Seq|Range|Set|Bag|Mapping|Void|UndefFailure|Exception|Code|Block|Routine|Sub|MacroMethod|Submethod|Regex|Str|str|Blob|Char|ByteCodepoint|Grapheme|StrPos|StrLen|Version|NumComplex|num|complex|Bit|bit|bool|True|FalseIncreasing|Decreasing|Ordered|Callable|AnyCharPositional|Associative|Ordering|KeyExtractorComparator|OrderingPair|IO|KitchenSink|RoleInt|int|int1|int2|int4|int8|int16|int32|int64Rat|rat|rat1|rat2|rat4|rat8|rat16|rat32|rat64Buf|buf|buf1|buf2|buf4|buf8|buf16|buf32|buf64UInt|uint|uint1|uint2|uint4|uint8|uint16|uint32uint64|Abstraction|utf8|utf16|utf32)\b - name - support.type.perl6 - - - match - \b(div|xx|x|mod|also|leg|cmp|before|after|eq|ne|le|lt|not|gt|ge|eqv|ff|fff|and|andthen|or|xor|orelse|extra|lcm|gcd)\b - name - keyword.operator.perl - - - match - (\$|@|%|&)(\*|:|!|\^|~|=|\?|(<(?=.+>)))?([a-zA-Z_\x{C0}-\x{FF}\$])([a-zA-Z0-9_\x{C0}-\x{FF}\$]|[\-'][a-zA-Z0-9_\x{C0}-\x{FF}\$])* - name - variable.other.identifier.perl.6 - - - match - \b(eager|hyper|substr|index|rindex|grep|map|sort|join|lines|hints|chmod|split|reduce|min|max|reverse|truncate|zip|cat|roundrobin|classify|first|sum|keys|values|pairs|defined|delete|exists|elems|end|kv|any|all|one|wrap|shape|key|value|name|pop|push|shift|splice|unshift|floor|ceiling|abs|exp|log|log10|rand|sign|sqrt|sin|cos|tan|round|strand|roots|cis|unpolar|polar|atan2|pick|chop|p5chop|chomp|p5chomp|lc|lcfirst|uc|ucfirst|capitalize|normalize|pack|unpack|quotemeta|comb|samecase|sameaccent|chars|nfd|nfc|nfkd|nfkc|printf|sprintf|caller|evalfile|run|runinstead|nothing|want|bless|chr|ord|gmtime|time|eof|localtime|gethost|getpw|chroot|getlogin|getpeername|kill|fork|wait|perl|graphs|codes|bytes|clone|print|open|read|write|readline|say|seek|close|opendir|readdir|slurp|spurt|shell|run|pos|fmt|vec|link|unlink|symlink|uniq|pair|asin|atan|sec|cosec|cotan|asec|acosec|acotan|sinh|cosh|tanh|asinh|done|acos|acosh|atanh|sech|cosech|cotanh|sech|acosech|acotanh|asech|ok|nok|plan_ok|dies_ok|lives_ok|skip|todo|pass|flunk|force_todo|use_ok|isa_ok|diag|is_deeply|isnt|like|skip_rest|unlike|cmp_ok|eval_dies_ok|nok_error|eval_lives_ok|approx|is_approx|throws_ok|version_lt|plan|EVAL|succ|pred|times|nonce|once|signature|new|connect|operator|undef|undefine|sleep|from|to|infix|postfix|prefix|circumfix|postcircumfix|minmax|lazy|count|unwrap|getc|pi|e|context|void|quasi|body|each|contains|rewinddir|subst|can|isa|flush|arity|assuming|rewind|callwith|callsame|nextwith|nextsame|attr|eval_elsewhere|none|srand|trim|trim_start|trim_end|lastcall|WHAT|WHERE|HOW|WHICH|VAR|WHO|WHENCE|ACCEPTS|REJECTS|not|true|iterator|by|re|im|invert|flip|gist|flat|tree|is-prime|throws_like|trans)\b - name - support.function.perl - - - repository - - qq_brace_string_content - - begin - { - end - } - patterns - - - include - #qq_brace_string_content - - - - qq_bracket_string_content - - begin - \[ - end - \] - patterns - - - include - #qq_bracket_string_content - - - - qq_double_string_content - - begin - " - end - " - patterns - - - include - #qq_double_string_content - - - - qq_paren_string_content - - begin - \( - end - \) - patterns - - - include - #qq_paren_string_content - - - - qq_single_string_content - - begin - ' - end - ' - patterns - - - include - #qq_single_string_content - - - - qq_slash_string_content - - begin - \\/ - end - \\/ - patterns - - - include - #qq_slash_string_content - - - - - scopeName - source.perl.6 - uuid - E685440C-0E20-4424-9693-864D5240A269 - - \ No newline at end of file diff --git a/extensions/perl/syntaxes/Perl.plist b/extensions/perl/syntaxes/Perl.plist deleted file mode 100644 index 9b608d3030e..00000000000 --- a/extensions/perl/syntaxes/Perl.plist +++ /dev/null @@ -1,3707 +0,0 @@ - - - - - comment - - TODO: Include RegExp syntax - - fileTypes - - pl - pm - pod - t - PL - psgi - vcl - - firstLineMatch - ^#!.*\bperl\b - keyEquivalent - ^~P - name - Perl - patterns - - - include - #line_comment - - - begin - ^= - captures - - 0 - - name - punctuation.definition.comment.perl - - - end - ^=cut - name - comment.block.documentation.perl - - - include - #variable - - - applyEndPatternLast - 1 - begin - \b(?=qr\s*[^\s\w]) - comment - string.regexp.compile.perl - end - ((([egimosxradlupc]*)))(?=(\s+\S|\s*[;\,\#\{\}\)]|\s*$)) - endCaptures - - 1 - - name - string.regexp.compile.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - begin - (qr)\s*\{ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \} - name - string.regexp.compile.nested_braces.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_braces_interpolated - - - - - begin - (qr)\s*\[ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \] - name - string.regexp.compile.nested_brackets.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_brackets_interpolated - - - - - begin - (qr)\s*< - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - > - name - string.regexp.compile.nested_ltgt.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_ltgt_interpolated - - - - - begin - (qr)\s*\( - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \) - name - string.regexp.compile.nested_parens.perl - patterns - - - comment - This is to prevent thinks like qr/foo$/ to treat $/ as a variable - match - \$(?=[^\s\w\\'\{\[\(\<]) - - - include - #escaped_char - - - include - #variable - - - include - #nested_parens_interpolated - - - - - begin - (qr)\s*' - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - ' - name - string.regexp.compile.single-quote.perl - patterns - - - include - #escaped_char - - - - - begin - (qr)\s*([^\s\w'\{\[\(\<]) - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \2 - name - string.regexp.compile.simple-delimiter.perl - patterns - - - comment - This is to prevent thinks like qr/foo$/ to treat $/ as a variable - match - \$(?=[^\s\w'\{\[\(\<]) - name - keyword.control.anchor.perl - - - include - #escaped_char - - - include - #variable - - - include - #nested_parens_interpolated - - - - - - - applyEndPatternLast - 1 - begin - (?<!\{|\+|\-)\b(?=m\s*[^\sa-zA-Z0-9]) - comment - string.regexp.find-m.perl - end - ((([egimosxradlupc]*)))(?=(\s+\S|\s*[;\,\#\{\}\)]|\s*$)) - endCaptures - - 1 - - name - string.regexp.find-m.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - begin - (m)\s*\{ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \} - name - string.regexp.find-m.nested_braces.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_braces_interpolated - - - - - begin - (m)\s*\[ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \] - name - string.regexp.find-m.nested_brackets.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_brackets_interpolated - - - - - begin - (m)\s*< - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - > - name - string.regexp.find-m.nested_ltgt.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_ltgt_interpolated - - - - - begin - (m)\s*\( - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \) - name - string.regexp.find-m.nested_parens.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_parens_interpolated - - - - - begin - (m)\s*' - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - ' - name - string.regexp.find-m.single-quote.perl - patterns - - - include - #escaped_char - - - - - begin - (?<!\{|\+|\-)(m)\s*([^\sa-zA-Z0-9'\{\[\(\<]) - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \2 - name - string.regexp.find-m.simple-delimiter.perl - patterns - - - comment - This is to prevent thinks like qr/foo$/ to treat $/ as a variable - match - \$(?=[^\sa-zA-Z0-9'\{\[\(\<]) - name - keyword.control.anchor.perl - - - include - #escaped_char - - - include - #variable - - - begin - \[ - beginCaptures - - 1 - - name - punctuation.definition.character-class.begin.perl - - - end - \] - endCaptures - - 1 - - name - punctuation.definition.character-class.end.perl - - - name - constant.other.character-class.set.perl - patterns - - - comment - This is to prevent thinks like qr/foo$/ to treat $/ as a variable - match - \$(?=[^\s\w'\{\[\(\<]) - name - keyword.control.anchor.perl - - - include - #escaped_char - - - - - include - #nested_parens_interpolated - - - - - - - applyEndPatternLast - 1 - begin - \b(?=(?<!\&)(s)(\s+\S|\s*[;\,\#\{\}\(\)\[<]|$)) - comment - string.regexp.replace.perl - end - ((([egimosxradlupc]*)))(?=(\s+\S|\s*[;\,\#\{\}\)\]>]|\s*$)) - endCaptures - - 1 - - name - string.regexp.replace.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - begin - (s)\s*\{ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \} - name - string.regexp.nested_braces.perl - patterns - - - include - #escaped_char - - - include - #nested_braces - - - - - begin - (s)\s*\[ - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \] - name - string.regexp.nested_brackets.perl - patterns - - - include - #escaped_char - - - include - #nested_brackets - - - - - begin - (s)\s*< - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - > - name - string.regexp.nested_ltgt.perl - patterns - - - include - #escaped_char - - - include - #nested_ltgt - - - - - begin - (s)\s*\( - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - \) - name - string.regexp.nested_parens.perl - patterns - - - include - #escaped_char - - - include - #nested_parens - - - - - begin - \{ - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \} - name - string.regexp.format.nested_braces.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_braces_interpolated - - - - - begin - \[ - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \] - name - string.regexp.format.nested_brackets.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_brackets_interpolated - - - - - begin - < - captures - - 0 - - name - punctuation.definition.string.perl - - - end - > - name - string.regexp.format.nested_ltgt.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_ltgt_interpolated - - - - - begin - \( - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \) - name - string.regexp.format.nested_parens.perl - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_parens_interpolated - - - - - begin - ' - captures - - 0 - - name - punctuation.definition.string.perl - - - end - ' - name - string.regexp.format.single_quote.perl - patterns - - - match - \\['\\] - name - constant.character.escape.perl - - - - - begin - ([^\s\w\[({<;]) - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \1 - name - string.regexp.format.simple_delimiter.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - match - \s+ - - - - - begin - \b(?=s([^\sa-zA-Z0-9\[({<]).*\1([egimosxradlupc]*)([\}\)\;\,]|\s+)) - comment - string.regexp.replaceXXX - end - ((([egimosxradlupc]*)))(?=([\}\)\;\,]|\s+|$)) - endCaptures - - 1 - - name - string.regexp.replace.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - begin - (s\s*)([^\sa-zA-Z0-9\[({<]) - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - (?=\2) - name - string.regexp.replaceXXX.simple_delimiter.perl - patterns - - - include - #escaped_char - - - - - begin - ' - captures - - 0 - - name - punctuation.definition.string.perl - - - end - ' - name - string.regexp.replaceXXX.format.single_quote.perl - patterns - - - match - \\['\\] - name - constant.character.escape.perl.perl - - - - - begin - ([^\sa-zA-Z0-9\[({<]) - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \1 - name - string.regexp.replaceXXX.format.simple_delimiter.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - - - begin - \b(?=(?<!\\)s\s*([^\s\w\[({<>])) - comment - string.regexp.replace.extended - end - ((([egimosradlupc]*x[egimosradlupc]*)))\b - endCaptures - - 1 - - name - string.regexp.replace.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - begin - (s)\s*(.) - captures - - 0 - - name - punctuation.definition.string.perl - - 1 - - name - support.function.perl - - - end - (?=\2) - name - string.regexp.replace.extended.simple_delimiter.perl - patterns - - - include - #escaped_char - - - - - begin - ' - captures - - 0 - - name - punctuation.definition.string.perl - - - end - '(?=[egimosradlupc]*x[egimosradlupc]*)\b - name - string.regexp.replace.extended.simple_delimiter.perl - patterns - - - include - #escaped_char - - - - - begin - (.) - captures - - 0 - - name - punctuation.definition.string.perl - - - end - \1(?=[egimosradlupc]*x[egimosradlupc]*)\b - name - string.regexp.replace.extended.simple_delimiter.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - - - begin - (?<=\(|\{|~|&)\s*((\/)) - beginCaptures - - 1 - - name - string.regexp.find.perl - - 2 - - name - punctuation.definition.string.perl - - - contentName - string.regexp.find.perl - end - ((\1([egimosxradlupc]*)))(?=(\s+\S|\s*[;\,\#\{\}\)]|\s*$)) - endCaptures - - 1 - - name - string.regexp.find.perl - - 2 - - name - punctuation.definition.string.perl - - 3 - - name - keyword.control.regexp-option.perl - - - patterns - - - comment - This is to prevent thinks like /foo$/ to treat $/ as a variable - match - \$(?=\/) - name - keyword.control.anchor.perl - - - include - #escaped_char - - - include - #variable - - - - - captures - - 1 - - name - constant.other.key.perl - - - match - \b(\w+)\s*(?==>) - - - match - (?<={)\s*\w+\s*(?=}) - name - constant.other.bareword.perl - - - captures - - 1 - - name - keyword.control.perl - - 2 - - name - entity.name.type.class.perl - - - match - ^\s*(package)\s+([^\s;]+) - name - meta.class.perl - - - captures - - 1 - - name - storage.type.sub.perl - - 2 - - name - entity.name.function.perl - - 3 - - name - storage.type.method.perl - - - match - \b(sub)(?:\s+([-a-zA-Z0-9_]+))?\s*(?:\([\$\@\*;]*\))?[^\w\{] - name - meta.function.perl - - - captures - - 1 - - name - entity.name.function.perl - - 2 - - name - punctuation.definition.parameters.perl - - 3 - - name - variable.parameter.function.perl - - - match - ^\s*(BEGIN|UNITCHECK|CHECK|INIT|END|DESTROY)\b - name - meta.function.perl - - - begin - ^(?=(\t| {4})) - end - (?=[^\t\s]) - name - meta.leading-tabs - patterns - - - captures - - 1 - - name - meta.odd-tab - - 2 - - name - meta.even-tab - - - match - (\t| {4})(\t| {4})? - - - - - captures - - 1 - - name - support.function.perl - - 2 - - name - punctuation.definition.string.perl - - 5 - - name - punctuation.definition.string.perl - - 8 - - name - punctuation.definition.string.perl - - - match - \b(tr|y)\s*([^A-Za-z0-9\s])(.*?)(?<!\\)(\\{2})*(\2)(.*?)(?<!\\)(\\{2})*(\2) - name - string.regexp.replace.perl - - - match - \b(__FILE__|__LINE__|__PACKAGE__|__SUB__)\b - name - constant.language.perl - - - begin - \b(__DATA__|__END__)\n? - beginCaptures - - 1 - - name - constant.language.perl - - - contentName - text.plain - end - \z - - - match - (?<!->)\b(continue|die|do|else|elsif|exit|for|foreach|goto|if|last|next|redo|return|select|unless|until|wait|while|switch|case|require|use|eval)\b - name - keyword.control.perl - - - match - \b(my|our|local)\b - name - storage.modifier.perl - - - match - (?<!\w)\-[rwx0RWXOezsfdlpSbctugkTBMAC]\b - name - keyword.operator.filetest.perl - - - match - \b(and|or|xor|as|not)\b - name - keyword.operator.logical.perl - - - match - (<=>|=>|->) - name - keyword.operator.comparison.perl - - - begin - (((<<) *"HTML"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.html.embedded.perl - end - (^HTML$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - text.html.basic - - - - - begin - (((<<) *"XML"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.xml.embedded.perl - end - (^XML$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - text.xml - - - - - begin - (((<<) *"CSS"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.css.embedded.perl - end - (^CSS$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.css - - - - - begin - (((<<) *"JAVASCRIPT"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.js.embedded.perl - end - (^JAVASCRIPT$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.js - - - - - begin - (((<<) *"SQL"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.sql.embedded.perl - end - (^SQL$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.sql - - - - - begin - (((<<) *"POSTSCRIPT"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.postscript.embedded.perl - end - (^POSTSCRIPT$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.postscript - - - - - begin - (((<<) *"([^"]*)"))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.doublequote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - string.unquoted.heredoc.doublequote.perl - end - (^\4$) - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - (((<<) *'HTML'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.html.embedded.perl - end - (^HTML$) - patterns - - - include - text.html.basic - - - - - begin - (((<<) *'XML'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.xml.embedded.perl - end - (^XML$) - patterns - - - include - text.xml - - - - - begin - (((<<) *'CSS'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.css.embedded.perl - end - (^CSS$) - patterns - - - include - source.css - - - - - begin - (((<<) *'JAVASCRIPT'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.js.embedded.perl - end - (^JAVASCRIPT$) - patterns - - - include - source.js - - - - - begin - (((<<) *'SQL'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.sql.embedded.perl - end - (^SQL$) - patterns - - - include - source.sql - - - - - begin - (((<<) *'POSTSCRIPT'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.postscript.embedded.perl - end - (^POSTSCRIPT$) - patterns - - - include - source.postscript - - - - - begin - (((<<) *'([^']*)'))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - string.unquoted.heredoc.quote.perl - end - (^\4$) - - - begin - (((<<) *\\((?![=\d\$\( ])[^;,'"`\s\)]*)))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.quote.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - string.unquoted.heredoc.quote.perl - end - (^\4$) - - - begin - (((<<) *`([^`]*)`))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.backtick.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - string.unquoted.heredoc.backtick.perl - end - (^\4$) - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - (((<<) *HTML\b))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.html.embedded.perl - end - (^HTML$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - text.html.basic - - - - - begin - (((<<) *XML\b))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - text.xml.embedded.perl - end - (^XML$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - text.xml - - - - - begin - (((<<) *JAVASCRIPT\b))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.js.embedded.perl - end - (^JAVASCRIPT$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.js - - - - - begin - (((<<) *SQL\b))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.sql.embedded.perl - end - (^SQL$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.sql - - - - - begin - (((<<) *POSTSCRIPT\b))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 4 - - patterns - - - include - $self - - - - - contentName - source.postscript.embedded.perl - end - (^POSTSCRIPT$) - patterns - - - include - #escaped_char - - - include - #variable - - - include - source.postscript - - - - - begin - (((<<) *((?![=\d\$\( ])[^;,'"`\s\)]*)))(.*)\n? - captures - - 1 - - name - punctuation.definition.string.perl - - 2 - - name - string.unquoted.heredoc.perl - - 3 - - name - punctuation.definition.heredoc.perl - - 5 - - patterns - - - include - $self - - - - - contentName - string.unquoted.heredoc.perl - end - (^\4$) - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - \bqq\s*([^\(\{\[\<\w\s]) - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \1 - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.qq.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - \bqx\s*([^'\(\{\[\<\w\s]) - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \1 - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - \bqx\s*' - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - ' - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx.single-quote.perl - patterns - - - include - #escaped_char - - - - - begin - " - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - " - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.double.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - (?<!->)\bqw?\s*([^\(\{\[\<\w\s]) - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \1 - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.q.perl - - - begin - ' - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - ' - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.single.perl - patterns - - - match - \\['\\] - name - constant.character.escape.perl - - - - - begin - ` - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - ` - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.perl - patterns - - - include - #escaped_char - - - include - #variable - - - - - begin - (?<!->)\bqq\s*\( - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \) - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.qq-paren.perl - patterns - - - include - #escaped_char - - - include - #nested_parens_interpolated - - - include - #variable - - - - - begin - \bqq\s*\{ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \} - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.qq-brace.perl - patterns - - - include - #escaped_char - - - include - #nested_braces_interpolated - - - include - #variable - - - - - begin - \bqq\s*\[ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \] - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.qq-bracket.perl - patterns - - - include - #escaped_char - - - include - #nested_brackets_interpolated - - - include - #variable - - - - - begin - \bqq\s*\< - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \> - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.qq-ltgt.perl - patterns - - - include - #escaped_char - - - include - #nested_ltgt_interpolated - - - include - #variable - - - - - begin - (?<!->)\bqx\s*\( - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \) - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx-paren.perl - patterns - - - include - #escaped_char - - - include - #nested_parens_interpolated - - - include - #variable - - - - - begin - \bqx\s*\{ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \} - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx-brace.perl - patterns - - - include - #escaped_char - - - include - #nested_braces_interpolated - - - include - #variable - - - - - begin - \bqx\s*\[ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \] - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx-bracket.perl - patterns - - - include - #escaped_char - - - include - #nested_brackets_interpolated - - - include - #variable - - - - - begin - \bqx\s*\< - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \> - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.interpolated.qx-ltgt.perl - patterns - - - include - #escaped_char - - - include - #nested_ltgt_interpolated - - - include - #variable - - - - - begin - (?<!->)\bqw?\s*\( - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \) - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.q-paren.perl - patterns - - - include - #nested_parens - - - - - begin - \bqw?\s*\{ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \} - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.q-brace.perl - patterns - - - include - #nested_braces - - - - - begin - \bqw?\s*\[ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \] - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.q-bracket.perl - patterns - - - include - #nested_brackets - - - - - begin - \bqw?\s*\< - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - \> - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.quoted.other.q-ltgt.perl - patterns - - - include - #nested_ltgt - - - - - begin - ^__\w+__ - beginCaptures - - 0 - - name - punctuation.definition.string.begin.perl - - - end - $ - endCaptures - - 0 - - name - punctuation.definition.string.end.perl - - - name - string.unquoted.program-block.perl - - - begin - \b(format)\s+(\w+)\s*= - beginCaptures - - 1 - - name - support.function.perl - - 2 - - name - entity.name.function.format.perl - - - end - ^\.\s*$ - name - meta.format.perl - patterns - - - include - #line_comment - - - include - #variable - - - - - captures - - 1 - - name - support.function.perl - - 2 - - name - entity.name.function.perl - - - match - \b(x)\s*(\d+)\b - - - match - \b(ARGV|DATA|ENV|SIG|STDERR|STDIN|STDOUT|atan2|bind|binmode|bless|caller|chdir|chmod|chomp|chop|chown|chr|chroot|close|closedir|cmp|connect|cos|crypt|dbmclose|dbmopen|defined|delete|dump|each|endgrent|endhostent|endnetent|endprotoent|endpwent|endservent|eof|eq|eval|exec|exists|exp|fcntl|fileno|flock|fork|formline|ge|getc|getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent|getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp|getppid|getpriority|getprotobyname|getprotobynumber|getprotoent|getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent|getsockname|getsockopt|glob|gmtime|grep|gt|hex|import|index|int|ioctl|join|keys|kill|lc|lcfirst|le|length|link|listen|local|localtime|log|lstat|lt|m|map|mkdir|msgctl|msgget|msgrcv|msgsnd|ne|no|oct|open|opendir|ord|pack|pipe|pop|pos|print|printf|push|quotemeta|rand|read|readdir|readlink|recv|ref|rename|reset|reverse|rewinddir|rindex|rmdir|s|scalar|seek|seekdir|semctl|semget|semop|send|setgrent|sethostent|setnetent|setpgrp|setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl|shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort|splice|split|sprintf|sqrt|srand|stat|study|substr|symlink|syscall|sysopen|sysread|system|syswrite|tell|telldir|tie|tied|time|times|tr|truncate|uc|ucfirst|umask|undef|unlink|unpack|unshift|untie|utime|values|vec|waitpid|wantarray|warn|write|y)\b - name - support.function.perl - - - captures - - 1 - - name - punctuation.section.scope.begin.perl - - 2 - - name - punctuation.section.scope.end.perl - - - comment - Match empty brackets for ↩ snippet - match - (\{)(\}) - - - captures - - 1 - - name - punctuation.section.scope.begin.perl - - 2 - - name - punctuation.section.scope.end.perl - - - comment - Match empty parenthesis for ↩ snippet - match - (\()(\)) - - - repository - - escaped_char - - patterns - - - match - \\\d+ - name - constant.character.escape.perl - - - match - \\c[^\s\\] - name - constant.character.escape.perl - - - match - \\g(?:\{(?:\w*|-\d+)\}|\d+) - name - constant.character.escape.perl - - - match - \\k(?:\{\w*\}|<\w*>|'\w*') - name - constant.character.escape.perl - - - match - \\N\{[^\}]*\} - name - constant.character.escape.perl - - - match - \\o\{\d*\} - name - constant.character.escape.perl - - - match - \\(?:p|P)(?:\{\w*\}|P) - name - constant.character.escape.perl - - - match - \\x(?:[0-9a-zA-Z]{2}|\{\w*\})? - name - constant.character.escape.perl - - - match - \\. - name - constant.character.escape.perl - - - - line_comment - - patterns - - - begin - (^[ \t]+)?(?=#) - beginCaptures - - 1 - - name - punctuation.whitespace.comment.leading.perl - - - end - (?!\G) - patterns - - - begin - # - beginCaptures - - 0 - - name - punctuation.definition.comment.perl - - - end - \n - name - comment.line.number-sign.perl - - - - - - nested_braces - - begin - \{ - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \} - patterns - - - include - #escaped_char - - - include - #nested_braces - - - - nested_braces_interpolated - - begin - \{ - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \} - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_braces_interpolated - - - - nested_brackets - - begin - \[ - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \] - patterns - - - include - #escaped_char - - - include - #nested_brackets - - - - nested_brackets_interpolated - - begin - \[ - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \] - patterns - - - include - #escaped_char - - - include - #variable - - - include - #nested_brackets_interpolated - - - - nested_ltgt - - begin - < - captures - - 1 - - name - punctuation.section.scope.perl - - - end - > - patterns - - - include - #nested_ltgt - - - - nested_ltgt_interpolated - - begin - < - captures - - 1 - - name - punctuation.section.scope.perl - - - end - > - patterns - - - include - #variable - - - include - #nested_ltgt_interpolated - - - - nested_parens - - begin - \( - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \) - patterns - - - include - #escaped_char - - - include - #nested_parens - - - - nested_parens_interpolated - - begin - \( - captures - - 1 - - name - punctuation.section.scope.perl - - - end - \) - patterns - - - comment - This is to prevent thinks like qr/foo$/ to treat $/ as a variable - match - \$(?=[^\s\w'\{\[\(\<]) - name - keyword.control.anchor.perl - - - include - #escaped_char - - - include - #variable - - - include - #nested_parens_interpolated - - - - variable - - patterns - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)&(?![A-Za-z0-9_]) - name - variable.other.regexp.match.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)`(?![A-Za-z0-9_]) - name - variable.other.regexp.pre-match.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)'(?![A-Za-z0-9_]) - name - variable.other.regexp.post-match.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)\+(?![A-Za-z0-9_]) - name - variable.other.regexp.last-paren-match.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)"(?![A-Za-z0-9_]) - name - variable.other.readwrite.list-separator.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)0(?![A-Za-z0-9_]) - name - variable.other.predefined.program-name.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)[_ab\*\.\/\|,\\;#%=\-~^:?!\$<>\(\)\[\]@](?![A-Za-z0-9_]) - name - variable.other.predefined.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - (\$)[0-9]+(?![A-Za-z0-9_]) - name - variable.other.subpattern.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - ([\$\@\%](#)?)([a-zA-Zx7f-xff\$]|::)([a-zA-Z0-9_x7f-xff\$]|::)*\b - name - variable.other.readwrite.global.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - 2 - - name - punctuation.definition.variable.perl - - - match - (\$\{)(?:[a-zA-Zx7f-xff\$]|::)(?:[a-zA-Z0-9_x7f-xff\$]|::)*(\}) - name - variable.other.readwrite.global.perl - - - captures - - 1 - - name - punctuation.definition.variable.perl - - - match - ([\$\@\%](#)?)[0-9_]\b - name - variable.other.readwrite.global.special.perl - - - - - scopeName - source.perl - uuid - EDBFE125-6B1C-11D9-9189-000D93589AF6 - - \ No newline at end of file diff --git a/extensions/perl/syntaxes/perl.tmLanguage.json b/extensions/perl/syntaxes/perl.tmLanguage.json new file mode 100644 index 00000000000..612c4796cbe --- /dev/null +++ b/extensions/perl/syntaxes/perl.tmLanguage.json @@ -0,0 +1,2546 @@ +{ + "comment": "\n\tTODO:\tInclude RegExp syntax\n", + "fileTypes": [ + "pl", + "pm", + "pod", + "t", + "PL", + "psgi", + "vcl" + ], + "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/|(?<=!)\\b)perl(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:(?:(?!mode\\s*:)[^:;]+:[^:;]+;)*\\s*mode\\s*:)?\\s*\n\tperl\n \\s*(?:;[^:;]+:[^:;]+?)*;?\\s*-\\*-\n |\n # Vim\n (?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|(?!^)\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n\tperl\n (?=\\s|:|$)\n)", + "keyEquivalent": "^~P", + "name": "Perl", + "patterns": [ + { + "include": "#line_comment" + }, + { + "begin": "^(?==[a-zA-Z]+)", + "end": "^(=cut\\b.*$)", + "endCaptures": { + "1": { + "patterns": [ + { + "include": "#pod" + } + ] + } + }, + "name": "comment.block.documentation.perl", + "patterns": [ + { + "include": "#pod" + } + ] + }, + { + "include": "#variable" + }, + { + "applyEndPatternLast": 1, + "begin": "\\b(?=qr\\s*[^\\s\\w])", + "comment": "string.regexp.compile.perl", + "end": "((([egimosxradlupcn]*)))(?=(\\s+\\S|\\s*[;\\,\\#\\{\\}\\)]|\\s*$))", + "endCaptures": { + "1": { + "name": "string.regexp.compile.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "3": { + "name": "keyword.control.regexp-option.perl" + } + }, + "patterns": [ + { + "begin": "(qr)\\s*\\{", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\}", + "name": "string.regexp.compile.nested_braces.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_braces_interpolated" + } + ] + }, + { + "begin": "(qr)\\s*\\[", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\]", + "name": "string.regexp.compile.nested_brackets.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_brackets_interpolated" + } + ] + }, + { + "begin": "(qr)\\s*<", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": ">", + "name": "string.regexp.compile.nested_ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_ltgt_interpolated" + } + ] + }, + { + "begin": "(qr)\\s*\\(", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\)", + "name": "string.regexp.compile.nested_parens.perl", + "patterns": [ + { + "comment": "This is to prevent thinks like qr/foo$/ to treat $/ as a variable", + "match": "\\$(?=[^\\s\\w\\\\'\\{\\[\\(\\<])" + }, + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_parens_interpolated" + } + ] + }, + { + "begin": "(qr)\\s*'", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "'", + "name": "string.regexp.compile.single-quote.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "(qr)\\s*([^\\s\\w'\\{\\[\\(\\<])", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\2", + "name": "string.regexp.compile.simple-delimiter.perl", + "patterns": [ + { + "comment": "This is to prevent thinks like qr/foo$/ to treat $/ as a variable", + "match": "\\$(?=[^\\s\\w'\\{\\[\\(\\<])", + "name": "keyword.control.anchor.perl" + }, + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_parens_interpolated" + } + ] + } + ] + }, + { + "applyEndPatternLast": 1, + "begin": "(?", + "name": "string.regexp.find-m.nested_ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_ltgt_interpolated" + } + ] + }, + { + "begin": "(m)\\s*\\(", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\)", + "name": "string.regexp.find-m.nested_parens.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_parens_interpolated" + } + ] + }, + { + "begin": "(m)\\s*'", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "'", + "name": "string.regexp.find-m.single-quote.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "(?]|\\s*$))", + "endCaptures": { + "1": { + "name": "string.regexp.replace.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "3": { + "name": "keyword.control.regexp-option.perl" + } + }, + "patterns": [ + { + "begin": "(s)\\s*\\{", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\}", + "name": "string.regexp.nested_braces.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_braces" + } + ] + }, + { + "begin": "(s)\\s*\\[", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\]", + "name": "string.regexp.nested_brackets.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_brackets" + } + ] + }, + { + "begin": "(s)\\s*<", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": ">", + "name": "string.regexp.nested_ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_ltgt" + } + ] + }, + { + "begin": "(s)\\s*\\(", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "\\)", + "name": "string.regexp.nested_parens.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_parens" + } + ] + }, + { + "begin": "\\{", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\}", + "name": "string.regexp.format.nested_braces.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_braces_interpolated" + } + ] + }, + { + "begin": "\\[", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\]", + "name": "string.regexp.format.nested_brackets.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_brackets_interpolated" + } + ] + }, + { + "begin": "<", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": ">", + "name": "string.regexp.format.nested_ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_ltgt_interpolated" + } + ] + }, + { + "begin": "\\(", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\)", + "name": "string.regexp.format.nested_parens.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_parens_interpolated" + } + ] + }, + { + "begin": "'", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "'", + "name": "string.regexp.format.single_quote.perl", + "patterns": [ + { + "match": "\\\\['\\\\]", + "name": "constant.character.escape.perl" + } + ] + }, + { + "begin": "([^\\s\\w\\[({<;])", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\1", + "name": "string.regexp.format.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "match": "\\s+" + } + ] + }, + { + "begin": "\\b(?=s([^\\sa-zA-Z0-9\\[({<]).*\\1([egimosxradlupcn]*)([\\}\\)\\;\\,]|\\s+))", + "comment": "string.regexp.replaceXXX", + "end": "((([egimosxradlupcn]*)))(?=([\\}\\)\\;\\,]|\\s+|\\s*$))", + "endCaptures": { + "1": { + "name": "string.regexp.replace.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "3": { + "name": "keyword.control.regexp-option.perl" + } + }, + "patterns": [ + { + "begin": "(s\\s*)([^\\sa-zA-Z0-9\\[({<])", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "(?=\\2)", + "name": "string.regexp.replaceXXX.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "'", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "'", + "name": "string.regexp.replaceXXX.format.single_quote.perl", + "patterns": [ + { + "match": "\\\\['\\\\]", + "name": "constant.character.escape.perl.perl" + } + ] + }, + { + "begin": "([^\\sa-zA-Z0-9\\[({<])", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\1", + "name": "string.regexp.replaceXXX.format.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + } + ] + }, + { + "begin": "\\b(?=(?]))", + "comment": "string.regexp.replace.extended", + "end": "((([egimosradlupc]*x[egimosradlupc]*)))\\b", + "endCaptures": { + "1": { + "name": "string.regexp.replace.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "3": { + "name": "keyword.control.regexp-option.perl" + } + }, + "patterns": [ + { + "begin": "(s)\\s*(.)", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + }, + "1": { + "name": "support.function.perl" + } + }, + "end": "(?=\\2)", + "name": "string.regexp.replace.extended.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "'", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "'(?=[egimosradlupc]*x[egimosradlupc]*)\\b", + "name": "string.regexp.replace.extended.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "(.)", + "captures": { + "0": { + "name": "punctuation.definition.string.perl" + } + }, + "end": "\\1(?=[egimosradlupc]*x[egimosradlupc]*)\\b", + "name": "string.regexp.replace.extended.simple_delimiter.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + } + ] + }, + { + "begin": "(?<=\\(|\\{|~|&|\\||if|unless|^)\\s*((\\/))", + "beginCaptures": { + "1": { + "name": "string.regexp.find.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + } + }, + "contentName": "string.regexp.find.perl", + "end": "((\\1([egimosxradlupcn]*)))(?=(\\s+\\S|\\s*[;\\,\\#\\{\\}\\)]|\\s*$))", + "endCaptures": { + "1": { + "name": "string.regexp.find.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "3": { + "name": "keyword.control.regexp-option.perl" + } + }, + "patterns": [ + { + "comment": "This is to prevent thinks like /foo$/ to treat $/ as a variable", + "match": "\\$(?=\\/)", + "name": "keyword.control.anchor.perl" + }, + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "captures": { + "1": { + "name": "constant.other.key.perl" + } + }, + "match": "\\b(\\w+)\\s*(?==>)" + }, + { + "match": "(?<={)\\s*\\w+\\s*(?=})", + "name": "constant.other.bareword.perl" + }, + { + "captures": { + "1": { + "name": "keyword.control.perl" + }, + "2": { + "name": "entity.name.type.class.perl" + } + }, + "match": "^\\s*(package)\\s+([^\\s;]+)", + "name": "meta.class.perl" + }, + { + "captures": { + "1": { + "name": "storage.type.sub.perl" + }, + "2": { + "name": "entity.name.function.perl" + }, + "3": { + "name": "storage.type.method.perl" + } + }, + "match": "\\b(sub)(?:\\s+([-a-zA-Z0-9_]+))?\\s*(?:\\([\\$\\@\\*;]*\\))?[^\\w\\{]", + "name": "meta.function.perl" + }, + { + "captures": { + "1": { + "name": "entity.name.function.perl" + }, + "2": { + "name": "punctuation.definition.parameters.perl" + }, + "3": { + "name": "variable.parameter.function.perl" + } + }, + "match": "^\\s*(BEGIN|UNITCHECK|CHECK|INIT|END|DESTROY)\\b", + "name": "meta.function.perl" + }, + { + "begin": "^(?=(\\t| {4}))", + "end": "(?=[^\\t\\s])", + "name": "meta.leading-tabs", + "patterns": [ + { + "captures": { + "1": { + "name": "meta.odd-tab" + }, + "2": { + "name": "meta.even-tab" + } + }, + "match": "(\\t| {4})(\\t| {4})?" + } + ] + }, + { + "captures": { + "1": { + "name": "support.function.perl" + }, + "2": { + "name": "punctuation.definition.string.perl" + }, + "5": { + "name": "punctuation.definition.string.perl" + }, + "8": { + "name": "punctuation.definition.string.perl" + } + }, + "match": "\\b(tr|y)\\s*([^A-Za-z0-9\\s])(.*?)(?)\\b(continue|default|die|do|else|elsif|exit|for|foreach|given|goto|if|last|next|redo|return|select|unless|until|wait|when|while|switch|case|require|use|eval)\\b", + "name": "keyword.control.perl" + }, + { + "match": "\\b(my|our|local)\\b", + "name": "storage.modifier.perl" + }, + { + "match": "(?|=>|->)", + "name": "keyword.operator.comparison.perl" + }, + { + "begin": "(((<<) *\"HTML\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.html.embedded.perl", + "end": "(^HTML$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.html.basic" + } + ] + }, + { + "begin": "(((<<) *\"XML\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.xml.embedded.perl", + "end": "(^XML$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.xml" + } + ] + }, + { + "begin": "(((<<) *\"CSS\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.css.embedded.perl", + "end": "(^CSS$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.css" + } + ] + }, + { + "begin": "(((<<) *\"JAVASCRIPT\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.js.embedded.perl", + "end": "(^JAVASCRIPT$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.js" + } + ] + }, + { + "begin": "(((<<) *\"SQL\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.sql.embedded.perl", + "end": "(^SQL$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.sql" + } + ] + }, + { + "begin": "(((<<) *\"POSTSCRIPT\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.postscript.embedded.perl", + "end": "(^POSTSCRIPT$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.postscript" + } + ] + }, + { + "begin": "(((<<) *\"([^\"]*)\"))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.doublequote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.doublequote.perl", + "end": "(^\\4$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(((<<) *'HTML'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.html.embedded.perl", + "end": "(^HTML$)", + "patterns": [ + { + "include": "text.html.basic" + } + ] + }, + { + "begin": "(((<<) *'XML'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.xml.embedded.perl", + "end": "(^XML$)", + "patterns": [ + { + "include": "text.xml" + } + ] + }, + { + "begin": "(((<<) *'CSS'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.css.embedded.perl", + "end": "(^CSS$)", + "patterns": [ + { + "include": "source.css" + } + ] + }, + { + "begin": "(((<<) *'JAVASCRIPT'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.js.embedded.perl", + "end": "(^JAVASCRIPT$)", + "patterns": [ + { + "include": "source.js" + } + ] + }, + { + "begin": "(((<<) *'SQL'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.sql.embedded.perl", + "end": "(^SQL$)", + "patterns": [ + { + "include": "source.sql" + } + ] + }, + { + "begin": "(((<<) *'POSTSCRIPT'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.postscript.embedded.perl", + "end": "(^POSTSCRIPT$)", + "patterns": [ + { + "include": "source.postscript" + } + ] + }, + { + "begin": "(((<<) *'([^']*)'))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.quote.perl", + "end": "(^\\4$)" + }, + { + "begin": "(((<<) *\\\\((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.quote.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.quote.perl", + "end": "(^\\4$)" + }, + { + "begin": "(((<<) *`([^`]*)`))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.backtick.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.backtick.perl", + "end": "(^\\4$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(((<<) *HTML\\b))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.html.embedded.perl", + "end": "(^HTML$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.html.basic" + } + ] + }, + { + "begin": "(((<<) *XML\\b))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "text.xml.embedded.perl", + "end": "(^XML$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "text.xml" + } + ] + }, + { + "begin": "(((<<) *JAVASCRIPT\\b))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.js.embedded.perl", + "end": "(^JAVASCRIPT$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.js" + } + ] + }, + { + "begin": "(((<<) *SQL\\b))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.sql.embedded.perl", + "end": "(^SQL$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.sql" + } + ] + }, + { + "begin": "(((<<) *POSTSCRIPT\\b))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "4": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "source.postscript.embedded.perl", + "end": "(^POSTSCRIPT$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "source.postscript" + } + ] + }, + { + "begin": "(((<<) *((?![=\\d\\$\\( ])[^;,'\"`\\s\\)]*)))(.*)\\n?", + "captures": { + "1": { + "name": "punctuation.definition.string.perl" + }, + "2": { + "name": "string.unquoted.heredoc.perl" + }, + "3": { + "name": "punctuation.definition.heredoc.perl" + }, + "5": { + "patterns": [ + { + "include": "$self" + } + ] + } + }, + "contentName": "string.unquoted.heredoc.perl", + "end": "(^\\4$)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqq\\s*([^\\(\\{\\[\\<\\w\\s])", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\1", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.qq.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqx\\s*([^'\\(\\{\\[\\<\\w\\s])", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\1", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqx\\s*'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx.single-quote.perl", + "patterns": [ + { + "include": "#escaped_char" + } + ] + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.double.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(?)\\bqw?\\s*([^\\(\\{\\[\\<\\w\\s])", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\1", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.q.perl" + }, + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.single.perl", + "patterns": [ + { + "match": "\\\\['\\\\]", + "name": "constant.character.escape.perl" + } + ] + }, + { + "begin": "`", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "`", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(?)\\bqq\\s*\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.qq-paren.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_parens_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqq\\s*\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.qq-brace.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_braces_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqq\\s*\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.qq-bracket.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_brackets_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqq\\s*\\<", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\>", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.qq-ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_ltgt_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(?)\\bqx\\s*\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx-paren.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_parens_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqx\\s*\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx-brace.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_braces_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqx\\s*\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx-bracket.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_brackets_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "\\bqx\\s*\\<", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\>", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.interpolated.qx-ltgt.perl", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_ltgt_interpolated" + }, + { + "include": "#variable" + } + ] + }, + { + "begin": "(?)\\bqw?\\s*\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.q-paren.perl", + "patterns": [ + { + "include": "#nested_parens" + } + ] + }, + { + "begin": "\\bqw?\\s*\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.q-brace.perl", + "patterns": [ + { + "include": "#nested_braces" + } + ] + }, + { + "begin": "\\bqw?\\s*\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.q-bracket.perl", + "patterns": [ + { + "include": "#nested_brackets" + } + ] + }, + { + "begin": "\\bqw?\\s*\\<", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\\>", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.other.q-ltgt.perl", + "patterns": [ + { + "include": "#nested_ltgt" + } + ] + }, + { + "begin": "^__\\w+__", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.unquoted.program-block.perl" + }, + { + "begin": "\\b(format)\\s+(\\w+)\\s*=", + "beginCaptures": { + "1": { + "name": "support.function.perl" + }, + "2": { + "name": "entity.name.function.format.perl" + } + }, + "end": "^\\.\\s*$", + "name": "meta.format.perl", + "patterns": [ + { + "include": "#line_comment" + }, + { + "include": "#variable" + } + ] + }, + { + "captures": { + "1": { + "name": "support.function.perl" + }, + "2": { + "name": "entity.name.function.perl" + } + }, + "match": "\\b(x)\\s*(\\d+)\\b" + }, + { + "match": "\\b(ARGV|DATA|ENV|SIG|STDERR|STDIN|STDOUT|atan2|bind|binmode|bless|caller|chdir|chmod|chomp|chop|chown|chr|chroot|close|closedir|cmp|connect|cos|crypt|dbmclose|dbmopen|defined|delete|dump|each|endgrent|endhostent|endnetent|endprotoent|endpwent|endservent|eof|eq|eval|exec|exists|exp|fcntl|fileno|flock|fork|formline|ge|getc|getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent|getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp|getppid|getpriority|getprotobyname|getprotobynumber|getprotoent|getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent|getsockname|getsockopt|glob|gmtime|grep|gt|hex|import|index|int|ioctl|join|keys|kill|lc|lcfirst|le|length|link|listen|local|localtime|log|lstat|lt|m|map|mkdir|msgctl|msgget|msgrcv|msgsnd|ne|no|oct|open|opendir|ord|pack|pipe|pop|pos|print|printf|push|quotemeta|rand|read|readdir|readlink|recv|ref|rename|reset|reverse|rewinddir|rindex|rmdir|s|say|scalar|seek|seekdir|semctl|semget|semop|send|setgrent|sethostent|setnetent|setpgrp|setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl|shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort|splice|split|sprintf|sqrt|srand|stat|study|substr|symlink|syscall|sysopen|sysread|system|syswrite|tell|telldir|tie|tied|time|times|tr|truncate|uc|ucfirst|umask|undef|unlink|unpack|unshift|untie|utime|values|vec|waitpid|wantarray|warn|write|y)\\b", + "name": "support.function.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.section.scope.begin.perl" + }, + "2": { + "name": "punctuation.section.scope.end.perl" + } + }, + "comment": "Match empty brackets for ↩ snippet", + "match": "(\\{)(\\})" + }, + { + "captures": { + "1": { + "name": "punctuation.section.scope.begin.perl" + }, + "2": { + "name": "punctuation.section.scope.end.perl" + } + }, + "comment": "Match empty parenthesis for ↩ snippet", + "match": "(\\()(\\))" + } + ], + "repository": { + "escaped_char": { + "patterns": [ + { + "match": "\\\\\\d+", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\c[^\\s\\\\]", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\g(?:\\{(?:\\w*|-\\d+)\\}|\\d+)", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\k(?:\\{\\w*\\}|<\\w*>|'\\w*')", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\N\\{[^\\}]*\\}", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\o\\{\\d*\\}", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\(?:p|P)(?:\\{\\w*\\}|P)", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\x(?:[0-9a-zA-Z]{2}|\\{\\w*\\})?", + "name": "constant.character.escape.perl" + }, + { + "match": "\\\\.", + "name": "constant.character.escape.perl" + } + ] + }, + "line_comment": { + "patterns": [ + { + "begin": "(^[ \\t]+)?(?=#)", + "beginCaptures": { + "1": { + "name": "punctuation.whitespace.comment.leading.perl" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "begin": "#", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.perl" + } + }, + "end": "\\n", + "name": "comment.line.number-sign.perl" + } + ] + } + ] + }, + "nested_braces": { + "begin": "\\{", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\}", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_braces" + } + ] + }, + "nested_braces_interpolated": { + "begin": "\\{", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\}", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_braces_interpolated" + } + ] + }, + "nested_brackets": { + "begin": "\\[", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\]", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_brackets" + } + ] + }, + "nested_brackets_interpolated": { + "begin": "\\[", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\]", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_brackets_interpolated" + } + ] + }, + "nested_ltgt": { + "begin": "<", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": ">", + "patterns": [ + { + "include": "#nested_ltgt" + } + ] + }, + "nested_ltgt_interpolated": { + "begin": "<", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": ">", + "patterns": [ + { + "include": "#variable" + }, + { + "include": "#nested_ltgt_interpolated" + } + ] + }, + "nested_parens": { + "begin": "\\(", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\)", + "patterns": [ + { + "include": "#escaped_char" + }, + { + "include": "#nested_parens" + } + ] + }, + "nested_parens_interpolated": { + "begin": "\\(", + "captures": { + "1": { + "name": "punctuation.section.scope.perl" + } + }, + "end": "\\)", + "patterns": [ + { + "comment": "This is to prevent thinks like qr/foo$/ to treat $/ as a variable", + "match": "\\$(?=[^\\s\\w'\\{\\[\\(\\<])", + "name": "keyword.control.anchor.perl" + }, + { + "include": "#escaped_char" + }, + { + "include": "#variable" + }, + { + "include": "#nested_parens_interpolated" + } + ] + }, + "pod": { + "patterns": [ + { + "match": "^=(pod|back|cut)\\b", + "name": "storage.type.class.pod.perl" + }, + { + "begin": "^(=begin)\\s+(html)\\s*$", + "beginCaptures": { + "1": { + "name": "storage.type.class.pod.perl" + }, + "2": { + "name": "variable.other.pod.perl" + } + }, + "contentName": "text.embedded.html.basic", + "end": "^(=end)\\s+(html)|^(?==cut)", + "endCaptures": { + "1": { + "name": "storage.type.class.pod.perl" + }, + "2": { + "name": "variable.other.pod.perl" + } + }, + "name": "meta.embedded.pod.perl", + "patterns": [ + { + "include": "text.html.basic" + } + ] + }, + { + "captures": { + "1": { + "name": "storage.type.class.pod.perl" + }, + "2": { + "name": "variable.other.pod.perl", + "patterns": [ + { + "include": "#pod-formatting" + } + ] + } + }, + "match": "^(=(?:head[1-4]|item|over|encoding|begin|end|for))\\b\\s*(.*)" + }, + { + "include": "#pod-formatting" + } + ] + }, + "pod-formatting": { + "patterns": [ + { + "captures": { + "1": { + "name": "markup.italic.pod.perl" + }, + "2": { + "name": "markup.italic.pod.perl" + } + }, + "match": "I(?:<([^<>]+)>|<+(\\s+(?:(?|[^>])+\\s+)>+)", + "name": "entity.name.type.instance.pod.perl" + }, + { + "captures": { + "1": { + "name": "markup.bold.pod.perl" + }, + "2": { + "name": "markup.bold.pod.perl" + } + }, + "match": "B(?:<([^<>]+)>|<+(\\s+(?:(?|[^>])+\\s+)>+)", + "name": "entity.name.type.instance.pod.perl" + }, + { + "captures": { + "1": { + "name": "markup.raw.pod.perl" + }, + "2": { + "name": "markup.raw.pod.perl" + } + }, + "match": "C(?:<([^<>]+)>|<+(\\\\s+(?:(?|[^>])+\\\\s+)>+)", + "name": "entity.name.type.instance.pod.perl" + }, + { + "captures": { + "1": { + "name": "markup.underline.link.hyperlink.pod.perl" + } + }, + "match": "L<([^>]+)>", + "name": "entity.name.type.instance.pod.perl" + }, + { + "match": "[EFSXZ]<[^>]*>", + "name": "entity.name.type.instance.pod.perl" + } + ] + }, + "variable": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)&(?![A-Za-z0-9_])", + "name": "variable.other.regexp.match.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)`(?![A-Za-z0-9_])", + "name": "variable.other.regexp.pre-match.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)'(?![A-Za-z0-9_])", + "name": "variable.other.regexp.post-match.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)\\+(?![A-Za-z0-9_])", + "name": "variable.other.regexp.last-paren-match.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)\"(?![A-Za-z0-9_])", + "name": "variable.other.readwrite.list-separator.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)0(?![A-Za-z0-9_])", + "name": "variable.other.predefined.program-name.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)[_ab\\*\\.\\/\\|,\\\\;#%=\\-~^:?!\\$<>\\(\\)\\[\\]@](?![A-Za-z0-9_])", + "name": "variable.other.predefined.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$)[0-9]+(?![A-Za-z0-9_])", + "name": "variable.other.subpattern.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "([\\$\\@\\%](#)?)([a-zA-Zx7f-xff\\$]|::)([a-zA-Z0-9_x7f-xff\\$]|::)*\\b", + "name": "variable.other.readwrite.global.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + }, + "2": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "(\\$\\{)(?:[a-zA-Zx7f-xff\\$]|::)(?:[a-zA-Z0-9_x7f-xff\\$]|::)*(\\})", + "name": "variable.other.readwrite.global.perl" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.variable.perl" + } + }, + "match": "([\\$\\@\\%](#)?)[0-9_]\\b", + "name": "variable.other.readwrite.global.special.perl" + } + ] + } + }, + "scopeName": "source.perl", + "uuid": "EDBFE125-6B1C-11D9-9189-000D93589AF6", + "version": "https://github.com/textmate/perl.tmbundle/commit/c0b7a4bd65882380522d82a60b536479a62b07c3" +} \ No newline at end of file diff --git a/extensions/perl/syntaxes/perl6.tmLanguage.json b/extensions/perl/syntaxes/perl6.tmLanguage.json new file mode 100644 index 00000000000..a3024a11ae7 --- /dev/null +++ b/extensions/perl/syntaxes/perl6.tmLanguage.json @@ -0,0 +1,319 @@ +{ + "fileTypes": [ + "p6", + "pl6", + "pm6", + "nqp" + ], + "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/|(?<=!)\\b)(?:perl6|nqp)(?:$|\\s)\n|\n# Perl 6 pragma\n\\buse\\s+v6\\b\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:(?:(?!mode\\s*:)[^:;]+:[^:;]+;)*\\s*mode\\s*:)?\\s*\n\tperl6\n \\s*(?:;[^:;]+:[^:;]+?)*;?\\s*-\\*-\n |\n # Vim\n (?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|(?!^)\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n\tperl6\n (?=\\s|:|$)\n)", + "keyEquivalent": "^~P", + "name": "Perl 6", + "patterns": [ + { + "begin": "^=begin", + "end": "^=end", + "name": "comment.block.perl" + }, + { + "begin": "(^[ \\t]+)?(?=#)", + "beginCaptures": { + "1": { + "name": "punctuation.whitespace.comment.leading.perl" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "begin": "#", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.perl" + } + }, + "end": "\\n", + "name": "comment.line.number-sign.perl" + } + ] + }, + { + "captures": { + "1": { + "name": "storage.type.class.perl.6" + }, + "3": { + "name": "entity.name.type.class.perl.6" + } + }, + "match": "(class|enum|grammar|knowhow|module|package|role|slang|subset)(\\s+)(((?:::|')?(?:([a-zA-Z_\\x{C0}-\\x{FF}\\$])([a-zA-Z0-9_\\x{C0}-\\x{FF}\\\\$]|[\\-'][a-zA-Z0-9_\\x{C0}-\\x{FF}\\$])*))+)", + "name": "meta.class.perl.6" + }, + { + "begin": "(?<=\\s)'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.single.perl", + "patterns": [ + { + "match": "\\\\['\\\\]", + "name": "constant.character.escape.perl" + } + ] + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.perl" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.perl" + } + }, + "name": "string.quoted.double.perl", + "patterns": [ + { + "match": "\\\\[abtnfre\"\\\\]", + "name": "constant.character.escape.perl" + } + ] + }, + { + "begin": "q(q|to|heredoc)*\\s*:?(q|to|heredoc)*\\s*/(.+)/", + "end": "\\3", + "name": "string.quoted.single.heredoc.perl" + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*{{", + "end": "}}", + "name": "string.quoted.double.heredoc.brace.perl", + "patterns": [ + { + "include": "#qq_brace_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*\\(\\(", + "end": "\\)\\)", + "name": "string.quoted.double.heredoc.paren.perl", + "patterns": [ + { + "include": "#qq_paren_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*\\[\\[", + "end": "\\]\\]", + "name": "string.quoted.double.heredoc.bracket.perl", + "patterns": [ + { + "include": "#qq_bracket_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*{", + "end": "}", + "name": "string.quoted.single.heredoc.brace.perl", + "patterns": [ + { + "include": "#qq_brace_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*/", + "end": "/", + "name": "string.quoted.single.heredoc.slash.perl", + "patterns": [ + { + "include": "#qq_slash_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*\\(", + "end": "\\)", + "name": "string.quoted.single.heredoc.paren.perl", + "patterns": [ + { + "include": "#qq_paren_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*\\[", + "end": "\\]", + "name": "string.quoted.single.heredoc.bracket.perl", + "patterns": [ + { + "include": "#qq_bracket_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*'", + "end": "'", + "name": "string.quoted.single.heredoc.single.perl", + "patterns": [ + { + "include": "#qq_single_string_content" + } + ] + }, + { + "begin": "(q|Q)(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*:?(x|exec|w|words|ww|quotewords|v|val|q|single|qq|double|s|scalar|a|array|h|hash|f|function|c|closure|b|blackslash|regexp|substr|trans|codes|p|path)*\\s*\"", + "end": "\"", + "name": "string.quoted.single.heredoc.double.perl", + "patterns": [ + { + "include": "#qq_double_string_content" + } + ] + }, + { + "match": "\\b\\$\\w+\\b", + "name": "variable.other.perl" + }, + { + "match": "\\b(macro|sub|submethod|method|multi|proto|only|rule|token|regex|category)\\b", + "name": "storage.type.declare.routine.perl" + }, + { + "match": "\\b(self)\\b", + "name": "variable.language.perl" + }, + { + "match": "\\b(use|require)\\b", + "name": "keyword.other.include.perl" + }, + { + "match": "\\b(if|else|elsif|unless)\\b", + "name": "keyword.control.conditional.perl" + }, + { + "match": "\\b(let|my|our|state|temp|has|constant)\\b", + "name": "storage.type.variable.perl" + }, + { + "match": "\\b(for|loop|repeat|while|until|gather|given)\\b", + "name": "keyword.control.repeat.perl" + }, + { + "match": "\\b(take|do|when|next|last|redo|return|contend|maybe|defer|default|exit|make|continue|break|goto|leave|async|lift)\\b", + "name": "keyword.control.flowcontrol.perl" + }, + { + "match": "\\b(is|as|but|trusts|of|returns|handles|where|augment|supersede)\\b", + "name": "storage.modifier.type.constraints.perl" + }, + { + "match": "\\b(BEGIN|CHECK|INIT|START|FIRST|ENTER|LEAVE|KEEP|UNDO|NEXT|LAST|PRE|POST|END|CATCH|CONTROL|TEMP)\\b", + "name": "meta.function.perl" + }, + { + "match": "\\b(die|fail|try|warn)\\b", + "name": "keyword.control.control-handlers.perl" + }, + { + "match": "\\b(prec|irs|ofs|ors|export|deep|binary|unary|reparsed|rw|parsed|cached|readonly|defequiv|will|ref|copy|inline|tighter|looser|equiv|assoc|required)\\b", + "name": "storage.modifier.perl" + }, + { + "match": "\\b(NaN|Inf)\\b", + "name": "constant.numeric.perl" + }, + { + "match": "\\b(oo|fatal)\\b", + "name": "keyword.other.pragma.perl" + }, + { + "match": "\\b(Object|Any|Junction|Whatever|Capture|MatchSignature|Proxy|Matcher|Package|Module|ClassGrammar|Scalar|Array|Hash|KeyHash|KeySet|KeyBagPair|List|Seq|Range|Set|Bag|Mapping|Void|UndefFailure|Exception|Code|Block|Routine|Sub|MacroMethod|Submethod|Regex|Str|str|Blob|Char|ByteCodepoint|Grapheme|StrPos|StrLen|Version|NumComplex|num|complex|Bit|bit|bool|True|FalseIncreasing|Decreasing|Ordered|Callable|AnyCharPositional|Associative|Ordering|KeyExtractorComparator|OrderingPair|IO|KitchenSink|RoleInt|int|int1|int2|int4|int8|int16|int32|int64Rat|rat|rat1|rat2|rat4|rat8|rat16|rat32|rat64Buf|buf|buf1|buf2|buf4|buf8|buf16|buf32|buf64UInt|uint|uint1|uint2|uint4|uint8|uint16|uint32uint64|Abstraction|utf8|utf16|utf32)\\b", + "name": "support.type.perl6" + }, + { + "match": "\\b(div|xx|x|mod|also|leg|cmp|before|after|eq|ne|le|lt|not|gt|ge|eqv|ff|fff|and|andthen|or|xor|orelse|extra|lcm|gcd)\\b", + "name": "keyword.operator.perl" + }, + { + "match": "(\\$|@|%|&)(\\*|:|!|\\^|~|=|\\?|(<(?=.+>)))?([a-zA-Z_\\x{C0}-\\x{FF}\\$])([a-zA-Z0-9_\\x{C0}-\\x{FF}\\$]|[\\-'][a-zA-Z0-9_\\x{C0}-\\x{FF}\\$])*", + "name": "variable.other.identifier.perl.6" + }, + { + "match": "\\b(eager|hyper|substr|index|rindex|grep|map|sort|join|lines|hints|chmod|split|reduce|min|max|reverse|truncate|zip|cat|roundrobin|classify|first|sum|keys|values|pairs|defined|delete|exists|elems|end|kv|any|all|one|wrap|shape|key|value|name|pop|push|shift|splice|unshift|floor|ceiling|abs|exp|log|log10|rand|sign|sqrt|sin|cos|tan|round|strand|roots|cis|unpolar|polar|atan2|pick|chop|p5chop|chomp|p5chomp|lc|lcfirst|uc|ucfirst|capitalize|normalize|pack|unpack|quotemeta|comb|samecase|sameaccent|chars|nfd|nfc|nfkd|nfkc|printf|sprintf|caller|evalfile|run|runinstead|nothing|want|bless|chr|ord|gmtime|time|eof|localtime|gethost|getpw|chroot|getlogin|getpeername|kill|fork|wait|perl|graphs|codes|bytes|clone|print|open|read|write|readline|say|seek|close|opendir|readdir|slurp|spurt|shell|run|pos|fmt|vec|link|unlink|symlink|uniq|pair|asin|atan|sec|cosec|cotan|asec|acosec|acotan|sinh|cosh|tanh|asinh|done|acos|acosh|atanh|sech|cosech|cotanh|sech|acosech|acotanh|asech|ok|nok|plan_ok|dies_ok|lives_ok|skip|todo|pass|flunk|force_todo|use_ok|isa_ok|diag|is_deeply|isnt|like|skip_rest|unlike|cmp_ok|eval_dies_ok|nok_error|eval_lives_ok|approx|is_approx|throws_ok|version_lt|plan|EVAL|succ|pred|times|nonce|once|signature|new|connect|operator|undef|undefine|sleep|from|to|infix|postfix|prefix|circumfix|postcircumfix|minmax|lazy|count|unwrap|getc|pi|e|context|void|quasi|body|each|contains|rewinddir|subst|can|isa|flush|arity|assuming|rewind|callwith|callsame|nextwith|nextsame|attr|eval_elsewhere|none|srand|trim|trim_start|trim_end|lastcall|WHAT|WHERE|HOW|WHICH|VAR|WHO|WHENCE|ACCEPTS|REJECTS|not|true|iterator|by|re|im|invert|flip|gist|flat|tree|is-prime|throws_like|trans)\\b", + "name": "support.function.perl" + } + ], + "repository": { + "qq_brace_string_content": { + "begin": "{", + "end": "}", + "patterns": [ + { + "include": "#qq_brace_string_content" + } + ] + }, + "qq_bracket_string_content": { + "begin": "\\[", + "end": "\\]", + "patterns": [ + { + "include": "#qq_bracket_string_content" + } + ] + }, + "qq_double_string_content": { + "begin": "\"", + "end": "\"", + "patterns": [ + { + "include": "#qq_double_string_content" + } + ] + }, + "qq_paren_string_content": { + "begin": "\\(", + "end": "\\)", + "patterns": [ + { + "include": "#qq_paren_string_content" + } + ] + }, + "qq_single_string_content": { + "begin": "'", + "end": "'", + "patterns": [ + { + "include": "#qq_single_string_content" + } + ] + }, + "qq_slash_string_content": { + "begin": "\\\\/", + "end": "\\\\/", + "patterns": [ + { + "include": "#qq_slash_string_content" + } + ] + } + }, + "scopeName": "source.perl.6", + "uuid": "E685440C-0E20-4424-9693-864D5240A269", + "version": "https://github.com/textmate/perl.tmbundle/commit/d9841a0878239fa43f88c640f8d458590f97e8f5" +} \ No newline at end of file diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index f7e58ccea08..a73e2654255 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -9,26 +9,31 @@ "contributes": { "themes": [ { + "id": "Default Dark+", "label": "Dark+ (default dark)", "uiTheme": "vs-dark", "path": "./themes/dark_plus.json" }, { + "id": "Default Light+", "label": "Light+ (default light)", "uiTheme": "vs", "path": "./themes/light_plus.json" }, { + "id": "Visual Studio Dark", "label": "Dark (Visual Studio)", "uiTheme": "vs-dark", "path": "./themes/dark_vs.json" }, { + "id": "Visual Studio Light", "label": "Light (Visual Studio)", "uiTheme": "vs", "path": "./themes/light_vs.json" }, { + "id": "Default High Contrast", "label": "High Contrast", "uiTheme": "hc-black", "path": "./themes/hc_black.json" diff --git a/extensions/typescript/npm-shrinkwrap.json b/extensions/typescript/npm-shrinkwrap.json index d9e5051ab11..edee59d0b14 100644 --- a/extensions/typescript/npm-shrinkwrap.json +++ b/extensions/typescript/npm-shrinkwrap.json @@ -13,9 +13,9 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" }, "typescript": { - "version": "typescript@2.2.1-insiders.20170209", - "from": "typescript@typescript@2.2.1-insiders.20170209", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.1-insiders.20170209.tgz" + "version": "typescript@2.2.1-insiders.20170216", + "from": "typescript@typescript@2.2.1-insiders.20170216", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.2.1-insiders.20170216.tgz" }, "vscode-extension-telemetry": { "version": "0.0.5", diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 415686fe9c9..b4d23cabe14 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -14,7 +14,7 @@ "semver": "4.3.6", "vscode-extension-telemetry": "^0.0.5", "vscode-nls": "^2.0.1", - "typescript": "typescript@2.2.1-insiders.20170209" + "typescript": "typescript@2.2.1-insiders.20170216" }, "devDependencies": { "@types/node": "^7.0.4", @@ -33,7 +33,12 @@ "onLanguage:jsx-tags", "onCommand:typescript.reloadProjects", "onCommand:javascript.reloadProjects", - "onCommand:typescript.selectTypeScriptVersion" + "onCommand:typescript.selectTypeScriptVersion", + "onCommand:javascript.goToProjectConfig", + "onCommand:typescript.goToProjectConfig", + "onCommand:_typescript.tryCompleteJsDoc", + "workspaceContains:jsconfig.json", + "workspaceContains:tsconfig.json" ], "main": "./out/typescriptMain", "enableProposedApi": true, @@ -79,6 +84,18 @@ } } ], + "keybindings": [ + { + "key": "enter", + "command": "_typescript.tryCompleteJsDoc", + "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'typescript'" + }, + { + "key": "enter", + "command": "_typescript.tryCompleteJsDoc", + "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'javascript'" + } + ], "configuration": { "type": "object", "title": "%configuration.typescript%", @@ -257,15 +274,28 @@ "commands": [ { "command": "typescript.reloadProjects", - "title": "%typescript.reloadProjects.title%" + "title": "%typescript.reloadProjects.title%", + "category": "TypeScript" }, { "command": "javascript.reloadProjects", - "title": "%javascript.reloadProjects.title%" + "title": "%javascript.reloadProjects.title%", + "category": "JavaScript" }, { "command": "typescript.selectTypeScriptVersion", - "title": "%typescript.selectTypeScriptVersion.title%" + "title": "%typescript.selectTypeScriptVersion.title%", + "category": "TypeScript" + }, + { + "command": "typescript.goToProjectConfig", + "title": "%typescript.goToProjectConfig.title%", + "category": "TypeScript" + }, + { + "command": "javascript.goToProjectConfig", + "title": "%javascript.goToProjectConfig.title%", + "category": "JavaScript" } ], "breakpoints": [ @@ -301,4 +331,4 @@ } ] } -} +} \ No newline at end of file diff --git a/extensions/typescript/package.nls.json b/extensions/typescript/package.nls.json index 2d1589b98cf..c33a12dbc21 100644 --- a/extensions/typescript/package.nls.json +++ b/extensions/typescript/package.nls.json @@ -1,6 +1,6 @@ { - "typescript.reloadProjects.title": "Reload TypeScript Project", - "javascript.reloadProjects.title": "Reload JavaScript Project", + "typescript.reloadProjects.title": "Reload Project", + "javascript.reloadProjects.title": "Reload Project", "configuration.typescript": "TypeScript", "typescript.useCodeSnippetsOnMethodSuggest.dec": "Complete functions with their parameter signature.", "typescript.tsdk.desc": "Specifies the folder path containing the tsserver and lib*.d.ts files to use.", @@ -24,6 +24,8 @@ "format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.", "format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.", "javascript.validate.enable": "Enable/disable JavaScript validation.", - "typescript.referencesCodeLens.enabled": "Enable/disable the references code lens.", - "typescript.selectTypeScriptVersion.title": "Select TypeScript version." + "typescript.goToProjectConfig.title": "Go to Project Configuration", + "javascript.goToProjectConfig.title": "Go to Project Configuration", + "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens.", + "typescript.selectTypeScriptVersion.title": "Select TypeScript Version" } \ No newline at end of file diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index d9aaa0cc7e1..8d8ea94e8ba 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -153,8 +153,12 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { if (this.typingsStatus.isAcquiringTypings) { return Promise.reject({ - label: localize('acquiringTypingsLabel', 'Acquiring typings...'), - detail: localize('acquiringTypingsDetail', 'Acquiring typings definitions for IntelliSense.') + label: localize( + { key: 'acquiringTypingsLabel', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] }, + 'Acquiring typings...'), + detail: localize( + { key: 'acquiringTypingsDetail', comment: ['Typings refers to the *.d.ts typings files that power our IntelliSense. It should not be localized'] }, + 'Acquiring typings definitions for IntelliSense.') }); } diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 2fe25d90d57..34d7fd97b4e 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -9,12 +9,13 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri } from 'vscode'; +import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter, Disposable, Uri, MessageItem, TextEditor } from 'vscode'; // This must be the first statement otherwise modules might got loaded with // the wrong locale. import * as nls from 'vscode-nls'; nls.config({ locale: env.language }); +const localize = nls.loadMessageBundle(); import * as path from 'path'; @@ -39,6 +40,7 @@ import WorkspaceSymbolProvider from './features/workspaceSymbolProvider'; import CodeActionProvider from './features/codeActionProvider'; import ReferenceCodeLensProvider from './features/referencesCodeLensProvider'; +import JsDocCompletionHelper from './utils/JsDocCompletionHelper'; import * as BuildStatus from './utils/buildStatus'; import * as ProjectStatus from './utils/projectStatus'; import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'; @@ -52,6 +54,16 @@ interface LanguageDescription { configFile: string; } +enum ProjectConfigAction { + None, + CreateConfig, + LearnMore +} + +interface ProjectConfigMessageItem extends MessageItem { + id: ProjectConfigAction; +} + export function activate(context: ExtensionContext): void { const MODE_ID_TS = 'typescript'; const MODE_ID_TSX = 'typescriptreact'; @@ -89,6 +101,29 @@ export function activate(context: ExtensionContext): void { client.onVersionStatusClicked(); })); + const jsDocCompletionHelper = new JsDocCompletionHelper(client); + context.subscriptions.push(commands.registerCommand('_typescript.tryCompleteJsDoc', () => { + const editor = window.activeTextEditor; + if (!editor || !editor.selection.isEmpty) { + return commands.executeCommand('type', { text: '\n' }); + } + return jsDocCompletionHelper.tryCompleteJsDoc(editor, editor.selection.active).then(didCompleteComment => { + if (didCompleteComment) { + return; + } + return commands.executeCommand('type', { text: '\n' }); + }); + })); + + const goToProjectConfig = (isTypeScript: boolean) => { + const editor = window.activeTextEditor; + if (editor) { + clientHost.goToProjectConfig(isTypeScript, editor.document.uri, editor.document.languageId); + } + }; + context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true))); + context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false))); + window.onDidChangeActiveTextEditor(VersionStatus.showHideStatus, null, context.subscriptions); client.onReady().then(() => { context.subscriptions.push(ProjectStatus.create(client, @@ -378,6 +413,76 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { return !!this.findLanguage(file); } + public goToProjectConfig( + isTypeScriptProject: boolean, + resource: Uri, + languageId: string + ): Thenable | undefined { + const rootPath = workspace.rootPath; + if (!rootPath) { + window.showInformationMessage( + localize( + 'typescript.projectConfigNoWorkspace', + 'Please open a folder in VS Code to use a TypeScript or JavaScript project')); + return; + } + + const file = this.client.normalizePath(resource); + // TODO: TSServer errors when 'projectInfo' is invoked on a non js/ts file + if (!file || !this.languagePerId[languageId]) { + window.showWarningMessage( + localize( + 'typescript.projectConfigUnsupportedFile', + 'Could not determine TypeScript or JavaScript project. Unsupported file type')); + return; + } + + return this.client.execute('projectInfo', { file, needFileNameList: false }).then(res => { + if (!res || !res.body) { + return window.showWarningMessage(localize('typescript.projectConfigCouldNotGetInfo', 'Could not determine TypeScript or JavaScript project')); + } + + const { configFileName } = res.body; + if (configFileName && configFileName.indexOf('/dev/null/') !== 0) { + return workspace.openTextDocument(configFileName) + .then(window.showTextDocument); + } + + return window.showInformationMessage( + (isTypeScriptProject + ? localize('typescript.noTypeScriptProjectConfig', 'File is not part of a TypeScript project') + : localize('typescript.noJavaScriptProjectConfig', 'File is not part of a JavaScript project') + ), { + title: isTypeScriptProject + ? localize('typescript.configureTsconfigQuickPick', 'Configure tsconfig.json') + : localize('typescript.configureJsconfigQuickPick', 'Configure jsconfig.json'), + id: ProjectConfigAction.CreateConfig + }, { + title: localize('typescript.projectConfigLearnMore', 'Learn More'), + id: ProjectConfigAction.LearnMore + }).then(selected => { + switch (selected && selected.id) { + case ProjectConfigAction.CreateConfig: + const configFile = Uri.file(path.join(rootPath, isTypeScriptProject ? 'tsconfig.json' : 'jsconfig.json')); + return workspace.openTextDocument(configFile) + .then(undefined, _ => workspace.openTextDocument(configFile.with({ scheme: 'untitled' }))) + .then(window.showTextDocument); + + case ProjectConfigAction.LearnMore: + if (isTypeScriptProject) { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=841896')); + } else { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=759670')); + } + return; + + default: + return Promise.resolve(undefined); + } + }); + }); + } + private findLanguage(file: string): LanguageProvider | null { for (let i = 0; i < this.languages.length; i++) { let language = this.languages[i]; diff --git a/extensions/typescript/src/typescriptService.ts b/extensions/typescript/src/typescriptService.ts index 7316240dc07..fc191c414c1 100644 --- a/extensions/typescript/src/typescriptService.ts +++ b/extensions/typescript/src/typescriptService.ts @@ -103,6 +103,7 @@ export interface ITypescriptServiceClient { execute(command: 'navtree', args: Proto.FileRequestArgs, token?: CancellationToken): Promise; execute(command: 'getCodeFixes', args: Proto.CodeFixRequestArgs, token?: CancellationToken): Promise; execute(command: 'getSupportedCodeFixes', args: null, token?: CancellationToken): Promise; + execute(command: 'docCommentTemplate', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise; // execute(command: 'compileOnSaveAffectedFileList', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise; // execute(command: 'compileOnSaveEmitFile', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise; execute(command: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise; diff --git a/extensions/typescript/src/utils/JsDocCompletionHelper.ts b/extensions/typescript/src/utils/JsDocCompletionHelper.ts new file mode 100644 index 00000000000..4b175aa4df9 --- /dev/null +++ b/extensions/typescript/src/utils/JsDocCompletionHelper.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TextEditor, Position, Range, Selection } from 'vscode'; + +import { ITypescriptServiceClient } from '../typescriptService'; + +import { FileLocationRequestArgs, DocCommandTemplateResponse } from '../protocol'; + +export default class JsDocCompletionHelper { + + constructor( + private client: ITypescriptServiceClient, + ) { } + + public tryCompleteJsDoc(editor: TextEditor, position: Position): Thenable { + const file = this.client.normalizePath(editor.document.uri); + if (!file) { + return Promise.resolve(false); + } + + const line = editor.document.lineAt(position.line).text; + + // Ensure line starts with '/**' then cursor + const prefix = line.slice(0, position.character).match(/^\s*(\/\*\*+)$/); + if (!prefix) { + return Promise.resolve(false); + } + + // Ensure there is no content after the cursor besides possibly the end of the comment + const suffix = line.slice(position.character).match(/^\s*\**\/?$/); + if (!suffix) { + return Promise.resolve(false); + } + + const start = position.translate(0, -prefix[1].length); + return editor.edit( + edits => { + edits.delete(new Range(start, new Position(start.line, line.length))); + }, { + undoStopBefore: true, + undoStopAfter: false + } + ).then(removedComment => { + if (!removedComment) { + // Edit failed, nothing to revert. + return false; + } + + const args: FileLocationRequestArgs = { + file: file, + line: start.line + 1, + offset: start.character + 1 + }; + + return Promise.race([ + this.client.execute('docCommentTemplate', args), + new Promise((_, reject) => { + setTimeout(reject, 250); + }) + ]).then((res: DocCommandTemplateResponse) => { + if (!res || !res.body) { + return false; + } + const commentText = res.body.newText; + return editor.edit( + edits => edits.insert(start, commentText), + { undoStopBefore: false, undoStopAfter: true }); + }, () => { + return false; + }).then(didInsertComment => { + if (didInsertComment) { + const newCursorPosition = new Position(start.line + 1, editor.document.lineAt(start.line + 1).text.length); + editor.selection = new Selection(newCursorPosition, newCursorPosition); + return true; + } + + // Revert to the original line content and restore position + return editor.edit( + edits => { + edits.insert(start, prefix[1] + suffix[0]); + }, { + undoStopBefore: false, + undoStopAfter: true + } + ).then(() => { + editor.selection = new Selection(position, position); + return false; + }); + }); + }); + } +} \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 55dc1daaf66..9faed92d5a3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -333,19 +333,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, "node-pty": { - "version": "0.6.1", - "from": "node-pty@0.6.1", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.6.1.tgz", + "version": "0.6.2", + "from": "node-pty@0.6.2", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.6.2.tgz", "dependencies": { - "extend": { - "version": "1.2.1", - "from": "extend@>=1.2.1 <1.3.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" - }, "nan": { - "version": "2.2.1", - "from": "nan@2.2.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz" + "version": "2.5.0", + "from": "nan@2.5.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.0.tgz" } } }, @@ -430,9 +425,9 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.0.tgz" }, "xterm": { - "version": "2.2.3", + "version": "2.3.0", "from": "Tyriar/xterm.js#vscode-release/1.10", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#90cd66bf353b86ad52d7b650760d8d879dd1c7b8" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#5513303451202b0135601a2f026602ed391b3906" }, "yauzl": { "version": "2.3.1", diff --git a/package.json b/package.json index 97771c90bb0..c28041756c7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-oss-dev", "version": "1.10.0", "electronVersion": "1.4.6", - "distro": "0edfcc54d5e8654b41e73aacb85adeda2461dcfe", + "distro": "fdc80c6b4d95e5f961bd3f85b927693915550942", "author": { "name": "Microsoft Corporation" }, @@ -31,7 +31,7 @@ "iconv-lite": "0.4.15", "minimist": "1.2.0", "native-keymap": "0.3.0", - "node-pty": "0.6.1", + "node-pty": "0.6.2", "semver": "4.3.6", "vscode-debugprotocol": "1.16.0", "vscode-textmate": "3.1.0", diff --git a/product.json b/product.json index 49114baad2b..49f852e2928 100644 --- a/product.json +++ b/product.json @@ -11,6 +11,7 @@ "win32RegValueName": "CodeOSS", "win32AppId": "{{E34003BB-9E10-4501-8C11-BE3FAA83F23F}", "win32AppUserModelId": "Microsoft.CodeOSS", + "win32ShellNameShort": "C&ode - OSS", "darwinBundleIdentifier": "com.visualstudio.code.oss", "reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new", "urlProtocol": "code-oss" diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index c09d4c6123a..e41ccb92f0f 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -30,10 +30,9 @@ if [ "@@NAME@@" != "code-oss" ]; then APT_DIR=$(get_apt_config_value Dir) APT_ETC=$APT_DIR$(get_apt_config_value Dir::Etc) - APT_SOURCE_PARTS=$APT_ETC$(get_apt_config_value Dir::Etc::sourceparts) - CODE_SOURCE_LIST=$APT_SOURCE_PARTS/visual-studio-@@NAME@@.list + APT_SOURCE_PARTS=$APT_ETC/$(get_apt_config_value Dir::Etc::sourceparts) + CODE_SOURCE_LIST=$APT_SOURCE_PARTS/vscode.list rm -f $CODE_SOURCE_LIST - # TODO: #2973: Enable once the apt repository is signed - #echo "deb @@UPDATEURL@@/api/deb/@@QUALITY@@/@@ARCHITECTURE@@/ /" > $CODE_SOURCE_LIST + # echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > $CODE_SOURCE_LIST fi \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh index 45eb9bb1fcc..9aa16fa3241 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,5 +1,4 @@ #!/bin/bash -set -e if [[ "$OSTYPE" == "darwin"* ]]; then realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } @@ -19,7 +18,7 @@ else fi INTENDED_VERSION="v`node -p "require('./package.json').electronVersion"`" -INSTALLED_VERSION=`cat .build/electron/version 2> /dev/null` +INSTALLED_VERSION=$(cat .build/electron/version 2> /dev/null) # Node modules test -d node_modules || ./scripts/npm.sh install diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 8b9b4088705..53df874292d 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -21,17 +21,12 @@ export interface IListElementEvent { event: E; } +export interface IListEvent { + elements: T[]; + indexes: number[]; +} + export interface IListMouseEvent extends MouseEvent { element: T; index: number; -} - -export interface IFocusChangeEvent { - elements: T[]; - indexes: number[]; -} - -export interface ISelectionChangeEvent { - elements: T[]; - indexes: number[]; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index b5649f39e12..70170ab730b 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; -import { IDelegate, IRenderer, IFocusChangeEvent, ISelectionChangeEvent } from './list'; +import { IDelegate, IRenderer, IListEvent } from './list'; import { List, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import Event, { mapEvent } from 'vs/base/common/event'; @@ -67,7 +67,7 @@ export class PagedList { container: HTMLElement, delegate: IDelegate, renderers: IPagedRenderer[], - options: IListOptions = {} + options: IListOptions = {} // TODO@Joao: should be IListOptions ) { const pagedRenderers = renderers.map(r => new PagedRenderer>(r, () => this.model)); this.list = new List(container, delegate, pagedRenderers, options); @@ -77,11 +77,11 @@ export class PagedList { return this.list; } - get onFocusChange(): Event> { + get onFocusChange(): Event> { return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onSelectionChange(): Event> { + get onSelectionChange(): Event> { return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 7e6fe181712..8cbae824f80 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -13,9 +13,26 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Event, { Emitter, EventBufferer, chain, mapEvent, fromCallback } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list'; +import { IDelegate, IRenderer, IListEvent, IListMouseEvent } from './list'; import { ListView, IListViewOptions } from './listView'; +export interface IIdentityProvider { + (element: T): string; +} + +export interface ISpliceable { + splice(start: number, deleteCount: number, elements: T[]): void; +} + +class CombinedSpliceable implements ISpliceable { + + constructor(private spliceables: ISpliceable[]) { } + + splice(start: number, deleteCount: number, elements: T[]): void { + this.spliceables.forEach(s => s.splice(start, deleteCount, elements)); + } +} + interface ITraitTemplateData { container: HTMLElement; data: D; @@ -52,7 +69,7 @@ class TraitRenderer implements IRenderer> } } -class Trait implements IDisposable { +class Trait implements ISpliceable, IDisposable { private indexes: number[]; @@ -63,21 +80,16 @@ class Trait implements IDisposable { this.indexes = []; } - splice(start: number, deleteCount: number, insertCount: number): void { - const diff = insertCount - deleteCount; + splice(start: number, deleteCount: number, elements: boolean[]): void { + const diff = elements.length - deleteCount; const end = start + deleteCount; - const indexes: number[] = []; + const indexes = [ + ...this.indexes.filter(i => i < start), + ...elements.reduce((r, hasTrait, i) => hasTrait ? [...r, i + start] : r, []), + ...this.indexes.filter(i => i >= end).map(i => i + diff) + ]; - for (let index of indexes) { - if (index >= start && index < end) { - continue; - } - - indexes.push(index > start ? index + diff : index); - } - - this.indexes = indexes; - this._onChange.fire({ indexes }); + this.set(indexes); } renderElement(element: T, index: number, container: HTMLElement): void { @@ -111,14 +123,41 @@ class Trait implements IDisposable { class FocusTrait extends Trait { - constructor(private getElementId: (number: number) => string) { + constructor( + private getDomId: IIdentityProvider + ) { super('focused'); } renderElement(element: T, index: number, container: HTMLElement): void { super.renderElement(element, index, container); container.setAttribute('role', 'treeitem'); - container.setAttribute('id', this.getElementId(index)); + container.setAttribute('id', this.getDomId(index)); + } +} + +/** + * The TraitSpliceable is used as a util class to be able + * to preserve traits across splice calls, given an identity + * provider. + */ +class TraitSpliceable implements ISpliceable { + + constructor( + private trait: Trait, + private view: ListView, + private getId?: IIdentityProvider + ) { } + + splice(start: number, deleteCount: number, elements: T[]): void { + if (!this.getId) { + return this.trait.splice(start, deleteCount, elements.map(e => false)); + } + + const pastElementsWithTrait = this.trait.get().map(i => this.getId(this.view.element(i))); + const elementsWithTrait = elements.map(e => pastElementsWithTrait.indexOf(this.getId(e)) > -1); + + this.trait.splice(start, deleteCount, elementsWithTrait); } } @@ -144,7 +183,7 @@ class KeyboardController implements IDisposable { private onEnter(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.setSelection(this.list.getFocus()); + this.list.open(this.list.getFocus()); } private onUpArrow(e: StandardKeyboardEvent): void { @@ -185,6 +224,7 @@ class KeyboardController implements IDisposable { } class MouseController implements IDisposable { + private disposables: IDisposable[]; constructor( @@ -207,7 +247,7 @@ class MouseController implements IDisposable { e.stopPropagation(); this.view.domNode.focus(); this.list.setFocus([e.index]); - this.list.setSelection([e.index]); + this.list.open([e.index]); } dispose() { @@ -215,18 +255,19 @@ class MouseController implements IDisposable { } } -export interface IListOptions extends IListViewOptions { +export interface IListOptions extends IListViewOptions { + identityProvider?: IIdentityProvider; ariaLabel?: string; mouseSupport?: boolean; keyboardSupport?: boolean; } -const DefaultOptions: IListOptions = { +const DefaultOptions: IListOptions = { keyboardSupport: true, mouseSupport: true }; -export class List implements IDisposable { +export class List implements ISpliceable, IDisposable { private static InstanceCount = 0; private idPrefix = `list_id_${++List.InstanceCount}`; @@ -235,25 +276,26 @@ export class List implements IDisposable { private selection: Trait; private eventBufferer: EventBufferer; private view: ListView; - private mouseController: MouseController; - private keyboardController: KeyboardController; + private spliceable: ISpliceable; private disposables: IDisposable[]; - @memoize - get onFocusChange(): Event> { - return this.eventBufferer.wrapEvent(mapEvent(this.focus.onChange, e => this.toListEvent(e))); + @memoize get onFocusChange(): Event> { + return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); } - @memoize - get onSelectionChange(): Event> { - return this.eventBufferer.wrapEvent(mapEvent(this.selection.onChange, e => this.toListEvent(e))); + @memoize get onSelectionChange(): Event> { + return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } - @memoize - get onContextMenu(): Event> { + @memoize get onContextMenu(): Event> { return fromCallback(handler => this.view.addListener('contextmenu', handler)); } + private _onOpen = new Emitter(); + @memoize get onOpen(): Event> { + return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes })); + } + private _onDOMFocus = new Emitter(); get onDOMFocus(): Event { return this._onDOMFocus.event; } @@ -267,9 +309,9 @@ export class List implements IDisposable { container: HTMLElement, delegate: IDelegate, renderers: IRenderer[], - options: IListOptions = DefaultOptions + options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(i => this.getElementId(i)); + this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); this.eventBufferer = new EventBufferer(); @@ -283,6 +325,12 @@ export class List implements IDisposable { this.view.domNode.setAttribute('role', 'tree'); this.view.domNode.tabIndex = 0; + this.spliceable = new CombinedSpliceable([ + new TraitSpliceable(this.focus, this.view, options.identityProvider), + new TraitSpliceable(this.selection, this.view, options.identityProvider), + this.view + ]); + this.disposables = [this.focus, this.selection, this.view, this._onDispose]; const tracker = DOM.trackFocus(this.view.domNode); @@ -290,13 +338,11 @@ export class List implements IDisposable { this.disposables.push(tracker.addBlurListener(() => this._onDOMBlur.fire())); if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) { - this.keyboardController = new KeyboardController(this, this.view); - this.disposables.push(this.keyboardController); + this.disposables.push(new KeyboardController(this, this.view)); } if (typeof options.mouseSupport !== 'boolean' || options.mouseSupport) { - this.mouseController = new MouseController(this, this.view); - this.disposables.push(this.mouseController); + this.disposables.push(new MouseController(this, this.view)); } this.onFocusChange(this._onFocusChange, this, this.disposables); @@ -307,11 +353,7 @@ export class List implements IDisposable { } splice(start: number, deleteCount: number, elements: T[] = []): void { - this.eventBufferer.bufferEvents(() => { - this.focus.splice(start, deleteCount, elements.length); - this.selection.splice(start, deleteCount, elements.length); - this.view.splice(start, deleteCount, elements); - }); + this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements)); } get length(): number { @@ -461,7 +503,7 @@ export class List implements IDisposable { } } - getElementId(index: number): string { + private getElementDomId(index: number): string { return `${this.idPrefix}_${index}`; } @@ -473,6 +515,11 @@ export class List implements IDisposable { return this.view.domNode; } + open(indexes: number[]): void { + this.setSelection(indexes); + this._onOpen.fire(indexes); + } + private toListEvent({ indexes }: ITraitChangeEvent) { return { indexes, elements: indexes.map(i => this.view.element(i)) }; } @@ -481,7 +528,7 @@ export class List implements IDisposable { const focus = this.focus.get(); if (focus.length > 0) { - this.view.domNode.setAttribute('aria-activedescendant', this.getElementId(focus[0])); + this.view.domNode.setAttribute('aria-activedescendant', this.getElementDomId(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 28b9b368c32..162172f2ad8 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -9,6 +9,7 @@ import { IEventEmitter, EventEmitter } from 'vs/base/common/eventEmitter'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as Events from 'vs/base/common/events'; import Event, { Emitter } from 'vs/base/common/event'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export interface IAction extends IDisposable { id: string; @@ -202,7 +203,7 @@ export class Action implements IAction { this._order = value; } - public run(event?: any): TPromise { + public run(event?: any, data?: ITelemetryData): TPromise { if (this._actionCallback !== void 0) { return this._actionCallback(event); } diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index bbc24c4aa3b..54aaa9dc6dc 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -99,6 +99,17 @@ function hsla2rgba(hsla: HSLA): RGBA { return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a }; } +export function hexToCSS(hex: string) { + if (hex.length === 9) { + return toCSSColor(hex2rgba(hex)); + } + return hex; +} + +function toCSSColor(rgba: RGBA): string { + return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${+rgba.a.toFixed(2)})`; +} + export class Color { private rgba: RGBA; @@ -194,8 +205,7 @@ export class Color { public toString(): string { if (!this.str) { - let p = this.rgba; - this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; + this.str = toCSSColor(this.rgba); } return this.str; } diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts index 317ba7e1394..8323f958dee 100644 --- a/src/vs/base/common/jsonEdit.ts +++ b/src/vs/base/common/jsonEdit.ts @@ -63,7 +63,7 @@ export function setProperty(text: string, path: JSONPath, value: any, formatting } } else { if (value === void 0) { // delete - throw new Error(`Property ${lastSegment} does not exist.`); + return []; // property does not exist, nothing to do } let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; let index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children[0].value)) : parent.children.length; diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index c2e21b77c1b..c0f271d7712 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -21,6 +21,7 @@ import { getCommonHTTPHeaders } from 'vs/platform/environment/node/http'; import { IWindowSettings, MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + export interface IWindowState { width?: number; height?: number; @@ -78,6 +79,7 @@ export interface IWindowConfiguration extends ParsedArgs { zoomLevel?: number; fullscreen?: boolean; highContrast?: boolean; + baseTheme?: string; accessibilitySupport?: boolean; isInitialStartup?: boolean; @@ -134,7 +136,7 @@ export interface IVSCodeWindow { export class VSCodeWindow implements IVSCodeWindow { - public static colorThemeStorageKey = 'theme'; + public static themeStorageKey = 'theme'; private static MIN_WIDTH = 200; private static MIN_HEIGHT = 120; @@ -177,9 +179,9 @@ export class VSCodeWindow implements IVSCodeWindow { this.restoreWindowState(config.state); // For VS theme we can show directly because background is white - const themeId = this.storageService.getItem(VSCodeWindow.colorThemeStorageKey); - const usesLightTheme = /vs($| )/.test(themeId); - const usesHighContrastTheme = /hc-black($| )/.test(themeId) || (platform.isWindows && systemPreferences.isInvertedColorScheme()); + const baseTheme = this.getBaseTheme(); + const usesLightTheme = 'vs' === baseTheme; + const usesHighContrastTheme = 'hc-black' === baseTheme || (platform.isWindows && systemPreferences.isInvertedColorScheme()); // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.currentWindowMode === WindowMode.Maximized || this.currentWindowMode === WindowMode.Fullscreen); @@ -505,6 +507,9 @@ export class VSCodeWindow implements IVSCodeWindow { windowConfiguration.highContrast = platform.isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast); windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled(); + // Theme + windowConfiguration.baseTheme = this.getBaseTheme(); + // Perf Counters windowConfiguration.perfStartTime = global.perfStartTime; windowConfiguration.perfAppReady = global.perfAppReady; @@ -524,6 +529,11 @@ export class VSCodeWindow implements IVSCodeWindow { return url; } + private getBaseTheme(): string { + const theme = this.storageService.getItem(VSCodeWindow.themeStorageKey, 'vs-dark'); + return theme.split(' ')[0]; + } + public serializeWindowState(): IWindowState { if (this.win.isFullScreen()) { return { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index a50e2993692..bbde876c9b3 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -26,6 +26,7 @@ import { ILogService } from 'vs/code/electron-main/log'; import { getPathLabel } from 'vs/base/common/labels'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowSettings } from 'vs/platform/windows/common/windows'; +import { getLastActiveWindow, OpenContext as OriginalOpenContext, findBestWindowOrFolder } from 'vs/code/node/windowsUtils'; import CommonEvent, { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/node/product'; @@ -34,26 +35,8 @@ enum WindowError { CRASHED } -export enum OpenContext { - - // opening when running from the command line - CLI, - - // macOS only: opening from the dock (also when opening files to a running instance from desktop) - DOCK, - - // opening from the main application window - MENU, - - // opening from a file or folder dialog - DIALOG, - - // opening from the OS's UI - DESKTOP, - - // opening through the API - API -} +export const OpenContext = OriginalOpenContext; +export type OpenContext = OriginalOpenContext; export interface IOpenConfiguration { context: OpenContext; @@ -292,10 +275,9 @@ export class WindowsManager implements IWindowsMainService { // Theme changes if (event === 'vscode:changeColorTheme' && typeof payload === 'string') { - this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload); + this.storageService.setItem(VSCodeWindow.themeStorageKey, payload); } } - public reload(win: VSCodeWindow, cli?: ParsedArgs): void { // Only reload when the window has not vetoed this @@ -409,20 +391,28 @@ export class WindowsManager implements IWindowsMainService { } // Open Files in last instance if any and flag tells us so - let bestWindow; - if (!openFilesInNewWindow && (bestWindow = this.findBestWindow(openConfig.context, [...filesToOpen, ...filesToCreate, ...filesToDiff]))) { - bestWindow.focus(); + const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0]; + const windowOrFolder = findBestWindowOrFolder({ + windows: WindowsManager.WINDOWS, + newWindow: openFilesInNewWindow, + reuseWindow: openConfig.forceReuseWindow, + context: openConfig.context, + filePath: fileToCheck && fileToCheck.filePath, + userHome: this.environmentService.userHome + }); + if (windowOrFolder instanceof VSCodeWindow) { + windowOrFolder.focus(); const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after - bestWindow.ready().then(readyWindow => { + windowOrFolder.ready().then(readyWindow => { readyWindow.send('vscode:openFiles', files); }); - usedWindows.push(bestWindow); + usedWindows.push(windowOrFolder); } // Otherwise open instance with files else { - const configuration = this.toConfiguration(openConfig, null, filesToOpen, filesToCreate, filesToDiff); + const configuration = this.toConfiguration(openConfig, windowOrFolder, filesToOpen, filesToCreate, filesToDiff); const browserWindow = this.openInBrowserWindow(configuration, true /* new window */); usedWindows.push(browserWindow); @@ -530,7 +520,7 @@ export class WindowsManager implements IWindowsMainService { const mru = this.getRecentPathsList(); paths.forEach(p => { - const {path, isFile} = p; + const { path, isFile } = p; if (isFile) { mru.files.unshift(path); @@ -1026,31 +1016,8 @@ export class WindowsManager implements IWindowsMainService { return res && res[0]; } - private findBestWindow(context: OpenContext, filePaths: IPath[]): VSCodeWindow { - const findContainer = context === OpenContext.DESKTOP || context === OpenContext.CLI; - return (findContainer && this.findContainingWindow(filePaths)) || this.getLastActiveWindow(); - } - - private findContainingWindow(filePaths: IPath[]): VSCodeWindow { - for (const filePath of filePaths) { - const windows = WindowsManager.WINDOWS.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath.filePath, window.openedWorkspacePath)); - if (windows.length) { - return windows.sort((a, b) => -(a.openedWorkspacePath.length - b.openedWorkspacePath.length))[0]; - } - } - return null; - } - public getLastActiveWindow(): VSCodeWindow { - if (WindowsManager.WINDOWS.length) { - const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime)); - const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate); - if (res && res.length) { - return res[0]; - } - } - - return null; + return getLastActiveWindow(WindowsManager.WINDOWS); } public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow { diff --git a/src/vs/code/node/windowsUtils.ts b/src/vs/code/node/windowsUtils.ts new file mode 100644 index 00000000000..97742a47756 --- /dev/null +++ b/src/vs/code/node/windowsUtils.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as path from 'path'; +import * as fs from 'original-fs'; +import * as platform from 'vs/base/common/platform'; +import * as paths from 'vs/base/common/paths'; + +export enum OpenContext { + + // opening when running from the command line + CLI, + + // macOS only: opening from the dock (also when opening files to a running instance from desktop) + DOCK, + + // opening from the main application window + MENU, + + // opening from a file or folder dialog + DIALOG, + + // opening from the OS's UI + DESKTOP, + + // opening through the API + API +} + +/** + * Exported subset of VSCodeWindow for testing. + */ +export interface ISimpleWindow { + openedWorkspacePath: string; + lastFocusTime: number; +} + +/** + * Exported for testing. + */ +export interface IBestWindowOrFolderOptions { + windows: WINDOW[]; + newWindow: boolean; + reuseWindow: boolean; + context: OpenContext; + filePath?: string; + userHome?: string; + vscodeFolder?: string; +} + +export function findBestWindowOrFolder({ windows, newWindow, reuseWindow, context, filePath, userHome, vscodeFolder }: IBestWindowOrFolderOptions): WINDOW | string { + // OpenContext.DOCK implies newWindow unless overwritten by settings. + const findBest = filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK); + const bestWindow = !newWindow && findBest && findBestWindow(windows, filePath); + const bestFolder = !newWindow && !reuseWindow && findBest && findBestFolder(filePath, userHome, vscodeFolder); + if (bestWindow && !(bestFolder && bestFolder.length > bestWindow.openedWorkspacePath.length)) { + return bestWindow; + } else if (bestFolder) { + return bestFolder; + } + return !newWindow ? getLastActiveWindow(windows) : null; +} + +function findBestWindow(windows: WINDOW[], filePath: string): WINDOW { + const containers = windows.filter(window => typeof window.openedWorkspacePath === 'string' && paths.isEqualOrParent(filePath, window.openedWorkspacePath)); + if (containers.length) { + return containers.sort((a, b) => -(a.openedWorkspacePath.length - b.openedWorkspacePath.length))[0]; + } + return null; +} + +function findBestFolder(filePath: string, userHome?: string, vscodeFolder?: string): string { + let folder = path.dirname(paths.normalize(filePath, true)); + let homeFolder = userHome && paths.normalize(userHome, true); + if (!platform.isLinux) { + homeFolder = homeFolder && homeFolder.toLowerCase(); + } + let previous = null; + try { + while (folder !== previous) { + if (isProjectFolder(folder, homeFolder, vscodeFolder)) { + return folder; + } + previous = folder; + folder = path.dirname(folder); + } + } catch (err) { + // assume impossible to access + } + return null; +} + +function isProjectFolder(folder: string, normalizedUserHome?: string, vscodeFolder = '.vscode') { + try { + if ((platform.isLinux ? folder : folder.toLowerCase()) === normalizedUserHome) { + // ~/.vscode/extensions is used for extensions + return fs.statSync(path.join(folder, vscodeFolder, 'settings.json')).isFile(); + } else { + return fs.statSync(path.join(folder, vscodeFolder)).isDirectory(); + } + } catch (err) { + if (!(err && err.code === 'ENOENT')) { + throw err; + } + } + return false; +} + +export function getLastActiveWindow(windows: WINDOW[]): WINDOW { + if (windows.length) { + const lastFocussedDate = Math.max.apply(Math, windows.map(w => w.lastFocusTime)); + const res = windows.filter(w => w.lastFocusTime === lastFocussedDate); + if (res && res.length) { + return res[0]; + } + } + + return null; +} diff --git a/src/vs/code/test/node/fixtures/no_vscode_folder/file.txt b/src/vs/code/test/node/fixtures/no_vscode_folder/file.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/code/test/node/fixtures/vscode_folder/_vscode/keepfolder.txt b/src/vs/code/test/node/fixtures/vscode_folder/_vscode/keepfolder.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/code/test/node/fixtures/vscode_folder/file.txt b/src/vs/code/test/node/fixtures/vscode_folder/file.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/code/test/node/fixtures/vscode_folder/nested_vscode_folder/_vscode/keepfolder.txt b/src/vs/code/test/node/fixtures/vscode_folder/nested_vscode_folder/_vscode/keepfolder.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/code/test/node/fixtures/vscode_home_folder/_vscode/settings.json b/src/vs/code/test/node/fixtures/vscode_home_folder/_vscode/settings.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/src/vs/code/test/node/fixtures/vscode_home_folder/_vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/vs/code/test/node/fixtures/vscode_home_folder/file.txt b/src/vs/code/test/node/fixtures/vscode_home_folder/file.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/code/test/node/windowsUtils.test.ts b/src/vs/code/test/node/windowsUtils.test.ts new file mode 100644 index 00000000000..7ca91ddadff --- /dev/null +++ b/src/vs/code/test/node/windowsUtils.test.ts @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import assert = require('assert'); +import path = require('path'); +import { findBestWindowOrFolder, OpenContext, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsUtils'; + +const fixturesFolder = require.toUrl('./fixtures'); + +function options(custom?: Partial>): IBestWindowOrFolderOptions { + return { + windows: [], + newWindow: false, + reuseWindow: false, + context: OpenContext.CLI, + vscodeFolder: '_vscode', + ...custom + }; +} + +const vscodeFolderWindow = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder') }; +const lastActiveWindow = { lastFocusTime: 3, openedWorkspacePath: null }; +const noVscodeFolderWindow = { lastFocusTime: 2, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder') }; +const windows = [ + vscodeFolderWindow, + lastActiveWindow, + noVscodeFolderWindow, +]; + +suite('WindowsUtils', () => { + + test('New window without folder when no windows exist', () => { + assert.equal(findBestWindowOrFolder(options()), null); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt') + })), null); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), + newWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later. + })), null); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), + reuseWindow: true // We assume this implies 'editor' work mode, might need separate CLI option later. + })), null); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), + context: OpenContext.API + })), null); + }); + + test('New window with folder when no windows exist', () => { + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt') + })), path.join(fixturesFolder, 'vscode_folder')); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt') + })), path.join(fixturesFolder, 'vscode_folder')); + }); + + test('New window without folder when windows exist', () => { + assert.equal(findBestWindowOrFolder(options({ + windows, + filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'), + newWindow: true + })), null); + }); + + test('Last active window', () => { + assert.equal(findBestWindowOrFolder(options({ + windows + })), lastActiveWindow); + assert.equal(findBestWindowOrFolder(options({ + windows, + filePath: path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt') + })), lastActiveWindow); + assert.equal(findBestWindowOrFolder(options({ + windows: [lastActiveWindow, noVscodeFolderWindow], + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), + reuseWindow: true + })), lastActiveWindow); + assert.equal(findBestWindowOrFolder(options({ + windows, + filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'), + context: OpenContext.API + })), lastActiveWindow); + }); + + test('Existing window with folder', () => { + assert.equal(findBestWindowOrFolder(options({ + windows, + filePath: path.join(fixturesFolder, 'no_vscode_folder', 'file.txt') + })), noVscodeFolderWindow); + assert.equal(findBestWindowOrFolder(options({ + windows, + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt') + })), vscodeFolderWindow); + }); + + test('Existing window wins over vscode folder if more specific', () => { + const window = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder') }; + assert.equal(findBestWindowOrFolder(options({ + windows: [window], + filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt') + })), window); + // check + assert.equal(findBestWindowOrFolder(options({ + windows: [window], + filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder2', 'subfolder', 'file.txt') + })), path.join(fixturesFolder, 'vscode_folder')); + }); + + test('More specific existing window wins', () => { + const window = { lastFocusTime: 2, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder') }; + const nestedFolderWindow = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder') }; + assert.equal(findBestWindowOrFolder(options({ + windows: [window, nestedFolderWindow], + filePath: path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt') + })), nestedFolderWindow); + }); + + test('VSCode folder wins over existing window if more specific', () => { + const window = { lastFocusTime: 1, openedWorkspacePath: path.join(fixturesFolder, 'vscode_folder') }; + assert.equal(findBestWindowOrFolder(options({ + windows: [window], + filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt') + })), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder')); + // check + assert.equal(findBestWindowOrFolder(options({ + windows: [window], + filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt') + })), window); + }); + + test('More specific VSCode folder wins', () => { + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder', 'subfolder', 'file.txt') + })), path.join(fixturesFolder, 'vscode_folder', 'nested_vscode_folder')); + }); + + test('VSCode folder in home folder needs settings.json', () => { + // Because ~/.vscode/extensions is used for extensions, ~/.vscode is not enough as a hint. + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_folder', 'file.txt'), + userHome: path.join(fixturesFolder, 'vscode_folder') + })), null); + assert.equal(findBestWindowOrFolder(options({ + filePath: path.join(fixturesFolder, 'vscode_home_folder', 'file.txt'), + userHome: path.join(fixturesFolder, 'vscode_home_folder') + })), path.join(fixturesFolder, 'vscode_home_folder')); + }); +}); diff --git a/src/vs/editor/browser/controller/input/textAreaWrapper.ts b/src/vs/editor/browser/controller/input/textAreaWrapper.ts index c9c2efa502f..7f46c9633cc 100644 --- a/src/vs/editor/browser/controller/input/textAreaWrapper.ts +++ b/src/vs/editor/browser/controller/input/textAreaWrapper.ts @@ -29,9 +29,12 @@ class ClipboardEventWrapper implements IClipboardEvent { return false; } - public setTextData(text: string): void { + public setTextData(text: string, richText: string): void { if (this._event.clipboardData) { this._event.clipboardData.setData('text/plain', text); + if (richText !== null) { + this._event.clipboardData.setData('text/html', richText); + } this._event.preventDefault(); return; } diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 894a39915b5..5a570fa542e 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -28,8 +28,7 @@ import { IDisposable, IReference, ImmortalReference, combinedDisposable } from ' import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { values } from 'vs/base/common/collections'; -import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { Menu } from 'vs/platform/actions/common/menu'; import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; @@ -428,10 +427,6 @@ export class SimpleMenuService implements IMenuService { public createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { return new Menu(id, TPromise.as(true), this._commandService, contextKeyService); } - - public getCommandActions(): ICommandAction[] { - return values(MenuRegistry.commands); - } } export class StandaloneTelemetryService implements ITelemetryService { diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 914e55b30c3..0b366dbd89e 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -1006,6 +1006,10 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom protected abstract _registerDecorationType(key: string, options: editorCommon.IDecorationRenderOptions, parentTypeKey?: string): void; protected abstract _removeDecorationType(key: string): void; protected abstract _resolveDecorationOptions(typeKey: string, writable: boolean): editorCommon.IModelDecorationOptions; + + public getTelemetryData(): { [key: string]: any; } { + return null; + } } class EditorContextKeysManager extends Disposable { diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 72cdd980e1a..6382434f530 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -11,8 +11,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState } from 'vs/editor/common/controller/textAreaState'; import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { EndOfLinePreference } from 'vs/editor/common/editorCommon'; +import { InternalEditorOptions } from 'vs/editor/common/editorCommon'; const enum ReadFromTextArea { Type, @@ -324,9 +323,13 @@ export class TextAreaHandler extends Disposable { // ------------- Clipboard operations private _ensureClipboardGetsEditorSelection(e: IClipboardEvent): void { - let whatToCopy = this._getPlainTextToCopy(); + let whatToCopy = this.model.getPlainTextToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); if (e.canUseTextData()) { - e.setTextData(whatToCopy); + let whatHTMLToCopy = null; + if (!this.Browser.isEdgeOrIE && (whatToCopy.length < 65536 || InternalEditorOptions.forceCopyWithSyntaxHighlighting)) { + whatHTMLToCopy = this.model.getHTMLToCopy(this.selections, this.Browser.enableEmptySelectionClipboard); + } + e.setTextData(whatToCopy, whatHTMLToCopy); } else { this.setTextAreaState('copy or cut', this.textAreaState.fromText(whatToCopy), false); } @@ -344,31 +347,4 @@ export class TextAreaHandler extends Disposable { this.lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty()); } } - - private _getPlainTextToCopy(): string { - let newLineCharacter = this.model.getEOL(); - let selections = this.selections; - - if (selections.length === 1) { - let range: Range = selections[0]; - if (range.isEmpty()) { - if (this.Browser.enableEmptySelectionClipboard) { - let modelLineNumber = this.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; - return this.model.getModelLineContent(modelLineNumber) + newLineCharacter; - } else { - return ''; - } - } - - return this.model.getValueInRange(range, EndOfLinePreference.TextDefined); - } else { - selections = selections.slice(0).sort(Range.compareRangesUsingStarts); - let result: string[] = []; - for (let i = 0; i < selections.length; i++) { - result.push(this.model.getValueInRange(selections[i], EndOfLinePreference.TextDefined)); - } - - return result.join(newLineCharacter); - } - } -} +} \ No newline at end of file diff --git a/src/vs/editor/common/controller/textAreaState.ts b/src/vs/editor/common/controller/textAreaState.ts index b73257f4170..7f121f04ade 100644 --- a/src/vs/editor/common/controller/textAreaState.ts +++ b/src/vs/editor/common/controller/textAreaState.ts @@ -13,7 +13,7 @@ import { Constants } from 'vs/editor/common/core/uint'; export interface IClipboardEvent { canUseTextData(): boolean; - setTextData(text: string): void; + setTextData(text: string, richText: string): void; getTextData(): string; } @@ -56,6 +56,8 @@ export interface ISimpleModel { getValueInRange(range: Range, eol: EndOfLinePreference): string; getModelLineContent(lineNumber: number): string; getLineCount(): number; + getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; + getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; coordinatesConverter: { convertViewPositionToModelPosition(viewPosition: Position): Position; diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index a09bbfe14e6..b32491438f4 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -1057,6 +1057,10 @@ export class EditorContribOptions { * Internal configuration options (transformed or computed) for the editor. */ export class InternalEditorOptions { + /** + * @internal + */ + static forceCopyWithSyntaxHighlighting: boolean = false; readonly _internalEditorOptionsBrand: void; readonly lineHeight: number; // todo: move to fontInfo @@ -4122,6 +4126,11 @@ export interface ICommonCodeEditor extends IEditor { * Get the layout info for the editor. */ getLayoutInfo(): EditorLayoutInfo; + + /** + * @internal + */ + getTelemetryData(): { [key: string]: any; }; } export interface ICommonDiffEditor extends IEditor { diff --git a/src/vs/editor/common/editorCommonExtensions.ts b/src/vs/editor/common/editorCommonExtensions.ts index f31cc29e5dc..6278efb3461 100644 --- a/src/vs/editor/common/editorCommonExtensions.ts +++ b/src/vs/editor/common/editorCommonExtensions.ts @@ -62,12 +62,12 @@ export abstract class EditorAction extends ConfigEditorCommand { } public runEditorCommand(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: any): void | TPromise { - this.reportTelemetry(accessor); + this.reportTelemetry(accessor, editor); return this.run(accessor, editor, args); } - protected reportTelemetry(accessor: ServicesAccessor) { - accessor.get(ITelemetryService).publicLog('editorActionInvoked', { name: this.label, id: this.id }); + protected reportTelemetry(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor) { + accessor.get(ITelemetryService).publicLog('editorActionInvoked', { name: this.label, id: this.id, ...editor.getTelemetryData() }); } public abstract run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: any): void | TPromise; diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index 9ad94baf5e7..cf1d060c186 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -8,11 +8,101 @@ import * as strings from 'vs/base/common/strings'; import { IState, ITokenizationSupport, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { CharCode } from 'vs/base/common/charCode'; +import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; export function tokenizeToString(text: string, languageId: string): string { return _tokenizeToString(text, _getSafeTokenizationSupport(languageId)); } +export function tokenizeLineToHTML(text: string, viewLineTokens: ViewLineToken[], rules: { [key: string]: string }, options: { startOffset: number, endOffset: number, tabSize: number }): string { + let tabSize = options.tabSize; + let result = `
`; + let charIndex = options.startOffset; + let tabsCharDelta = 0; + + for (let tokenIndex = 0, lenJ = viewLineTokens.length; tokenIndex < lenJ; tokenIndex++) { + const token = viewLineTokens[tokenIndex]; + const tokenEndIndex = token.endIndex; + + if (token.endIndex < options.startOffset) { + continue; + } + + const tokenType = token.type; + let partContentCnt = 0; + let partContent = ''; + + for (; charIndex < tokenEndIndex && charIndex < options.endOffset; charIndex++) { + const charCode = text.charCodeAt(charIndex); + + switch (charCode) { + case CharCode.Tab: + let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; + tabsCharDelta += insertSpacesCount - 1; + while (insertSpacesCount > 0) { + partContent += ' '; + partContentCnt++; + insertSpacesCount--; + } + break; + + case CharCode.Space: + partContent += ' '; + partContentCnt++; + break; + + case CharCode.LessThan: + partContent += '<'; + partContentCnt++; + break; + + case CharCode.GreaterThan: + partContent += '>'; + partContentCnt++; + break; + + case CharCode.Ampersand: + partContent += '&'; + partContentCnt++; + break; + + case CharCode.Null: + partContent += '�'; + partContentCnt++; + break; + + case CharCode.UTF8_BOM: + case CharCode.LINE_SEPARATOR_2028: + partContent += '\ufffd'; + partContentCnt++; + break; + + case CharCode.CarriageReturn: + // zero width space, because carriage return would introduce a line break + partContent += '​'; + partContentCnt++; + break; + + default: + partContent += String.fromCharCode(charCode); + partContentCnt++; + } + } + + // TODO: adopt new view line tokens. + let style = tokenType.split(' ').map(type => rules[type]).join(''); + result += `${partContent}`; + + if (token.endIndex > options.endOffset) { + break; + } + } + + result += `
`; + return result; +} + function _getSafeTokenizationSupport(languageId: string): ITokenizationSupport { let tokenizationSupport = TokenizationRegistry.get(languageId); if (tokenizationSupport) { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 9ce04658699..e513984f268 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -88,6 +88,9 @@ export interface IViewModel extends IEventEmitter { getModelLineContent(modelLineNumber: number): string; getModelLineMaxColumn(modelLineNumber: number): number; validateModelPosition(modelPosition: IPosition): Position; + + getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; + getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string; } export class MinimapLinesRenderingData { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 1793f09a553..d258214b220 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -11,6 +11,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ViewModelCursors } from 'vs/editor/common/viewModel/viewModelCursors'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; @@ -566,4 +568,99 @@ export class ViewModel extends EventEmitter implements IViewModel { public validateModelPosition(position: editorCommon.IPosition): Position { return this.model.validatePosition(position); } + + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + let newLineCharacter = this.getEOL(); + + if (ranges.length === 1) { + let range: Range = ranges[0]; + if (range.isEmpty()) { + if (enableEmptySelectionClipboard) { + let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + return this.getModelLineContent(modelLineNumber) + newLineCharacter; + } else { + return ''; + } + } + + return this.getValueInRange(range, editorCommon.EndOfLinePreference.TextDefined); + } else { + ranges = ranges.slice(0).sort(Range.compareRangesUsingStarts); + let result: string[] = []; + for (let i = 0; i < ranges.length; i++) { + result.push(this.getValueInRange(ranges[i], editorCommon.EndOfLinePreference.TextDefined)); + } + + return result.join(newLineCharacter); + } + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + // TODO: adopt new view line tokens. + let rules: { [key: string]: string } = {}; + let colorMap = TokenizationRegistry.getColorMap(); + for (let i = 1, len = colorMap.length; i < len; i++) { + let color = colorMap[i]; + if (/^(?:[0-9a-fA-F]{3}){1,2}$/.test(color)) { + color = '#' + color; + } + rules[`mtk${i}`] = `color: ${color};`; + } + rules['mtki'] = 'font-style: italic;'; + rules['mtkb'] = 'font-weight: bold;'; + rules['mtku'] = 'text-decoration: underline;'; + + let defaultForegroundColor = /^(?:[0-9a-fA-F]{3}){1,2}$/.test(colorMap[1]) ? '#' + colorMap[1] : colorMap[1]; + let defaultBackgroundColor = /^(?:[0-9a-fA-F]{3}){1,2}$/.test(colorMap[2]) ? '#' + colorMap[2] : colorMap[2]; + + let fontInfo = this.configuration.editor.fontInfo; + + let output = `
`; + + if (ranges.length === 1) { + let range: Range = ranges[0]; + + if (range.isEmpty()) { + if (enableEmptySelectionClipboard) { + let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + let viewLineStart = new Position(range.startLineNumber, 1); + let viewLineEnd = new Position(range.startLineNumber, this.getLineMaxColumn(range.startLineNumber)); + let startOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineStart).column - 1; + let endOffset = this.coordinatesConverter.convertViewPositionToModelPosition(viewLineEnd).column - 1; + let viewLineRenderingData = this.getViewLineRenderingData(new Range(viewLineStart.lineNumber, viewLineStart.column, viewLineEnd.lineNumber, viewLineEnd.column), modelLineNumber); + let html = tokenizeLineToHTML(this.getModelLineContent(modelLineNumber), + viewLineRenderingData.tokens, + rules, + { + startOffset: startOffset, + endOffset: endOffset, + tabSize: this.getTabSize() + }); + output += `${html}`; + } else { + return ''; + } + } else { + for (let i = 0, lineCount = range.endLineNumber - range.startLineNumber; i <= lineCount; i++) { + let viewLineRenderingData = this.getViewLineRenderingData(range, range.startLineNumber + i); + let lineContent = viewLineRenderingData.content; + let startOffset = i === 0 ? range.startColumn - 1 : 0; + let endOffset = i === lineCount ? range.endColumn - 1 : lineContent.length; + + let html = tokenizeLineToHTML(lineContent, viewLineRenderingData.tokens, rules, + { + startOffset: startOffset, + endOffset: endOffset, + tabSize: this.getTabSize() + }); + output += `${html}`; + } + } + } + + output += '
'; + + return output; + } } diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 1714c171e3c..e1d576c415d 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -25,6 +25,14 @@ function conditionalEditorAction(testCommand: string) { return editorAction; } +function conditionalCopyWithSyntaxHighlighting() { + if (browser.isEdgeOrIE || !browser.supportsExecCommand('copy')) { + return () => { }; + } + + return editorAction; +} + abstract class ExecCommandAction extends EditorAction { private browserCommand: string; @@ -136,3 +144,36 @@ class ExecCommandPasteAction extends ExecCommandAction { }); } } + +@conditionalCopyWithSyntaxHighlighting() +class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { + + constructor() { + super('copy', { + id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', + label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), + alias: 'Copy With Syntax Highlighting', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.TextFocus, + primary: null + }, + menuOpts: { + group: CLIPBOARD_CONTEXT_MENU_GROUP, + order: 2 + } + }); + } + + public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void { + var enableEmptySelectionClipboard = editor.getConfiguration().contribInfo.emptySelectionClipboard && browser.enableEmptySelectionClipboard; + + if (!enableEmptySelectionClipboard && editor.getSelection().isEmpty()) { + return; + } + + editorCommon.InternalEditorOptions.forceCopyWithSyntaxHighlighting = true; + super.run(accessor, editor); + editorCommon.InternalEditorOptions.forceCopyWithSyntaxHighlighting = false; + } +} diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index c93d00bd5d7..7edee439119 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -505,7 +505,7 @@ abstract class FoldingAction extends EditorAction { if (!foldingController) { return; } - this.reportTelemetry(accessor); + this.reportTelemetry(accessor, editor); this.invoke(foldingController, editor, args); } diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css index 058ef445e0f..2493cde3d82 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css @@ -3,6 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-editor .monaco-editor-overlaymessage { + padding-bottom: 8px; +} + +.monaco-editor .monaco-editor-overlaymessage .message { + padding: 1px 4px; + background: #1382CE; + color: white; +} + +.monaco-editor .monaco-editor-overlaymessage .anchor { + width: 0 !important; + height: 0 !important; + border-color: transparent; + border-style: solid; + z-index: 1000; + border-width: 8px; + border-top-color: #1382CE; + position: absolute; +} + .monaco-editor .preview-zone-widget.preview .monaco-editor, .monaco-editor .preview-zone-widget.preview .monaco-editor-background, .monaco-editor .preview-zone-widget.preview .monaco-editor .line-numbers, diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 18e0271ade8..5a07ce54dc2 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -8,6 +8,7 @@ import 'vs/css!./goToDeclaration'; import * as nls from 'vs/nls'; import { Throttler } from 'vs/base/common/async'; +import { any, filterEvent } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkedString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; @@ -24,7 +25,7 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { editorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { Location, DefinitionProviderRegistry } from 'vs/editor/common/modes'; -import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; import { ReferencesController } from 'vs/editor/contrib/referenceSearch/browser/referencesController'; @@ -34,16 +35,90 @@ import { PeekContext } from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import * as corePosition from 'vs/editor/common/core/position'; - import ModeContextKeys = editorCommon.ModeContextKeys; import EditorContextKeys = editorCommon.EditorContextKeys; +class MessageOverlay implements IContentWidget { + + private static _last: IDisposable; + + static show(editor: ICodeEditor, pos: editorCommon.IPosition, message: string): void { + + dispose(MessageOverlay._last); + + const widget = new MessageOverlay(message, pos.lineNumber, editor.getOffsetForColumn(pos.lineNumber, pos.column)); + const remove = () => editor.removeContentWidget(widget); + editor.addContentWidget(widget); + + const listener = any( + filterEvent(editor.onMouseMove, e => { + const { position } = e.target; + return Math.abs(position.lineNumber - pos.lineNumber) > 1 + || Math.abs(position.column - pos.column) > 10; + }), + editor.onDidChangeCursorPosition, + editor.onDidBlurEditorText, + editor.onDidDispose, + editor.onDidChangeModel + )(_ => { + listener.dispose(); + remove(); + }); + + + MessageOverlay._last = { + dispose() { + listener.dispose(); + remove(); + } + }; + } + + readonly allowEditorOverflow = true; + readonly suppressMouseDown = false; + + private _position: editorCommon.IPosition; + private _domNode: HTMLDivElement; + + constructor(text: string, lineNumber: number, horizontalOffset: number) { + this._position = { lineNumber, column: 1 }; + + this._domNode = document.createElement('div'); + this._domNode.style.paddingLeft = `${horizontalOffset - 6}px`; + this._domNode.classList.add('monaco-editor-overlaymessage'); + + + const message = document.createElement('div'); + message.classList.add('message'); + message.textContent = text; + this._domNode.appendChild(message); + + const anchor = document.createElement('div'); + anchor.classList.add('anchor'); + this._domNode.appendChild(anchor); + } + + getId(): string { + return 'messageoverlay'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IContentWidgetPosition { + return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] }; + } +} + + export class DefinitionActionConfig { constructor( public openToSide = false, public openInPeek = false, - public filterCurrent = true + public filterCurrent = true, + public noResultsMessage = nls.localize('generic.noResults', "Could not find anything") ) { // } @@ -67,10 +142,6 @@ export class DefinitionAction extends EditorAction { return this.getDeclarationsAtPosition(model, pos).then(references => { - if (!references) { - return; - } - // * remove falsy references // * remove reference at the current pos // * collapse ranges to start pos @@ -80,7 +151,7 @@ export class DefinitionAction extends EditorAction { if (!reference || !reference.range) { continue; } - let {uri, range} = reference; + let { uri, range } = reference; if (!this._configuration.filterCurrent || uri.toString() !== model.uri.toString() || !Range.containsPosition(range, pos)) { @@ -93,6 +164,7 @@ export class DefinitionAction extends EditorAction { } if (result.length === 0) { + MessageOverlay.show(editor, pos, this._configuration.noResultsMessage); return; } @@ -125,7 +197,7 @@ export class DefinitionAction extends EditorAction { } private _openReference(editorService: IEditorService, reference: Location, sideBySide: boolean): TPromise { - let {uri, range} = reference; + let { uri, range } = reference; return editorService.openEditor({ resource: uri, options: { diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index fe68bf1d150..c759d8b45de 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -322,7 +322,7 @@ class MarkerNavigationAction extends EditorAction { } let model = controller.getOrCreateModel(); - telemetryService.publicLog('zoneWidgetShown', { mode: 'go to error' }); + telemetryService.publicLog('zoneWidgetShown', { mode: 'go to error', ...editor.getTelemetryData() }); if (model) { if (this._isNext) { model.next(); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 4e1bee69956..99f7bc879d7 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -14,7 +14,7 @@ import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { addClass, append, $, hide, removeClass, show, toggleClass } from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IDelegate, IFocusChangeEvent, IRenderer, ISelectionChangeEvent } from 'vs/base/browser/ui/list/list'; +import { IDelegate, IListEvent, IRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -407,7 +407,7 @@ export class SuggestWidget implements IContentWidget, IDelegate }); } - private onListSelection(e: ISelectionChangeEvent): void { + private onListSelection(e: IListEvent): void { if (!e.elements.length) { return; } @@ -439,7 +439,7 @@ export class SuggestWidget implements IContentWidget, IDelegate } } - private onListFocus(e: IFocusChangeEvent): void { + private onListFocus(e: IListEvent): void { if (!e.elements.length) { if (this.currentSuggestionDetails) { this.currentSuggestionDetails.cancel(); @@ -591,9 +591,9 @@ export class SuggestWidget implements IContentWidget, IDelegate this.completionModel = null; } else { - const {stats} = this.completionModel; + const { stats } = this.completionModel; stats['wasAutomaticallyTriggered'] = !!isAuto; - this.telemetryService.publicLog('suggestWidget', stats); + this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() }); this.list.splice(0, this.list.length, this.completionModel.items); this.list.setFocus([this.completionModel.topScoreIdx]); @@ -694,7 +694,7 @@ export class SuggestWidget implements IContentWidget, IDelegate this.setState(State.Details); this.editor.focus(); - this.telemetryService.publicLog('suggestWidget:toggleDetails'); + this.telemetryService.publicLog('suggestWidget:toggleDetails', this.editor.getTelemetryData()); } private show(): void { diff --git a/src/vs/editor/electron-browser/textMate/TMSyntax.ts b/src/vs/editor/electron-browser/textMate/TMSyntax.ts index 05d584b9a51..09817ef6d3f 100644 --- a/src/vs/editor/electron-browser/textMate/TMSyntax.ts +++ b/src/vs/editor/electron-browser/textMate/TMSyntax.ts @@ -21,6 +21,7 @@ import { grammarsExtPoint, IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint } from import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding'; import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; +import { hexToCSS } from 'vs/base/common/color'; export class TMScopeRegistry { @@ -153,7 +154,7 @@ export class MainProcessTextMateSyntax implements ITextMateService { let rules: string[] = []; for (let i = 1, len = colorMap.length; i < len; i++) { let color = colorMap[i]; - rules[i] = `.mtk${i} { color: ${color.substr(0, 7)}; }`; + rules[i] = `.mtk${i} { color: ${hexToCSS(color)}; }`; } rules.push('.mtki { font-style: italic; }'); rules.push('.mtkb { font-weight: bold; }'); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index 6872d63cf9a..02ee516811b 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -53,6 +53,14 @@ class SingleLineTestModel implements ISimpleModel { getLineCount(): number { return 1; } + + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } } class TestView { @@ -180,4 +188,4 @@ const TESTS = [ TESTS.forEach((t) => { document.body.appendChild(doCreateTest(TextAreaStrategy.NVDA, t.description, t.in, t.out)); document.body.appendChild(doCreateTest(TextAreaStrategy.IENarrator, t.description, t.in, t.out)); -}); +}); \ No newline at end of file diff --git a/src/vs/editor/test/common/controller/textAreaState.test.ts b/src/vs/editor/test/common/controller/textAreaState.test.ts index 959974b9e33..6da10786e1f 100644 --- a/src/vs/editor/test/common/controller/textAreaState.test.ts +++ b/src/vs/editor/test/common/controller/textAreaState.test.ts @@ -472,4 +472,12 @@ class SimpleModel implements ISimpleModel { public getLineCount(): number { return this._lines.length; } + + public getPlainTextToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } + + public getHTMLToCopy(ranges: Range[], enableEmptySelectionClipboard: boolean): string { + return ''; + } } diff --git a/src/vs/platform/actions/browser/menusExtensionPoint.ts b/src/vs/platform/actions/browser/menusExtensionPoint.ts index 497a437071f..91cd7b8c64a 100644 --- a/src/vs/platform/actions/browser/menusExtensionPoint.ts +++ b/src/vs/platform/actions/browser/menusExtensionPoint.ts @@ -28,6 +28,7 @@ namespace schema { export function parseMenuId(value: string): MenuId { switch (value) { + case 'commandPalette': return MenuId.CommandPalette; case 'editor/title': return MenuId.EditorTitle; case 'editor/context': return MenuId.EditorContext; case 'explorer/context': return MenuId.ExplorerContext; @@ -224,7 +225,7 @@ ExtensionsRegistry.registerExtensionPoint(value)) { for (let command of value) { handleCommand(command, extension); @@ -259,7 +260,7 @@ ExtensionsRegistry.registerExtensionPoint('menus', [], schema.menusContribtion).setHandler(extensions => { for (let extension of extensions) { - const {value, collector} = extension; + const { value, collector } = extension; forEach(value, entry => { if (!schema.isValidMenuItems(entry.value, collector)) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 67fe9f67b89..590fd423b9e 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -21,11 +21,6 @@ export interface ICommandAction { iconClass?: string; } -export interface IMenu extends IDisposable { - onDidChange: Event; - getActions(arg?: any): [string, MenuItemAction[]][]; -} - export interface IMenuItem { command: ICommandAction; alt?: ICommandAction; @@ -49,6 +44,7 @@ export class MenuId { static readonly SCMTitle = new MenuId('11'); static readonly SCMResourceGroupContext = new MenuId('12'); static readonly SCMResourceContext = new MenuId('13'); + static readonly CommandPalette = new MenuId('14'); constructor(private _id: string) { @@ -59,6 +55,11 @@ export class MenuId { } } +export interface IMenu extends IDisposable { + onDidChange: Event; + getActions(arg?: any): [string, MenuItemAction[]][]; +} + export const IMenuService = createDecorator('menuService'); export interface IMenuService { @@ -66,8 +67,6 @@ export interface IMenuService { _serviceBrand: any; createMenu(id: MenuId, scopedKeybindingService: IContextKeyService): IMenu; - - getCommandActions(): ICommandAction[]; } export interface IMenuRegistry { @@ -94,7 +93,7 @@ export const MenuRegistry: IMenuRegistry = new class { return this.commands[id]; } - appendMenuItem({id}: MenuId, item: IMenuItem): IDisposable { + appendMenuItem({ id }: MenuId, item: IMenuItem): IDisposable { let array = this.menuItems[id]; if (!array) { this.menuItems[id] = array = [item]; @@ -111,8 +110,30 @@ export const MenuRegistry: IMenuRegistry = new class { }; } - getMenuItems({id}: MenuId): IMenuItem[] { - return this.menuItems[id] || []; + getMenuItems({ id }: MenuId): IMenuItem[] { + const result = this.menuItems[id] || []; + + if (id === MenuId.CommandPalette.id) { + // CommandPalette is special because it shows + // all commands by default + this._appendImplicitItems(result); + } + return result; + } + + private _appendImplicitItems(result: IMenuItem[]) { + const set = new Set(); + for (const { command, alt } of result) { + set.add(command.id); + if (alt) { + set.add(alt.id); + } + } + for (let id in this.commands) { + if (!set.has(id)) { + result.push({ command: this.commands[id] }); + } + } } }; @@ -202,4 +223,4 @@ export class SyncActionDescriptor { public get keybindingWeight(): number { return this._keybindingWeight; } -} \ No newline at end of file +} diff --git a/src/vs/platform/actions/common/menu.ts b/src/vs/platform/actions/common/menu.ts index 621eee3a73a..e4b241d8bb8 100644 --- a/src/vs/platform/actions/common/menu.ts +++ b/src/vs/platform/actions/common/menu.ts @@ -21,7 +21,7 @@ export class Menu implements IMenu { private _onDidChange = new Emitter(); constructor( - id: MenuId, + private id: MenuId, startupSignal: TPromise, @ICommandService private _commandService: ICommandService, @IContextKeyService private _contextKeyService: IContextKeyService diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 87d79822189..77ee1f52905 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -5,9 +5,8 @@ 'use strict'; -import { values } from 'vs/base/common/collections'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, ICommandAction, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { Menu } from 'vs/platform/actions/common/menu'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -26,8 +25,4 @@ export class MenuService implements IMenuService { createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { return new Menu(id, this._extensionService.onReady(), this._commandService, contextKeyService); } - - getCommandActions(): ICommandAction[] { - return values(MenuRegistry.commands); - } } diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts index 4cc8cc02d73..b8408b178ee 100644 --- a/src/vs/platform/actions/test/common/menuService.test.ts +++ b/src/vs/platform/actions/test/common/menuService.test.ts @@ -186,4 +186,20 @@ suite('MenuService', function () { assert.equal(two.id, 'b'); assert.equal(three.id, 'a'); }); + + test('special MenuId palette', function () { + + disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { id: 'a', title: 'Explicit' } + })); + + MenuRegistry.addCommand({ id: 'b', title: 'Implicit' }); + + const [first, second] = MenuRegistry.getMenuItems(MenuId.CommandPalette); + assert.equal(first.command.id, 'a'); + assert.equal(first.command.title, 'Explicit'); + + assert.equal(second.command.id, 'b'); + assert.equal(second.command.title, 'Implicit'); + }); }); diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 08cdd95782f..b5181490056 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -122,13 +122,13 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - public registerDefaultConfigurations(defaultConnfigurations: IDefaultConfigurationExtension[]): void { + public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void { const configurationNode: IConfigurationNode = { id: 'defaultOverrides', title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"), properties: {} }; - for (const defaultConfiguration of defaultConnfigurations) { + for (const defaultConfiguration of defaultConfigurations) { for (const key in defaultConfiguration.defaults) { const defaultValue = defaultConfiguration.defaults[key]; if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') { @@ -141,7 +141,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } } - this.registerConfiguration(configurationNode, false); + if (Object.keys(configurationNode.properties).length) { + this.registerConfiguration(configurationNode, false); + } } private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, overridable: boolean = false) { @@ -360,10 +362,8 @@ configurationExtPoint.setHandler(extensions => { validateProperties(configuration, collector); - if (configuration.properties) { - configuration.id = extensions[i].description.id; - configurations.push(configuration); - } + configuration.id = extensions[i].description.id; + configurations.push(configuration); } configurationRegistry.registerConfigurations(configurations, false); diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 4f2dab386eb..b8c47e548ea 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -29,25 +29,25 @@ function getUniqueUserId(): string { return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6); } -function getIPCHandlePrefix(nixBaseDir: string = os.tmpdir()): string { - let name = pkg.name; - - // Support to run VS Code multiple times as different user - // by making the socket unique over the logged in user - let userId = getUniqueUserId(); - if (userId) { - name += `-${userId}`; - } - - if (process.platform === 'win32') { - return `\\\\.\\pipe\\${name}`; - } else { - return path.join(nixBaseDir, name); - } +function getNixIPCHandle(userDataPath: string, type: string): string { + return path.join(userDataPath, `${pkg.version}-${type}.sock`); } -function getIPCHandleSuffix(): string { - return process.platform === 'win32' ? '-sock' : '.sock'; +function getWin32IPCHandle(type: string): string { + // Support to run VS Code multiple times as different user + // by making the socket unique over the logged in user + const userId = getUniqueUserId(); + const name = product.applicationName + (userId ? `-${userId}` : ''); + + return `\\\\.\\pipe\\${name}-${pkg.version}-${type}-sock`; +} + +function getIPCHandle(userDataPath: string, type: string): string { + if (process.platform === 'win32') { + return getWin32IPCHandle(type); + } else { + return getNixIPCHandle(userDataPath, type); + } } export class EnvironmentService implements IEnvironmentService { @@ -113,10 +113,10 @@ export class EnvironmentService implements IEnvironmentService { get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; } @memoize - get mainIPCHandle(): string { return `${getIPCHandlePrefix(this.userDataPath)}-${pkg.version}${getIPCHandleSuffix()}`; } + get mainIPCHandle(): string { return getIPCHandle(this.userDataPath, 'main'); } @memoize - get sharedIPCHandle(): string { return `${getIPCHandlePrefix(this.userDataPath)}-${pkg.version}-shared${getIPCHandleSuffix()}`; } + get sharedIPCHandle(): string { return getIPCHandle(this.userDataPath, 'shared'); } @memoize get nodeCachedDataDir(): string { return path.join(this.userDataPath, 'CachedData'); } diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index aca834c2d6f..1d9edfb7576 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -15,11 +15,17 @@ export interface ITelemetryInfo { instanceId: string; } +export interface ITelemetryData { + from?: string; + target?: string; + [key: string]: any; +} + export interface ITelemetryExperiments { showNewUserWatermark: boolean; openUntitledFile: boolean; - openGettingStarted?: boolean; enableWelcomePage: boolean; + repositionPlaygroundLink: boolean; } export interface ITelemetryService { @@ -30,7 +36,7 @@ export interface ITelemetryService { * Sends a telemetry event that has been privacy approved. * Do not call this unless you have been given approval. */ - publicLog(eventName: string, data?: any): TPromise; + publicLog(eventName: string, data?: ITelemetryData): TPromise; getTelemetryInfo(): TPromise; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 2b886246083..aa7e3f4a754 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryInfo, ITelemetryExperiments, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryAppender, defaultExperiments } from 'vs/platform/telemetry/common/telemetryUtils'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -100,7 +100,7 @@ export class TelemetryService implements ITelemetryService { this._disposables = dispose(this._disposables); } - publicLog(eventName: string, data?: any): TPromise { + publicLog(eventName: string, data?: ITelemetryData): TPromise { // don't send events when the user is optout if (!this._userOptIn) { return TPromise.as(undefined); diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 8ef9acb59ef..dbc50faabce 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -14,7 +14,7 @@ import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/com import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, ITelemetryExperiments, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StorageService } from 'vs/platform/storage/common/storageService'; import * as objects from 'vs/base/common/objects'; @@ -22,13 +22,14 @@ import * as objects from 'vs/base/common/objects'; export const defaultExperiments: ITelemetryExperiments = { showNewUserWatermark: false, openUntitledFile: true, - enableWelcomePage: true + enableWelcomePage: true, + repositionPlaygroundLink: false, }; export const NullTelemetryService = { _serviceBrand: undefined, _experiments: defaultExperiments, - publicLog(eventName: string, data?: any) { + publicLog(eventName: string, data?: ITelemetryData) { return TPromise.as(null); }, isOptedIn: true, @@ -44,9 +45,6 @@ export const NullTelemetryService = { } }; -const beginGettingStartedExp = Date.UTC(2017, 0, 9); -const endGettingStartedExp = Date.UTC(2017, 0, 16); - export function loadExperiments(accessor: ServicesAccessor): ITelemetryExperiments { const contextService = accessor.get(IWorkspaceContextService); const storageService = accessor.get(IStorageService); @@ -58,8 +56,8 @@ export function loadExperiments(accessor: ServicesAccessor): ITelemetryExperimen let { showNewUserWatermark, openUntitledFile, - openGettingStarted, - enableWelcomePage + enableWelcomePage, + repositionPlaygroundLink, } = splitExperimentsRandomness(); const newUserDuration = 24 * 60 * 60 * 1000; @@ -70,17 +68,11 @@ export function loadExperiments(accessor: ServicesAccessor): ITelemetryExperimen openUntitledFile = defaultExperiments.openUntitledFile; } - const isNewSession = !storageService.get('telemetry.lastSessionDate'); - const now = Date.now(); - if (!(isNewSession && now >= beginGettingStartedExp && now < endGettingStartedExp)) { - openGettingStarted = undefined; - } - return applyOverrides({ showNewUserWatermark, openUntitledFile, - openGettingStarted, - enableWelcomePage + enableWelcomePage, + repositionPlaygroundLink, }); } @@ -103,13 +95,13 @@ function splitExperimentsRandomness(): ITelemetryExperiments { const random1 = getExperimentsRandomness(); const [random2, showNewUserWatermark] = splitRandom(random1); const [random3, openUntitledFile] = splitRandom(random2); - const [random4, openGettingStarted] = splitRandom(random3); + const [random4, repositionPlaygroundLink] = splitRandom(random3); const [, enableWelcomePage] = splitRandom(random4); return { showNewUserWatermark, openUntitledFile, - openGettingStarted, - enableWelcomePage + enableWelcomePage, + repositionPlaygroundLink, }; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index dc4f1b3f2a6..03dc91cd19e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -134,6 +134,7 @@ declare module 'vscode' { readonly resources: SCMResourceGroup[]; readonly onDidChange: Event; readonly count?: number | undefined; + readonly state?: string; getOriginalResource?(uri: Uri, token: CancellationToken): ProviderResult; open?(resource: SCMResource, token: CancellationToken): ProviderResult; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 3e227a1150a..1a0c54b595a 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -183,6 +183,7 @@ export abstract class MainThreadMessageServiceShape { export abstract class MainThreadOutputServiceShape { $append(channelId: string, label: string, value: string): TPromise { throw ni(); } $clear(channelId: string, label: string): TPromise { throw ni(); } + $dispose(channelId: string, label: string): TPromise { throw ni(); } $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { throw ni(); } $close(channelId: string): TPromise { throw ni(); } } @@ -258,7 +259,7 @@ export type SCMRawResourceGroup = [string /*id*/, string /*label*/, SCMRawResour export abstract class MainThreadSCMShape { $register(id: string, features: SCMProviderFeatures): void { throw ni(); } $unregister(id: string): void { throw ni(); } - $onChange(id: string, resources: SCMRawResourceGroup[], count: number | undefined): void { throw ni(); } + $onChange(id: string, resources: SCMRawResourceGroup[], count: number | undefined, state: string | undefined): void { throw ni(); } $setInputBoxValue(value: string): void { throw ni(); } } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 7bda3aa3363..9a0190ae7e5 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -29,7 +29,7 @@ export class ExtHostOutputChannel implements vscode.OutputChannel { dispose(): void { if (!this._disposed) { - this._proxy.$clear(this._id, this._name).then(() => { + this._proxy.$dispose(this._id, this._name).then(() => { this._disposed = true; }); } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index c874314608e..978ca281f8e 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -189,7 +189,7 @@ export class ExtHostSCM { return [g.id, g.label, rawResources] as SCMRawResourceGroup; }); - this._proxy.$onChange(providerId, rawResourceGroups, provider.count); + this._proxy.$onChange(providerId, rawResourceGroups, provider.count, provider.state); }); return new Disposable(() => { diff --git a/src/vs/workbench/api/node/mainThreadOutputService.ts b/src/vs/workbench/api/node/mainThreadOutputService.ts index bbaacb49b47..b0a32a6202d 100644 --- a/src/vs/workbench/api/node/mainThreadOutputService.ts +++ b/src/vs/workbench/api/node/mainThreadOutputService.ts @@ -58,4 +58,9 @@ export class MainThreadOutputService extends MainThreadOutputServiceShape { return undefined; } + + public $dispose(channelId: string, label: string): TPromise { + this._getChannel(channelId, label).dispose(); + return undefined; + } } diff --git a/src/vs/workbench/api/node/mainThreadSCM.ts b/src/vs/workbench/api/node/mainThreadSCM.ts index 0962c9453c7..5726da0a8e1 100644 --- a/src/vs/workbench/api/node/mainThreadSCM.ts +++ b/src/vs/workbench/api/node/mainThreadSCM.ts @@ -30,6 +30,9 @@ class MainThreadSCMProvider implements ISCMProvider { private _count: number | undefined = undefined; get count(): number | undefined { return this._count; } + private _state: string | undefined = undefined; + get state(): string | undefined { return this._state; } + constructor( private _id: string, private proxy: ExtHostSCMShape, @@ -71,7 +74,7 @@ class MainThreadSCMProvider implements ISCMProvider { // } } - $onChange(rawResourceGroups: SCMRawResourceGroup[], count: number | undefined): void { + $onChange(rawResourceGroups: SCMRawResourceGroup[], count: number | undefined, state: string | undefined): void { this._resources = rawResourceGroups.map(rawGroup => { const [id, label, rawResources] = rawGroup; @@ -96,7 +99,9 @@ class MainThreadSCMProvider implements ISCMProvider { return { id, label, resources }; }); + this._count = count; + this._state = state; this._onDidChange.fire(this.resources); } @@ -140,14 +145,14 @@ export class MainThreadSCM extends MainThreadSCMShape { delete this.providers[id]; } - $onChange(id: string, rawResourceGroups: SCMRawResourceGroup[], count: number | undefined): void { + $onChange(id: string, rawResourceGroups: SCMRawResourceGroup[], count: number | undefined, state: string | undefined): void { const provider = this.providers[id]; if (!provider) { return; } - provider.$onChange(rawResourceGroups, count); + provider.$onChange(rawResourceGroups, count, state); } $setInputBoxValue(value: string): void { diff --git a/src/vs/workbench/common/actionRegistry.ts b/src/vs/workbench/common/actionRegistry.ts index e16dd395bcd..95669c4d86e 100644 --- a/src/vs/workbench/common/actionRegistry.ts +++ b/src/vs/workbench/common/actionRegistry.ts @@ -168,14 +168,15 @@ export function triggerAndDisposeAction(instantitationService: IInstantiationSer return undefined; } + const from = args && args.from || 'keybinding'; if (telemetryService) { - telemetryService.publicLog('workbenchActionExecuted', { id: actionInstance.id, from: args && args.from || 'keybinding' }); + telemetryService.publicLog('workbenchActionExecuted', { id: actionInstance.id, from }); } // run action when workbench is created return partService.joinCreation().then(() => { try { - return TPromise.as(actionInstance.run()).then(() => { + return TPromise.as(actionInstance.run(undefined, { from })).then(() => { actionInstance.dispose(); }, (err) => { actionInstance.dispose(); diff --git a/src/vs/workbench/electron-browser/bootstrap/index.html b/src/vs/workbench/electron-browser/bootstrap/index.html index 73cc8d4c91d..b0d3e486f7b 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.html +++ b/src/vs/workbench/electron-browser/bootstrap/index.html @@ -9,6 +9,12 @@ .monaco-shell.vs-dark { background-color: #1E1E1E; } + .monaco-shell.vs { + background-color: #FFFFFF; + } + .monaco-shell.hc-black { + background-color: #000000; + } @@ -16,13 +22,13 @@