mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
Merge branch 'master' of https://github.com/microsoft/vscode into clantz/dev-container
This commit is contained in:
commit
f6caf01684
4
.github/workflows/author-verified.yml
vendored
4
.github/workflows/author-verified.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: Author Verified
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [trigger-author-verified]
|
||||
schedule:
|
||||
- cron: 20 14 * * * # 4:20pm Zurich
|
||||
issues:
|
||||
|
@ -16,7 +17,7 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v17
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Install Actions
|
||||
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')
|
||||
|
@ -31,6 +32,7 @@ jobs:
|
|||
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')
|
||||
uses: ./actions/author-verified
|
||||
with:
|
||||
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
|
||||
requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by commenting `/verified` if things are now working as expected.\n\nIf things still don't seem right, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallette to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!"
|
||||
pendingReleaseLabel: awaiting-insiders-release
|
||||
authorVerificationRequestedLabel: author-verification-requested
|
||||
|
|
2
.github/workflows/classifier-apply.yml
vendored
2
.github/workflows/classifier-apply.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v17
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
|
|
2
.github/workflows/commands.yml
vendored
2
.github/workflows/commands.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v17
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run Commands
|
||||
|
|
2
.github/workflows/english-please.yml
vendored
2
.github/workflows/english-please.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v17
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Install Actions
|
||||
if: contains(github.event.issue.labels.*.name, '*english-please')
|
||||
|
|
3
.github/workflows/feature-request.yml
vendored
3
.github/workflows/feature-request.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: Feature Request Manager
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [trigger-feature-request-manager]
|
||||
issues:
|
||||
types: [milestoned]
|
||||
schedule:
|
||||
|
@ -17,7 +18,7 @@ jobs:
|
|||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v17
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
|
||||
run: npm install --production --prefix ./actions
|
||||
|
|
24
.github/workflows/latest-release-monitor.yml
vendored
Normal file
24
.github/workflows/latest-release-monitor.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: Latest Release Monitor
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0/5 * * * *
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Install Storage Module
|
||||
run: npm install @azure/storage-blob
|
||||
- name: Run Latest Release Monitor
|
||||
uses: ./actions/latest-release-monitor
|
||||
with:
|
||||
storageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING_NEW}}
|
||||
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
|
3
.github/workflows/locker.yml
vendored
3
.github/workflows/locker.yml
vendored
|
@ -3,6 +3,7 @@ on:
|
|||
schedule:
|
||||
- cron: 20 23 * * * # 4:20pm Redmond
|
||||
repository_dispatch:
|
||||
types: [trigger-locker]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
|
@ -13,7 +14,7 @@ jobs:
|
|||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v17
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run Locker
|
||||
|
|
3
.github/workflows/needs-more-info-closer.yml
vendored
3
.github/workflows/needs-more-info-closer.yml
vendored
|
@ -3,6 +3,7 @@ on:
|
|||
schedule:
|
||||
- cron: 20 11 * * * # 4:20am Redmond
|
||||
repository_dispatch:
|
||||
types: [trigger-needs-more-info]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
|
@ -13,7 +14,7 @@ jobs:
|
|||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v17
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run Needs More Info Closer
|
||||
|
|
2
.github/workflows/on-label.yml
vendored
2
.github/workflows/on-label.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v17
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
|
|
2
.github/workflows/on-open.yml
vendored
2
.github/workflows/on-open.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
|||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v17
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
|
|
31
.github/workflows/release-pipeline-labeler.yml
vendored
Normal file
31
.github/workflows/release-pipeline-labeler.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
name: "Release Pipeline Labeler"
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
repository_dispatch:
|
||||
types: [released-insider]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
ref: v19
|
||||
path: ./actions
|
||||
- name: Checkout Repo
|
||||
if: github.event_name != 'issues'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./repo
|
||||
fetch-depth: 0
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: "Run Release Pipeline Labeler"
|
||||
uses: ./actions/release-pipeline
|
||||
with:
|
||||
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
|
||||
notYetReleasedLabel: unreleased
|
||||
insidersReleasedLabel: insiders-released
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
with:
|
||||
repository: 'microsoft/vscode-github-triage-actions'
|
||||
path: ./actions
|
||||
ref: v17
|
||||
ref: v19
|
||||
- name: Install Actions
|
||||
if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item')
|
||||
run: npm install --production --prefix ./actions
|
||||
|
|
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
@ -397,6 +397,7 @@
|
|||
"compounds": [
|
||||
{
|
||||
"name": "VS Code",
|
||||
"stopAll": true,
|
||||
"configurations": [
|
||||
"Launch VS Code",
|
||||
"Attach to Main Process",
|
||||
|
|
2
.yarnrc
2
.yarnrc
|
@ -1,3 +1,3 @@
|
|||
disturl "https://atom.io/download/electron"
|
||||
target "7.3.0"
|
||||
target "7.3.1"
|
||||
runtime "electron"
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# Visual Studio Code - Open Source ("Code - OSS")
|
||||
|
||||
<!-- [![Build Status](https://dev.azure.com/vscode/VSCode/_apis/build/status/VS%20Code?branchName=master)](https://aka.ms/vscode-builds) -->
|
||||
[![Build Status](https://dev.azure.com/vscode/VSCode/_apis/build/status/VS%20Code?branchName=master)](https://dev.azure.com/vscode/VSCode/_build/latest?definitionId=12)
|
||||
[![Build Status](https://dev.azure.com/vscode/VSCode/_apis/build/status/VS%20Code?branchName=master)](https://aka.ms/vscode-builds)
|
||||
[![Feature Requests](https://img.shields.io/github/issues/Microsoft/vscode/feature-request.svg)](https://github.com/Microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc)
|
||||
[![Bugs](https://img.shields.io/github/issues/Microsoft/vscode/bug.svg)](https://github.com/Microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
[![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode)
|
||||
|
|
|
@ -23,7 +23,7 @@ This project incorporates components from the projects listed below. The origina
|
|||
16. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle)
|
||||
17. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift)
|
||||
18. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
|
||||
19. Ikuyadeu/vscode-R version 1.1.8 (https://github.com/Ikuyadeu/vscode-R)
|
||||
19. Ikuyadeu/vscode-R version 1.3.0 (https://github.com/Ikuyadeu/vscode-R)
|
||||
20. insane version 2.6.2 (https://github.com/bevacqua/insane)
|
||||
21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site)
|
||||
22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar)
|
||||
|
|
|
@ -55,6 +55,9 @@ Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl,{#RepoDir}\build\
|
|||
Name: "korean"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.ko.isl,{#RepoDir}\build\win32\i18n\messages.ko.isl" {#LocalizedLanguageFile("kor")}
|
||||
Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-cn.isl,{#RepoDir}\build\win32\i18n\messages.zh-cn.isl" {#LocalizedLanguageFile("chs")}
|
||||
Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-tw.isl,{#RepoDir}\build\win32\i18n\messages.zh-tw.isl" {#LocalizedLanguageFile("cht")}
|
||||
Name: "brazilianPortuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl,{#RepoDir}\build\win32\i18n\messages.pt-br.isl" {#LocalizedLanguageFile("ptb")}
|
||||
Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl,{#RepoDir}\build\win32\i18n\messages.hu.isl" {#LocalizedLanguageFile("hun")}
|
||||
Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl,{#RepoDir}\build\win32\i18n\messages.tr.isl" {#LocalizedLanguageFile("trk")}
|
||||
|
||||
[InstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\resources\app\out"; Check: IsNotUpdate
|
||||
|
|
|
@ -60,12 +60,12 @@
|
|||
"git": {
|
||||
"name": "electron",
|
||||
"repositoryUrl": "https://github.com/electron/electron",
|
||||
"commitHash": "8f502de1dc5b6df4218a900d0857de7a40301d98"
|
||||
"commitHash": "bc8fc0d406d32e4c02f3ec9f161deaacbe4f5989"
|
||||
}
|
||||
},
|
||||
"isOnlyProductionDependency": true,
|
||||
"license": "MIT",
|
||||
"version": "7.3.0"
|
||||
"version": "7.3.1"
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { startClient, LanguageClientConstructor } from '../cssClient';
|
||||
import { LanguageClient } from 'vscode-languageclient/browser';
|
||||
|
||||
declare const Worker: {
|
||||
new(stringUrl: string): any;
|
||||
};
|
||||
declare const TextDecoder: {
|
||||
new(encoding?: string): { decode(buffer: ArrayBuffer): string; };
|
||||
};
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
const serverMain = context.asAbsolutePath('server/dist/browser/cssServerMain.js');
|
||||
try {
|
||||
const worker = new Worker(serverMain);
|
||||
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
|
||||
return new LanguageClient(id, name, clientOptions, worker);
|
||||
};
|
||||
|
||||
startClient(context, newLanguageClient, { TextDecoder });
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface Options {
|
||||
locale?: string;
|
||||
cacheLanguageResolution?: boolean;
|
||||
}
|
||||
export interface LocalizeInfo {
|
||||
key: string;
|
||||
comment: string[];
|
||||
}
|
||||
export interface LocalizeFunc {
|
||||
(info: LocalizeInfo, message: string, ...args: any[]): string;
|
||||
(key: string, message: string, ...args: any[]): string;
|
||||
}
|
||||
export interface LoadFunc {
|
||||
(file?: string): LocalizeFunc;
|
||||
}
|
||||
|
||||
function format(message: string, args: any[]): string {
|
||||
let result: string;
|
||||
|
||||
if (args.length === 0) {
|
||||
result = message;
|
||||
} else {
|
||||
result = message.replace(/\{(\d+)\}/g, (match, rest) => {
|
||||
let index = rest[0];
|
||||
return typeof args[index] !== 'undefined' ? args[index] : match;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function localize(_key: string | LocalizeInfo, message: string, ...args: any[]): string {
|
||||
return format(message, args);
|
||||
}
|
||||
|
||||
export function loadMessageBundle(_file?: string): LocalizeFunc {
|
||||
return localize;
|
||||
}
|
||||
|
||||
export function config(_opt?: Options | string): LoadFunc {
|
||||
return loadMessageBundle;
|
||||
}
|
|
@ -3,38 +3,31 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, workspace, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList } from 'vscode';
|
||||
import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, ProvideCompletionItemsSignature } from 'vscode-languageclient';
|
||||
import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList } from 'vscode';
|
||||
import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, CommonLanguageClient } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getCustomDataPathsFromAllExtensions, getCustomDataPathsInAllWorkspaces } from './customData';
|
||||
import { getCustomDataSource } from './customData';
|
||||
import { RequestService, serveFileSystemRequests } from './requests';
|
||||
|
||||
namespace CustomDataChangedNotification {
|
||||
export const type: NotificationType<string[]> = new NotificationType('css/customDataChanged');
|
||||
}
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient;
|
||||
|
||||
let serverMain = readJSONFile(context.asAbsolutePath('./server/package.json')).main;
|
||||
let serverModule = context.asAbsolutePath(path.join('server', serverMain));
|
||||
export interface Runtime {
|
||||
TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string; } };
|
||||
fs?: RequestService;
|
||||
}
|
||||
|
||||
// The debug options for the server
|
||||
let debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] };
|
||||
export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {
|
||||
|
||||
// If the extension is launch in debug mode the debug server options are use
|
||||
// Otherwise the run options are used
|
||||
let serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
const customDataSource = getCustomDataSource(context.subscriptions);
|
||||
|
||||
let documentSelector = ['css', 'scss', 'less'];
|
||||
|
||||
let dataPaths = [
|
||||
...getCustomDataPathsInAllWorkspaces(workspace.workspaceFolders),
|
||||
...getCustomDataPathsFromAllExtensions()
|
||||
];
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector,
|
||||
|
@ -42,7 +35,7 @@ export function activate(context: ExtensionContext) {
|
|||
configurationSection: ['css', 'scss', 'less']
|
||||
},
|
||||
initializationOptions: {
|
||||
dataPaths
|
||||
handledSchemas: ['file']
|
||||
},
|
||||
middleware: {
|
||||
provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
|
||||
|
@ -82,8 +75,17 @@ export function activate(context: ExtensionContext) {
|
|||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
let client = new LanguageClient('css', localize('cssserver.name', 'CSS Language Server'), serverOptions, clientOptions);
|
||||
let client = newLanguageClient('css', localize('cssserver.name', 'CSS Language Server'), clientOptions);
|
||||
client.registerProposedFeatures();
|
||||
client.onReady().then(() => {
|
||||
|
||||
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
|
||||
customDataSource.onDidChange(() => {
|
||||
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
|
||||
});
|
||||
|
||||
serveFileSystemRequests(client, runtime);
|
||||
});
|
||||
|
||||
let disposable = client.start();
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
|
@ -118,7 +120,7 @@ export function activate(context: ExtensionContext) {
|
|||
const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/;
|
||||
|
||||
return languages.registerCompletionItemProvider(documentSelector, {
|
||||
provideCompletionItems(doc, pos) {
|
||||
provideCompletionItems(doc: TextDocument, pos: Position) {
|
||||
let lineUntilPos = doc.getText(new Range(new Position(pos.line, 0), pos));
|
||||
let match = lineUntilPos.match(regionCompletionRegExpr);
|
||||
if (match) {
|
||||
|
@ -162,13 +164,3 @@ export function activate(context: ExtensionContext) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readJSONFile(location: string) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(location).toString());
|
||||
} catch (e) {
|
||||
console.log(`Problems reading ${location}: ${e}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,42 +3,78 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { workspace, WorkspaceFolder, extensions } from 'vscode';
|
||||
import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode';
|
||||
import { resolvePath, joinPath } from './requests';
|
||||
|
||||
interface ExperimentalConfig {
|
||||
customData?: string[];
|
||||
experimental?: {
|
||||
customData?: string[];
|
||||
export function getCustomDataSource(toDispose: Disposable[]) {
|
||||
let pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
|
||||
let pathsInExtensions = getCustomDataPathsFromAllExtensions();
|
||||
|
||||
const onChange = new EventEmitter<void>();
|
||||
|
||||
toDispose.push(extensions.onDidChange(_ => {
|
||||
const newPathsInExtensions = getCustomDataPathsFromAllExtensions();
|
||||
if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) {
|
||||
pathsInExtensions = newPathsInExtensions;
|
||||
onChange.fire();
|
||||
}
|
||||
}));
|
||||
toDispose.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('css.customData')) {
|
||||
pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
|
||||
onChange.fire();
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
get uris() {
|
||||
return pathsInWorkspace.concat(pathsInExtensions);
|
||||
},
|
||||
get onDidChange() {
|
||||
return onChange.event;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getCustomDataPathsInAllWorkspaces(workspaceFolders: readonly WorkspaceFolder[] | undefined): string[] {
|
||||
|
||||
function getCustomDataPathsInAllWorkspaces(): string[] {
|
||||
const workspaceFolders = workspace.workspaceFolders;
|
||||
|
||||
const dataPaths: string[] = [];
|
||||
|
||||
if (!workspaceFolders) {
|
||||
return dataPaths;
|
||||
}
|
||||
|
||||
workspaceFolders.forEach(wf => {
|
||||
const allCssConfig = workspace.getConfiguration(undefined, wf.uri);
|
||||
const wfCSSConfig = allCssConfig.inspect<ExperimentalConfig>('css');
|
||||
if (wfCSSConfig && wfCSSConfig.workspaceFolderValue && wfCSSConfig.workspaceFolderValue.customData) {
|
||||
const customData = wfCSSConfig.workspaceFolderValue.customData;
|
||||
if (Array.isArray(customData)) {
|
||||
customData.forEach(t => {
|
||||
if (typeof t === 'string') {
|
||||
dataPaths.push(path.resolve(wf.uri.fsPath, t));
|
||||
}
|
||||
});
|
||||
const collect = (paths: string[] | undefined, rootFolder: Uri) => {
|
||||
if (Array.isArray(paths)) {
|
||||
for (const path of paths) {
|
||||
if (typeof path === 'string') {
|
||||
dataPaths.push(resolvePath(rootFolder, path).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < workspaceFolders.length; i++) {
|
||||
const folderUri = workspaceFolders[i].uri;
|
||||
const allCssConfig = workspace.getConfiguration('css', folderUri);
|
||||
const customDataInspect = allCssConfig.inspect<string[]>('customData');
|
||||
if (customDataInspect) {
|
||||
collect(customDataInspect.workspaceFolderValue, folderUri);
|
||||
if (i === 0) {
|
||||
if (workspace.workspaceFile) {
|
||||
collect(customDataInspect.workspaceValue, workspace.workspaceFile);
|
||||
}
|
||||
collect(customDataInspect.globalValue, folderUri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return dataPaths;
|
||||
}
|
||||
|
||||
export function getCustomDataPathsFromAllExtensions(): string[] {
|
||||
function getCustomDataPathsFromAllExtensions(): string[] {
|
||||
const dataPaths: string[] = [];
|
||||
|
||||
for (const extension of extensions.all) {
|
||||
|
@ -47,7 +83,7 @@ export function getCustomDataPathsFromAllExtensions(): string[] {
|
|||
if (contributes && contributes.css && contributes.css.customData && Array.isArray(contributes.css.customData)) {
|
||||
const relativePaths: string[] = contributes.css.customData;
|
||||
relativePaths.forEach(rp => {
|
||||
dataPaths.push(path.resolve(extension.extensionPath, rp));
|
||||
dataPaths.push(joinPath(extension.extensionUri, rp).toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getNodeFSRequestService } from './nodeFs';
|
||||
import { ExtensionContext, extensions } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor } from '../cssClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
|
||||
import { TextDecoder } from 'util';
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main;
|
||||
const serverMain = clientMain?.replace('client', 'server').replace('cssClientMain', 'cssServerMain');
|
||||
if (!serverMain) {
|
||||
throw new Error('Unable to compute CSS server module path. Client: ' + clientMain);
|
||||
}
|
||||
|
||||
const serverModule = context.asAbsolutePath(serverMain);
|
||||
|
||||
// The debug options for the server
|
||||
const debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] };
|
||||
|
||||
// If the extension is launch in debug mode the debug server options are use
|
||||
// Otherwise the run options are used
|
||||
const serverOptions: ServerOptions = {
|
||||
run: { module: serverModule, transport: TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
|
||||
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
|
||||
return new LanguageClient(id, name, serverOptions, clientOptions);
|
||||
};
|
||||
|
||||
startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder });
|
||||
}
|
85
extensions/css-language-features/client/src/node/nodeFs.ts
Normal file
85
extensions/css-language-features/client/src/node/nodeFs.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { Uri } from 'vscode';
|
||||
import { getScheme, RequestService, FileType } from '../requests';
|
||||
|
||||
export function getNodeFSRequestService(): RequestService {
|
||||
function ensureFileUri(location: string) {
|
||||
if (getScheme(location) !== 'file') {
|
||||
throw new Error('fileRequestService can only handle file URLs');
|
||||
}
|
||||
}
|
||||
return {
|
||||
getContent(location: string, encoding?: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const uri = Uri.parse(location);
|
||||
fs.readFile(uri.fsPath, encoding, (err, buf) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
c(buf.toString());
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
stat(location: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const uri = Uri.parse(location);
|
||||
fs.stat(uri.fsPath, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 });
|
||||
} else {
|
||||
return e(err);
|
||||
}
|
||||
}
|
||||
|
||||
let type = FileType.Unknown;
|
||||
if (stats.isFile()) {
|
||||
type = FileType.File;
|
||||
} else if (stats.isDirectory()) {
|
||||
type = FileType.Directory;
|
||||
} else if (stats.isSymbolicLink()) {
|
||||
type = FileType.SymbolicLink;
|
||||
}
|
||||
|
||||
c({
|
||||
type,
|
||||
ctime: stats.ctime.getTime(),
|
||||
mtime: stats.mtime.getTime(),
|
||||
size: stats.size
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
readDirectory(location: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const path = Uri.parse(location).fsPath;
|
||||
|
||||
fs.readdir(path, { withFileTypes: true }, (err, children) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
c(children.map(stat => {
|
||||
if (stat.isSymbolicLink()) {
|
||||
return [stat.name, FileType.SymbolicLink];
|
||||
} else if (stat.isDirectory()) {
|
||||
return [stat.name, FileType.Directory];
|
||||
} else if (stat.isFile()) {
|
||||
return [stat.name, FileType.File];
|
||||
} else {
|
||||
return [stat.name, FileType.Unknown];
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
148
extensions/css-language-features/client/src/requests.ts
Normal file
148
extensions/css-language-features/client/src/requests.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { RequestType, CommonLanguageClient } from 'vscode-languageclient';
|
||||
import { Runtime } from './cssClient';
|
||||
|
||||
export namespace FsContentRequest {
|
||||
export const type: RequestType<{ uri: string; encoding?: string; }, string, any, any> = new RequestType('fs/content');
|
||||
}
|
||||
export namespace FsStatRequest {
|
||||
export const type: RequestType<string, FileStat, any, any> = new RequestType('fs/stat');
|
||||
}
|
||||
|
||||
export namespace FsReadDirRequest {
|
||||
export const type: RequestType<string, [string, FileType][], any, any> = new RequestType('fs/readDir');
|
||||
}
|
||||
|
||||
export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) {
|
||||
client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
|
||||
const uri = Uri.parse(param.uri);
|
||||
if (uri.scheme === 'file' && runtime.fs) {
|
||||
return runtime.fs.getContent(param.uri);
|
||||
}
|
||||
return workspace.fs.readFile(uri).then(buffer => {
|
||||
return new runtime.TextDecoder(param.encoding).decode(buffer);
|
||||
});
|
||||
});
|
||||
client.onRequest(FsReadDirRequest.type, (uriString: string) => {
|
||||
const uri = Uri.parse(uriString);
|
||||
if (uri.scheme === 'file' && runtime.fs) {
|
||||
return runtime.fs.readDirectory(uriString);
|
||||
}
|
||||
return workspace.fs.readDirectory(uri);
|
||||
});
|
||||
client.onRequest(FsStatRequest.type, (uriString: string) => {
|
||||
const uri = Uri.parse(uriString);
|
||||
if (uri.scheme === 'file' && runtime.fs) {
|
||||
return runtime.fs.stat(uriString);
|
||||
}
|
||||
return workspace.fs.stat(uri);
|
||||
});
|
||||
}
|
||||
|
||||
export enum FileType {
|
||||
/**
|
||||
* The file type is unknown.
|
||||
*/
|
||||
Unknown = 0,
|
||||
/**
|
||||
* A regular file.
|
||||
*/
|
||||
File = 1,
|
||||
/**
|
||||
* A directory.
|
||||
*/
|
||||
Directory = 2,
|
||||
/**
|
||||
* A symbolic link to a file.
|
||||
*/
|
||||
SymbolicLink = 64
|
||||
}
|
||||
export interface FileStat {
|
||||
/**
|
||||
* The type of the file, e.g. is a regular file, a directory, or symbolic link
|
||||
* to a file.
|
||||
*/
|
||||
type: FileType;
|
||||
/**
|
||||
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
ctime: number;
|
||||
/**
|
||||
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
mtime: number;
|
||||
/**
|
||||
* The size in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface RequestService {
|
||||
getContent(uri: string, encoding?: string): Promise<string>;
|
||||
|
||||
stat(uri: string): Promise<FileStat>;
|
||||
readDirectory(uri: string): Promise<[string, FileType][]>;
|
||||
}
|
||||
|
||||
export function getScheme(uri: string) {
|
||||
return uri.substr(0, uri.indexOf(':'));
|
||||
}
|
||||
|
||||
export function dirname(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
|
||||
}
|
||||
|
||||
export function basename(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return uri.substr(lastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
const Slash = '/'.charCodeAt(0);
|
||||
const Dot = '.'.charCodeAt(0);
|
||||
|
||||
export function isAbsolutePath(path: string) {
|
||||
return path.charCodeAt(0) === Slash;
|
||||
}
|
||||
|
||||
export function resolvePath(uri: Uri, path: string): Uri {
|
||||
if (isAbsolutePath(path)) {
|
||||
return uri.with({ path: normalizePath(path.split('/')) });
|
||||
}
|
||||
return joinPath(uri, path);
|
||||
}
|
||||
|
||||
export function normalizePath(parts: string[]): string {
|
||||
const newParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
|
||||
// ignore
|
||||
} else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
|
||||
newParts.pop();
|
||||
} else {
|
||||
newParts.push(part);
|
||||
}
|
||||
}
|
||||
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
|
||||
newParts.push('');
|
||||
}
|
||||
let res = newParts.join('/');
|
||||
if (parts[0].length === 0) {
|
||||
res = '/' + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function joinPath(uri: Uri, ...paths: string[]): Uri {
|
||||
const parts = uri.path.split('/');
|
||||
for (let path of paths) {
|
||||
parts.push(...path.split('/'));
|
||||
}
|
||||
return uri.with({ path: normalizePath(parts) });
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin(
|
||||
/vscode\-nls[\\/]lib[\\/]main\.js/,
|
||||
path.join(__dirname, 'client/out/browser/vscodeNlsShim.js')
|
||||
);
|
||||
|
||||
const clientConfig = withDefaults({
|
||||
target: 'webworker',
|
||||
context: path.join(__dirname, 'client'),
|
||||
entry: {
|
||||
extension: './src/browser/cssClientMain.ts'
|
||||
},
|
||||
output: {
|
||||
filename: 'cssClientMain.js',
|
||||
path: path.join(__dirname, 'client', 'dist', 'browser')
|
||||
}
|
||||
});
|
||||
clientConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
|
||||
clientConfig.module.rules[0].use.shift(); // remove nls loader
|
||||
|
||||
const serverConfig = withDefaults({
|
||||
target: 'webworker',
|
||||
context: path.join(__dirname, 'server'),
|
||||
entry: {
|
||||
extension: './src/browser/cssServerMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'cssServerMain.js',
|
||||
path: path.join(__dirname, 'server', 'dist', 'browser'),
|
||||
libraryTarget: 'var'
|
||||
}
|
||||
});
|
||||
serverConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
|
||||
serverConfig.module.rules[0].use.shift(); // remove nls loader
|
||||
|
||||
module.exports = [clientConfig, serverConfig];
|
|
@ -13,10 +13,10 @@ const path = require('path');
|
|||
module.exports = withDefaults({
|
||||
context: path.join(__dirname, 'client'),
|
||||
entry: {
|
||||
extension: './src/cssMain.ts',
|
||||
extension: './src/node/cssClientMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'cssMain.js',
|
||||
path: path.join(__dirname, 'client', 'dist')
|
||||
filename: 'cssClientMain.js',
|
||||
path: path.join(__dirname, 'client', 'dist', 'node')
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
"onLanguage:scss",
|
||||
"onCommand:_css.applyCodeAction"
|
||||
],
|
||||
"main": "./client/out/cssMain",
|
||||
"main": "./client/out/node/cssClientMain",
|
||||
"browser": "./client/dist/browser/cssClientMain",
|
||||
"enableProposedApi": true,
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server",
|
||||
|
@ -806,7 +807,7 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^6.1.3",
|
||||
"vscode-languageclient": "7.0.0-next.5",
|
||||
"vscode-nls": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -13,10 +13,10 @@ const path = require('path');
|
|||
module.exports = withDefaults({
|
||||
context: path.join(__dirname),
|
||||
entry: {
|
||||
extension: './src/cssServerMain.ts',
|
||||
extension: './src/node/cssServerMain.ts',
|
||||
},
|
||||
output: {
|
||||
filename: 'cssServerMain.js',
|
||||
path: path.join(__dirname, 'dist')
|
||||
path: path.join(__dirname, 'dist', 'node'),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"main": "./out/cssServerMain",
|
||||
"main": "./out/node/cssServerMain",
|
||||
"browser": "./dist/browser/cssServerMain",
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^4.1.2",
|
||||
"vscode-languageserver": "^6.1.1"
|
||||
"vscode-css-languageservice": "4.3.0-next.2",
|
||||
"vscode-languageserver": "7.0.0-next.3",
|
||||
"vscode-uri": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "7.0.2",
|
||||
|
@ -27,6 +29,6 @@
|
|||
"install-service-local": "npm install ../../../../vscode-css-languageservice -f",
|
||||
"install-server-next": "yarn add vscode-languageserver@next",
|
||||
"install-server-local": "npm install ../../../../vscode-languageserver-node/server -f",
|
||||
"test": "../../../node_modules/.bin/mocha"
|
||||
"test": "node ./test/index.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser';
|
||||
import { startServer } from '../cssServer';
|
||||
|
||||
declare let self: any;
|
||||
|
||||
const messageReader = new BrowserMessageReader(self);
|
||||
const messageWriter = new BrowserMessageWriter(self);
|
||||
|
||||
const connection = createConnection(messageReader, messageWriter);
|
||||
|
||||
startServer(connection, {});
|
373
extensions/css-language-features/server/src/cssServer.ts
Normal file
373
extensions/css-language-features/server/src/cssServer.ts
Normal file
|
@ -0,0 +1,373 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType
|
||||
} from 'vscode-languageserver/lib/common/api';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { formatError, runSafeAsync } from './utils/runner';
|
||||
import { getDocumentContext } from './utils/documentContext';
|
||||
import { fetchDataProviders } from './customData';
|
||||
import { RequestService, getRequestService } from './requests';
|
||||
|
||||
namespace CustomDataChangedNotification {
|
||||
export const type: NotificationType<string[]> = new NotificationType('css/customDataChanged');
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
css: LanguageSettings;
|
||||
less: LanguageSettings;
|
||||
scss: LanguageSettings;
|
||||
}
|
||||
|
||||
export interface RuntimeEnvironment {
|
||||
file?: RequestService;
|
||||
http?: RequestService
|
||||
}
|
||||
|
||||
export function startServer(connection: Connection, runtime: RuntimeEnvironment) {
|
||||
|
||||
// Create a text document manager.
|
||||
const documents = new TextDocuments(TextDocument);
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
const stylesheets = getLanguageModelCache<Stylesheet>(10, 60, document => getLanguageService(document).parseStylesheet(document));
|
||||
documents.onDidClose(e => {
|
||||
stylesheets.onDocumentRemoved(e.document);
|
||||
});
|
||||
connection.onShutdown(() => {
|
||||
stylesheets.dispose();
|
||||
});
|
||||
|
||||
let scopedSettingsSupport = false;
|
||||
let foldingRangeLimit = Number.MAX_VALUE;
|
||||
let workspaceFolders: WorkspaceFolder[];
|
||||
|
||||
let dataProvidersReady: Promise<any> = Promise.resolve();
|
||||
|
||||
const languageServices: { [id: string]: LanguageService } = {};
|
||||
|
||||
const notReady = () => Promise.reject('Not Ready');
|
||||
let requestService: RequestService = { getContent: notReady, stat: notReady, readDirectory: notReady };
|
||||
|
||||
// After the server has started the client sends an initialize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilities.
|
||||
connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
workspaceFolders = (<any>params).workspaceFolders;
|
||||
if (!Array.isArray(workspaceFolders)) {
|
||||
workspaceFolders = [];
|
||||
if (params.rootPath) {
|
||||
workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() });
|
||||
}
|
||||
}
|
||||
|
||||
requestService = getRequestService(params.initializationOptions.handledSchemas || ['file'], connection, runtime);
|
||||
|
||||
function getClientCapability<T>(name: string, def: T) {
|
||||
const keys = name.split('.');
|
||||
let c: any = params.capabilities;
|
||||
for (let i = 0; c && i < keys.length; i++) {
|
||||
if (!c.hasOwnProperty(keys[i])) {
|
||||
return def;
|
||||
}
|
||||
c = c[keys[i]];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
|
||||
scopedSettingsSupport = !!getClientCapability('workspace.configuration', false);
|
||||
foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
|
||||
|
||||
languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities });
|
||||
languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities });
|
||||
languageServices.less = getLESSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities });
|
||||
|
||||
const capabilities: ServerCapabilities = {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-'] } : undefined,
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
referencesProvider: true,
|
||||
definitionProvider: true,
|
||||
documentHighlightProvider: true,
|
||||
documentLinkProvider: {
|
||||
resolveProvider: false
|
||||
},
|
||||
codeActionProvider: true,
|
||||
renameProvider: true,
|
||||
colorProvider: {},
|
||||
foldingRangeProvider: true,
|
||||
selectionRangeProvider: true
|
||||
};
|
||||
return { capabilities };
|
||||
});
|
||||
|
||||
function getLanguageService(document: TextDocument) {
|
||||
let service = languageServices[document.languageId];
|
||||
if (!service) {
|
||||
connection.console.log('Document type is ' + document.languageId + ', using css instead.');
|
||||
service = languageServices['css'];
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
let documentSettings: { [key: string]: Thenable<LanguageSettings | undefined> } = {};
|
||||
// remove document settings on close
|
||||
documents.onDidClose(e => {
|
||||
delete documentSettings[e.document.uri];
|
||||
});
|
||||
function getDocumentSettings(textDocument: TextDocument): Thenable<LanguageSettings | undefined> {
|
||||
if (scopedSettingsSupport) {
|
||||
let promise = documentSettings[textDocument.uri];
|
||||
if (!promise) {
|
||||
const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] };
|
||||
promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0]);
|
||||
documentSettings[textDocument.uri] = promise;
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
updateConfiguration(<Settings>change.settings);
|
||||
});
|
||||
|
||||
function updateConfiguration(settings: Settings) {
|
||||
for (const languageId in languageServices) {
|
||||
languageServices[languageId].configure((settings as any)[languageId]);
|
||||
}
|
||||
// reset all document settings
|
||||
documentSettings = {};
|
||||
// Revalidate any open text documents
|
||||
documents.all().forEach(triggerValidation);
|
||||
}
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
|
||||
const validationDelayMs = 500;
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
});
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
});
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
clearTimeout(request);
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
validateTextDocument(textDocument);
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
function validateTextDocument(textDocument: TextDocument): void {
|
||||
const settingsPromise = getDocumentSettings(textDocument);
|
||||
Promise.all([settingsPromise, dataProvidersReady]).then(async ([settings]) => {
|
||||
const stylesheet = stylesheets.get(textDocument);
|
||||
const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings);
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}, e => {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateDataProviders(dataPaths: string[]) {
|
||||
dataProvidersReady = fetchDataProviders(dataPaths, requestService).then(customDataProviders => {
|
||||
for (const lang in languageServices) {
|
||||
languageServices[lang].setDataProviders(true, customDataProviders);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
connection.onCompletion((textDocumentPosition, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const styleSheet = stylesheets.get(document);
|
||||
const documentContext = getDocumentContext(document.uri, workspaceFolders);
|
||||
return getLanguageService(document).doComplete2(document, textDocumentPosition.position, styleSheet, documentContext);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onHover((textDocumentPosition, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const styleSheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentSymbol((documentSymbolParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentSymbols(document, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDefinition((documentDefinitionParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(documentDefinitionParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDefinition(document, documentDefinitionParams.position, stylesheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing definitions for ${documentDefinitionParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentHighlight((documentHighlightParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(documentHighlightParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentHighlights(document, documentHighlightParams.position, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
|
||||
connection.onDocumentLinks(async (documentLinkParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(documentLinkParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const documentContext = getDocumentContext(document.uri, workspaceFolders);
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentLinks2(document, stylesheet, documentContext);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document links for ${documentLinkParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
|
||||
connection.onReferences((referenceParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(referenceParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onCodeAction((codeActionParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(codeActionParams.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentColor((params, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentColors(document, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onColorPresentation((params, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing color presentations for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onRenameRequest((renameParameters, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(renameParameters.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing renames for ${renameParameters.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onFoldingRanges((params, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
return getLanguageService(document).getFoldingRanges(document, { rangeLimit: foldingRangeLimit });
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onSelectionRanges((params, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
const positions: Position[] = params.positions;
|
||||
|
||||
if (document) {
|
||||
await dataProvidersReady;
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).getSelectionRanges(document, positions, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onNotification(CustomDataChangedNotification.type, updateDataProviders);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,390 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind
|
||||
} from 'vscode-languageserver';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { stat as fsStat } from 'fs';
|
||||
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, FileSystemProvider, FileType, TextDocument, CompletionList, Position } from 'vscode-css-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { getPathCompletionParticipant } from './pathCompletion';
|
||||
import { formatError, runSafe, runSafeAsync } from './utils/runner';
|
||||
import { getDocumentContext } from './utils/documentContext';
|
||||
import { getDataProviders } from './customData';
|
||||
|
||||
export interface Settings {
|
||||
css: LanguageSettings;
|
||||
less: LanguageSettings;
|
||||
scss: LanguageSettings;
|
||||
}
|
||||
|
||||
// Create a connection for the server.
|
||||
const connection: IConnection = createConnection();
|
||||
|
||||
console.log = connection.console.log.bind(connection.console);
|
||||
console.error = connection.console.error.bind(connection.console);
|
||||
|
||||
process.on('unhandledRejection', (e: any) => {
|
||||
connection.console.error(formatError(`Unhandled exception`, e));
|
||||
});
|
||||
|
||||
// Create a text document manager.
|
||||
const documents = new TextDocuments(TextDocument);
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
const stylesheets = getLanguageModelCache<Stylesheet>(10, 60, document => getLanguageService(document).parseStylesheet(document));
|
||||
documents.onDidClose(e => {
|
||||
stylesheets.onDocumentRemoved(e.document);
|
||||
});
|
||||
connection.onShutdown(() => {
|
||||
stylesheets.dispose();
|
||||
});
|
||||
|
||||
let scopedSettingsSupport = false;
|
||||
let foldingRangeLimit = Number.MAX_VALUE;
|
||||
let workspaceFolders: WorkspaceFolder[];
|
||||
|
||||
const languageServices: { [id: string]: LanguageService } = {};
|
||||
|
||||
const fileSystemProvider: FileSystemProvider = {
|
||||
stat(documentUri: string) {
|
||||
const filePath = URI.parse(documentUri).fsPath;
|
||||
|
||||
return new Promise((c, e) => {
|
||||
fsStat(filePath, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return c({
|
||||
type: FileType.Unknown,
|
||||
ctime: -1,
|
||||
mtime: -1,
|
||||
size: -1
|
||||
});
|
||||
} else {
|
||||
return e(err);
|
||||
}
|
||||
}
|
||||
|
||||
let type = FileType.Unknown;
|
||||
if (stats.isFile()) {
|
||||
type = FileType.File;
|
||||
} else if (stats.isDirectory()) {
|
||||
type = FileType.Directory;
|
||||
} else if (stats.isSymbolicLink()) {
|
||||
type = FileType.SymbolicLink;
|
||||
}
|
||||
|
||||
c({
|
||||
type,
|
||||
ctime: stats.ctime.getTime(),
|
||||
mtime: stats.mtime.getTime(),
|
||||
size: stats.size
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// After the server has started the client sends an initialize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilities.
|
||||
connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
workspaceFolders = (<any>params).workspaceFolders;
|
||||
if (!Array.isArray(workspaceFolders)) {
|
||||
workspaceFolders = [];
|
||||
if (params.rootPath) {
|
||||
workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() });
|
||||
}
|
||||
}
|
||||
|
||||
const dataPaths: string[] = params.initializationOptions.dataPaths || [];
|
||||
const customDataProviders = getDataProviders(dataPaths);
|
||||
|
||||
function getClientCapability<T>(name: string, def: T) {
|
||||
const keys = name.split('.');
|
||||
let c: any = params.capabilities;
|
||||
for (let i = 0; c && i < keys.length; i++) {
|
||||
if (!c.hasOwnProperty(keys[i])) {
|
||||
return def;
|
||||
}
|
||||
c = c[keys[i]];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
|
||||
scopedSettingsSupport = !!getClientCapability('workspace.configuration', false);
|
||||
foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
|
||||
|
||||
languageServices.css = getCSSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities });
|
||||
languageServices.scss = getSCSSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities });
|
||||
languageServices.less = getLESSLanguageService({ customDataProviders, fileSystemProvider, clientCapabilities: params.capabilities });
|
||||
|
||||
const capabilities: ServerCapabilities = {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-'] } : undefined,
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
referencesProvider: true,
|
||||
definitionProvider: true,
|
||||
documentHighlightProvider: true,
|
||||
documentLinkProvider: {
|
||||
resolveProvider: false
|
||||
},
|
||||
codeActionProvider: true,
|
||||
renameProvider: true,
|
||||
colorProvider: {},
|
||||
foldingRangeProvider: true,
|
||||
selectionRangeProvider: true
|
||||
};
|
||||
return { capabilities };
|
||||
});
|
||||
|
||||
function getLanguageService(document: TextDocument) {
|
||||
let service = languageServices[document.languageId];
|
||||
if (!service) {
|
||||
connection.console.log('Document type is ' + document.languageId + ', using css instead.');
|
||||
service = languageServices['css'];
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
let documentSettings: { [key: string]: Thenable<LanguageSettings | undefined> } = {};
|
||||
// remove document settings on close
|
||||
documents.onDidClose(e => {
|
||||
delete documentSettings[e.document.uri];
|
||||
});
|
||||
function getDocumentSettings(textDocument: TextDocument): Thenable<LanguageSettings | undefined> {
|
||||
if (scopedSettingsSupport) {
|
||||
let promise = documentSettings[textDocument.uri];
|
||||
if (!promise) {
|
||||
const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] };
|
||||
promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0]);
|
||||
documentSettings[textDocument.uri] = promise;
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
updateConfiguration(<Settings>change.settings);
|
||||
});
|
||||
|
||||
function updateConfiguration(settings: Settings) {
|
||||
for (const languageId in languageServices) {
|
||||
languageServices[languageId].configure((settings as any)[languageId]);
|
||||
}
|
||||
// reset all document settings
|
||||
documentSettings = {};
|
||||
// Revalidate any open text documents
|
||||
documents.all().forEach(triggerValidation);
|
||||
}
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
|
||||
const validationDelayMs = 500;
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
});
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
});
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
clearTimeout(request);
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
validateTextDocument(textDocument);
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
function validateTextDocument(textDocument: TextDocument): void {
|
||||
const settingsPromise = getDocumentSettings(textDocument);
|
||||
settingsPromise.then(settings => {
|
||||
const stylesheet = stylesheets.get(textDocument);
|
||||
const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings);
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}, e => {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
|
||||
});
|
||||
}
|
||||
|
||||
connection.onCompletion((textDocumentPosition, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
if (!document) {
|
||||
return null;
|
||||
}
|
||||
const cssLS = getLanguageService(document);
|
||||
const pathCompletionList: CompletionList = {
|
||||
isIncomplete: false,
|
||||
items: []
|
||||
};
|
||||
cssLS.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, pathCompletionList)]);
|
||||
const result = cssLS.doComplete(document, textDocumentPosition.position, stylesheets.get(document));
|
||||
return {
|
||||
isIncomplete: pathCompletionList.isIncomplete,
|
||||
items: [...pathCompletionList.items, ...result.items]
|
||||
};
|
||||
}, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onHover((textDocumentPosition, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
if (document) {
|
||||
const styleSheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentSymbol((documentSymbolParams, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentSymbols(document, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDefinition((documentDefinitionParams, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(documentDefinitionParams.textDocument.uri);
|
||||
if (document) {
|
||||
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDefinition(document, documentDefinitionParams.position, stylesheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing definitions for ${documentDefinitionParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentHighlight((documentHighlightParams, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(documentHighlightParams.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentHighlights(document, documentHighlightParams.position, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
|
||||
connection.onDocumentLinks(async (documentLinkParams, token) => {
|
||||
return runSafeAsync(async () => {
|
||||
const document = documents.get(documentLinkParams.textDocument.uri);
|
||||
if (document) {
|
||||
const documentContext = getDocumentContext(document.uri, workspaceFolders);
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return await getLanguageService(document).findDocumentLinks2(document, stylesheet, documentContext);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document links for ${documentLinkParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
|
||||
connection.onReferences((referenceParams, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(referenceParams.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onCodeAction((codeActionParams, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(codeActionParams.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onDocumentColor((params, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).findDocumentColors(document, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onColorPresentation((params, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing color presentations for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onRenameRequest((renameParameters, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(renameParameters.textDocument.uri);
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet);
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing renames for ${renameParameters.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onFoldingRanges((params, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return getLanguageService(document).getFoldingRanges(document, { rangeLimit: foldingRangeLimit });
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
connection.onSelectionRanges((params, token) => {
|
||||
return runSafe(() => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
const positions: Position[] = params.positions;
|
||||
|
||||
if (document) {
|
||||
const stylesheet = stylesheets.get(document);
|
||||
return getLanguageService(document).getSelectionRanges(document, positions, stylesheet);
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
|
@ -3,48 +3,36 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CSSDataV1, ICSSDataProvider } from 'vscode-css-languageservice';
|
||||
import * as fs from 'fs';
|
||||
import { ICSSDataProvider, newCSSDataProvider } from 'vscode-css-languageservice';
|
||||
import { RequestService } from './requests';
|
||||
|
||||
export function getDataProviders(dataPaths: string[]): ICSSDataProvider[] {
|
||||
const providers = dataPaths.map(p => {
|
||||
if (fs.existsSync(p)) {
|
||||
const data = parseCSSData(fs.readFileSync(p, 'utf-8'));
|
||||
return {
|
||||
provideProperties: () => data.properties || [],
|
||||
provideAtDirectives: () => data.atDirectives || [],
|
||||
providePseudoClasses: () => data.pseudoClasses || [],
|
||||
providePseudoElements: () => data.pseudoElements || []
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
provideProperties: () => [],
|
||||
provideAtDirectives: () => [],
|
||||
providePseudoClasses: () => [],
|
||||
providePseudoElements: () => []
|
||||
};
|
||||
export function fetchDataProviders(dataPaths: string[], requestService: RequestService): Promise<ICSSDataProvider[]> {
|
||||
const providers = dataPaths.map(async p => {
|
||||
try {
|
||||
const content = await requestService.getContent(p);
|
||||
return parseCSSData(content);
|
||||
} catch (e) {
|
||||
return newCSSDataProvider({ version: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
return providers;
|
||||
return Promise.all(providers);
|
||||
}
|
||||
|
||||
function parseCSSData(source: string): CSSDataV1 {
|
||||
function parseCSSData(source: string): ICSSDataProvider {
|
||||
let rawData: any;
|
||||
|
||||
try {
|
||||
rawData = JSON.parse(source);
|
||||
} catch (err) {
|
||||
return {
|
||||
version: 1
|
||||
};
|
||||
return newCSSDataProvider({ version: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
return newCSSDataProvider({
|
||||
version: 1,
|
||||
properties: rawData.properties || [],
|
||||
atDirectives: rawData.atDirectives || [],
|
||||
pseudoClasses: rawData.pseudoClasses || [],
|
||||
pseudoElements: rawData.pseudoElements || []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createConnection, Connection } from 'vscode-languageserver/node';
|
||||
import { formatError } from '../utils/runner';
|
||||
import { startServer } from '../cssServer';
|
||||
import { getNodeFSRequestService } from './nodeFs';
|
||||
|
||||
// Create a connection for the server.
|
||||
const connection: Connection = createConnection();
|
||||
|
||||
console.log = connection.console.log.bind(connection.console);
|
||||
console.error = connection.console.error.bind(connection.console);
|
||||
|
||||
process.on('unhandledRejection', (e: any) => {
|
||||
connection.console.error(formatError(`Unhandled exception`, e));
|
||||
});
|
||||
|
||||
startServer(connection, { file: getNodeFSRequestService() });
|
87
extensions/css-language-features/server/src/node/nodeFs.ts
Normal file
87
extensions/css-language-features/server/src/node/nodeFs.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RequestService, getScheme } from '../requests';
|
||||
import { URI as Uri } from 'vscode-uri';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { FileType } from 'vscode-css-languageservice';
|
||||
|
||||
export function getNodeFSRequestService(): RequestService {
|
||||
function ensureFileUri(location: string) {
|
||||
if (getScheme(location) !== 'file') {
|
||||
throw new Error('fileRequestService can only handle file URLs');
|
||||
}
|
||||
}
|
||||
return {
|
||||
getContent(location: string, encoding?: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const uri = Uri.parse(location);
|
||||
fs.readFile(uri.fsPath, encoding, (err, buf) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
c(buf.toString());
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
stat(location: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const uri = Uri.parse(location);
|
||||
fs.stat(uri.fsPath, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return c({ type: FileType.Unknown, ctime: -1, mtime: -1, size: -1 });
|
||||
} else {
|
||||
return e(err);
|
||||
}
|
||||
}
|
||||
|
||||
let type = FileType.Unknown;
|
||||
if (stats.isFile()) {
|
||||
type = FileType.File;
|
||||
} else if (stats.isDirectory()) {
|
||||
type = FileType.Directory;
|
||||
} else if (stats.isSymbolicLink()) {
|
||||
type = FileType.SymbolicLink;
|
||||
}
|
||||
|
||||
c({
|
||||
type,
|
||||
ctime: stats.ctime.getTime(),
|
||||
mtime: stats.mtime.getTime(),
|
||||
size: stats.size
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
readDirectory(location: string) {
|
||||
ensureFileUri(location);
|
||||
return new Promise((c, e) => {
|
||||
const path = Uri.parse(location).fsPath;
|
||||
|
||||
fs.readdir(path, { withFileTypes: true }, (err, children) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
c(children.map(stat => {
|
||||
if (stat.isSymbolicLink()) {
|
||||
return [stat.name, FileType.SymbolicLink];
|
||||
} else if (stat.isDirectory()) {
|
||||
return [stat.name, FileType.Directory];
|
||||
} else if (stat.isFile()) {
|
||||
return [stat.name, FileType.File];
|
||||
} else {
|
||||
return [stat.name, FileType.Unknown];
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { URI } from 'vscode-uri';
|
||||
|
||||
import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types';
|
||||
import { WorkspaceFolder } from 'vscode-languageserver';
|
||||
import { ICompletionParticipant } from 'vscode-css-languageservice';
|
||||
|
||||
import { startsWith, endsWith } from './utils/strings';
|
||||
|
||||
export function getPathCompletionParticipant(
|
||||
document: TextDocument,
|
||||
workspaceFolders: WorkspaceFolder[],
|
||||
result: CompletionList
|
||||
): ICompletionParticipant {
|
||||
return {
|
||||
onCssURILiteralValue: ({ position, range, uriValue }) => {
|
||||
const fullValue = stripQuotes(uriValue);
|
||||
if (!shouldDoPathCompletion(uriValue, workspaceFolders)) {
|
||||
if (fullValue === '.' || fullValue === '..') {
|
||||
result.isIncomplete = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let suggestions = providePathSuggestions(uriValue, position, range, document, workspaceFolders);
|
||||
result.items = [...suggestions, ...result.items];
|
||||
},
|
||||
onCssImportPath: ({ position, range, pathValue }) => {
|
||||
const fullValue = stripQuotes(pathValue);
|
||||
if (!shouldDoPathCompletion(pathValue, workspaceFolders)) {
|
||||
if (fullValue === '.' || fullValue === '..') {
|
||||
result.isIncomplete = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let suggestions = providePathSuggestions(pathValue, position, range, document, workspaceFolders);
|
||||
|
||||
if (document.languageId === 'scss') {
|
||||
suggestions.forEach(s => {
|
||||
if (startsWith(s.label, '_') && endsWith(s.label, '.scss')) {
|
||||
if (s.textEdit) {
|
||||
s.textEdit.newText = s.label.slice(1, -5);
|
||||
} else {
|
||||
s.label = s.label.slice(1, -5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result.items = [...suggestions, ...result.items];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function providePathSuggestions(pathValue: string, position: Position, range: Range, document: TextDocument, workspaceFolders: WorkspaceFolder[]) {
|
||||
const fullValue = stripQuotes(pathValue);
|
||||
const isValueQuoted = startsWith(pathValue, `'`) || startsWith(pathValue, `"`);
|
||||
const valueBeforeCursor = isValueQuoted
|
||||
? fullValue.slice(0, position.character - (range.start.character + 1))
|
||||
: fullValue.slice(0, position.character - range.start.character);
|
||||
const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders);
|
||||
const currentDocFsPath = URI.parse(document.uri).fsPath;
|
||||
|
||||
const paths = providePaths(valueBeforeCursor, currentDocFsPath, workspaceRoot)
|
||||
.filter(p => {
|
||||
// Exclude current doc's path
|
||||
return path.resolve(currentDocFsPath, '../', p) !== currentDocFsPath;
|
||||
})
|
||||
.filter(p => {
|
||||
// Exclude paths that start with `.`
|
||||
return p[0] !== '.';
|
||||
});
|
||||
|
||||
const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range;
|
||||
const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange);
|
||||
|
||||
const suggestions = paths.map(p => pathToSuggestion(p, replaceRange));
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
function shouldDoPathCompletion(pathValue: string, workspaceFolders: WorkspaceFolder[]): boolean {
|
||||
const fullValue = stripQuotes(pathValue);
|
||||
if (fullValue === '.' || fullValue === '..') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function stripQuotes(fullValue: string) {
|
||||
if (startsWith(fullValue, `'`) || startsWith(fullValue, `"`)) {
|
||||
return fullValue.slice(1, -1);
|
||||
} else {
|
||||
return fullValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of path suggestions. Folder suggestions are suffixed with a slash.
|
||||
*/
|
||||
function providePaths(valueBeforeCursor: string, activeDocFsPath: string, root?: string): string[] {
|
||||
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
|
||||
const valueBeforeLastSlash = valueBeforeCursor.slice(0, lastIndexOfSlash + 1);
|
||||
|
||||
const startsWithSlash = startsWith(valueBeforeCursor, '/');
|
||||
let parentDir: string;
|
||||
if (startsWithSlash) {
|
||||
if (!root) {
|
||||
return [];
|
||||
}
|
||||
parentDir = path.resolve(root, '.' + valueBeforeLastSlash);
|
||||
} else {
|
||||
parentDir = path.resolve(activeDocFsPath, '..', valueBeforeLastSlash);
|
||||
}
|
||||
|
||||
try {
|
||||
return fs.readdirSync(parentDir).map(f => {
|
||||
return isDir(path.resolve(parentDir, f))
|
||||
? f + '/'
|
||||
: f;
|
||||
});
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const isDir = (p: string) => {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function pathToReplaceRange(valueBeforeCursor: string, fullValue: string, fullValueRange: Range) {
|
||||
let replaceRange: Range;
|
||||
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
|
||||
if (lastIndexOfSlash === -1) {
|
||||
replaceRange = fullValueRange;
|
||||
} else {
|
||||
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
|
||||
// Find the last slash before cursor, and calculate the start of replace range from there
|
||||
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
|
||||
const startPos = shiftPosition(fullValueRange.end, -valueAfterLastSlash.length);
|
||||
// If whitespace exists, replace until it
|
||||
const whitespaceIndex = valueAfterLastSlash.indexOf(' ');
|
||||
let endPos;
|
||||
if (whitespaceIndex !== -1) {
|
||||
endPos = shiftPosition(startPos, whitespaceIndex);
|
||||
} else {
|
||||
endPos = fullValueRange.end;
|
||||
}
|
||||
replaceRange = Range.create(startPos, endPos);
|
||||
}
|
||||
|
||||
return replaceRange;
|
||||
}
|
||||
|
||||
function pathToSuggestion(p: string, replaceRange: Range): CompletionItem {
|
||||
const isDir = p[p.length - 1] === '/';
|
||||
|
||||
if (isDir) {
|
||||
return {
|
||||
label: escapePath(p),
|
||||
kind: CompletionItemKind.Folder,
|
||||
textEdit: TextEdit.replace(replaceRange, escapePath(p)),
|
||||
command: {
|
||||
title: 'Suggest',
|
||||
command: 'editor.action.triggerSuggest'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: escapePath(p),
|
||||
kind: CompletionItemKind.File,
|
||||
textEdit: TextEdit.replace(replaceRange, escapePath(p))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Escape https://www.w3.org/TR/CSS1/#url
|
||||
function escapePath(p: string) {
|
||||
return p.replace(/(\s|\(|\)|,|"|')/g, '\\$1');
|
||||
}
|
||||
|
||||
function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: WorkspaceFolder[]): string | undefined {
|
||||
for (const folder of workspaceFolders) {
|
||||
if (startsWith(activeDoc.uri, folder.uri)) {
|
||||
return path.resolve(URI.parse(folder.uri).fsPath);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function shiftPosition(pos: Position, offset: number): Position {
|
||||
return Position.create(pos.line, pos.character + offset);
|
||||
}
|
||||
function shiftRange(range: Range, startOffset: number, endOffset: number): Range {
|
||||
const start = shiftPosition(range.start, startOffset);
|
||||
const end = shiftPosition(range.end, endOffset);
|
||||
return Range.create(start, end);
|
||||
}
|
160
extensions/css-language-features/server/src/requests.ts
Normal file
160
extensions/css-language-features/server/src/requests.ts
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vscode-uri';
|
||||
import { RequestType, Connection } from 'vscode-languageserver';
|
||||
import { RuntimeEnvironment } from './cssServer';
|
||||
|
||||
export namespace FsContentRequest {
|
||||
export const type: RequestType<{ uri: string; encoding?: string; }, string, any, any> = new RequestType('fs/content');
|
||||
}
|
||||
export namespace FsStatRequest {
|
||||
export const type: RequestType<string, FileStat, any, any> = new RequestType('fs/stat');
|
||||
}
|
||||
|
||||
export namespace FsReadDirRequest {
|
||||
export const type: RequestType<string, [string, FileType][], any, any> = new RequestType('fs/readDir');
|
||||
}
|
||||
|
||||
export enum FileType {
|
||||
/**
|
||||
* The file type is unknown.
|
||||
*/
|
||||
Unknown = 0,
|
||||
/**
|
||||
* A regular file.
|
||||
*/
|
||||
File = 1,
|
||||
/**
|
||||
* A directory.
|
||||
*/
|
||||
Directory = 2,
|
||||
/**
|
||||
* A symbolic link to a file.
|
||||
*/
|
||||
SymbolicLink = 64
|
||||
}
|
||||
export interface FileStat {
|
||||
/**
|
||||
* The type of the file, e.g. is a regular file, a directory, or symbolic link
|
||||
* to a file.
|
||||
*/
|
||||
type: FileType;
|
||||
/**
|
||||
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
ctime: number;
|
||||
/**
|
||||
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
|
||||
*/
|
||||
mtime: number;
|
||||
/**
|
||||
* The size in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface RequestService {
|
||||
getContent(uri: string, encoding?: string): Promise<string>;
|
||||
|
||||
stat(uri: string): Promise<FileStat>;
|
||||
readDirectory(uri: string): Promise<[string, FileType][]>;
|
||||
}
|
||||
|
||||
|
||||
export function getRequestService(handledSchemas: string[], connection: Connection, runtime: RuntimeEnvironment): RequestService {
|
||||
const builtInHandlers: { [protocol: string]: RequestService | undefined } = {};
|
||||
for (let protocol of handledSchemas) {
|
||||
if (protocol === 'file') {
|
||||
builtInHandlers[protocol] = runtime.file;
|
||||
} else if (protocol === 'http' || protocol === 'https') {
|
||||
builtInHandlers[protocol] = runtime.http;
|
||||
}
|
||||
}
|
||||
return {
|
||||
async stat(uri: string): Promise<FileStat> {
|
||||
const handler = builtInHandlers[getScheme(uri)];
|
||||
if (handler) {
|
||||
return handler.stat(uri);
|
||||
}
|
||||
const res = await connection.sendRequest(FsStatRequest.type, uri.toString());
|
||||
return res;
|
||||
},
|
||||
readDirectory(uri: string): Promise<[string, FileType][]> {
|
||||
const handler = builtInHandlers[getScheme(uri)];
|
||||
if (handler) {
|
||||
return handler.readDirectory(uri);
|
||||
}
|
||||
return connection.sendRequest(FsReadDirRequest.type, uri.toString());
|
||||
},
|
||||
getContent(uri: string, encoding?: string): Promise<string> {
|
||||
const handler = builtInHandlers[getScheme(uri)];
|
||||
if (handler) {
|
||||
return handler.getContent(uri, encoding);
|
||||
}
|
||||
return connection.sendRequest(FsContentRequest.type, { uri: uri.toString(), encoding });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getScheme(uri: string) {
|
||||
return uri.substr(0, uri.indexOf(':'));
|
||||
}
|
||||
|
||||
export function dirname(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
|
||||
}
|
||||
|
||||
export function basename(uri: string) {
|
||||
const lastIndexOfSlash = uri.lastIndexOf('/');
|
||||
return uri.substr(lastIndexOfSlash + 1);
|
||||
}
|
||||
|
||||
const Slash = '/'.charCodeAt(0);
|
||||
const Dot = '.'.charCodeAt(0);
|
||||
|
||||
export function isAbsolutePath(path: string) {
|
||||
return path.charCodeAt(0) === Slash;
|
||||
}
|
||||
|
||||
export function resolvePath(uriString: string, path: string): string {
|
||||
if (isAbsolutePath(path)) {
|
||||
const uri = URI.parse(uriString);
|
||||
const parts = path.split('/');
|
||||
return uri.with({ path: normalizePath(parts) }).toString();
|
||||
}
|
||||
return joinPath(uriString, path);
|
||||
}
|
||||
|
||||
export function normalizePath(parts: string[]): string {
|
||||
const newParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
|
||||
// ignore
|
||||
} else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
|
||||
newParts.pop();
|
||||
} else {
|
||||
newParts.push(part);
|
||||
}
|
||||
}
|
||||
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
|
||||
newParts.push('');
|
||||
}
|
||||
let res = newParts.join('/');
|
||||
if (parts[0].length === 0) {
|
||||
res = '/' + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export function joinPath(uriString: string, ...paths: string[]): string {
|
||||
const uri = URI.parse(uriString);
|
||||
const parts = uri.path.split('/');
|
||||
for (let path of paths) {
|
||||
parts.push(...path.split('/'));
|
||||
}
|
||||
return uri.with({ path: normalizePath(parts) }).toString();
|
||||
}
|
|
@ -6,10 +6,11 @@ import 'mocha';
|
|||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { TextDocument, CompletionList } from 'vscode-languageserver-types';
|
||||
import { TextDocument, CompletionList, TextEdit } from 'vscode-languageserver-types';
|
||||
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
|
||||
import { getPathCompletionParticipant } from '../pathCompletion';
|
||||
import { getCSSLanguageService } from 'vscode-css-languageservice';
|
||||
import { getCSSLanguageService, LanguageServiceOptions, getSCSSLanguageService } from 'vscode-css-languageservice';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
import { getDocumentContext } from '../utils/documentContext';
|
||||
|
||||
export interface ItemDescription {
|
||||
label: string;
|
||||
|
@ -17,7 +18,6 @@ export interface ItemDescription {
|
|||
}
|
||||
|
||||
suite('Completions', () => {
|
||||
const cssLanguageService = getCSSLanguageService();
|
||||
|
||||
let assertCompletion = function (completions: CompletionList, expected: ItemDescription, document: TextDocument, _offset: number) {
|
||||
let matches = completions.items.filter(completion => {
|
||||
|
@ -26,12 +26,12 @@ suite('Completions', () => {
|
|||
|
||||
assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`);
|
||||
let match = matches[0];
|
||||
if (expected.resultText && match.textEdit) {
|
||||
if (expected.resultText && TextEdit.is(match.textEdit)) {
|
||||
assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText);
|
||||
}
|
||||
};
|
||||
|
||||
function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): void {
|
||||
async function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): Promise<any> {
|
||||
const offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
|
@ -42,12 +42,12 @@ suite('Completions', () => {
|
|||
workspaceFolders = [{ name: 'x', uri: testUri.substr(0, testUri.lastIndexOf('/')) }];
|
||||
}
|
||||
|
||||
let participantResult = CompletionList.create([]);
|
||||
cssLanguageService.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, participantResult)]);
|
||||
const lsOptions: LanguageServiceOptions = { fileSystemProvider: getNodeFSRequestService() };
|
||||
const cssLanguageService = lang === 'scss' ? getSCSSLanguageService(lsOptions) : getCSSLanguageService(lsOptions);
|
||||
|
||||
const context = getDocumentContext(testUri, workspaceFolders);
|
||||
const stylesheet = cssLanguageService.parseStylesheet(document);
|
||||
let list = cssLanguageService.doComplete(document, position, stylesheet)!;
|
||||
list.items = list.items.concat(participantResult.items);
|
||||
let list = await cssLanguageService.doComplete2(document, position, stylesheet, context);
|
||||
|
||||
if (expected.count) {
|
||||
assert.equal(list.items.length, expected.count);
|
||||
|
@ -59,17 +59,17 @@ suite('Completions', () => {
|
|||
}
|
||||
}
|
||||
|
||||
test('CSS url() Path completion', function () {
|
||||
test('CSS url() Path completion', async function () {
|
||||
let testUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: URI.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
assertCompletions('html { background-image: url("./|")', {
|
||||
await assertCompletions('html { background-image: url("./|")', {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: 'html { background-image: url("./about.html")' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('../|')`, {
|
||||
await assertCompletions(`html { background-image: url('../|')`, {
|
||||
items: [
|
||||
{ label: 'about/', resultText: `html { background-image: url('../about/')` },
|
||||
{ label: 'index.html', resultText: `html { background-image: url('../index.html')` },
|
||||
|
@ -77,7 +77,7 @@ suite('Completions', () => {
|
|||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('../src/a|')`, {
|
||||
await assertCompletions(`html { background-image: url('../src/a|')`, {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: `html { background-image: url('../src/feature.js')` },
|
||||
{ label: 'data/', resultText: `html { background-image: url('../src/data/')` },
|
||||
|
@ -85,25 +85,25 @@ suite('Completions', () => {
|
|||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('../src/data/f|.asar')`, {
|
||||
await assertCompletions(`html { background-image: url('../src/data/f|.asar')`, {
|
||||
items: [
|
||||
{ label: 'foo.asar', resultText: `html { background-image: url('../src/data/foo.asar')` }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('|')`, {
|
||||
await assertCompletions(`html { background-image: url('|')`, {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: `html { background-image: url('about.html')` },
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('/|')`, {
|
||||
await assertCompletions(`html { background-image: url('/|')`, {
|
||||
items: [
|
||||
{ label: 'pathCompletionFixtures/', resultText: `html { background-image: url('/pathCompletionFixtures/')` }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url('/pathCompletionFixtures/|')`, {
|
||||
await assertCompletions(`html { background-image: url('/pathCompletionFixtures/|')`, {
|
||||
items: [
|
||||
{ label: 'about/', resultText: `html { background-image: url('/pathCompletionFixtures/about/')` },
|
||||
{ label: 'index.html', resultText: `html { background-image: url('/pathCompletionFixtures/index.html')` },
|
||||
|
@ -111,53 +111,53 @@ suite('Completions', () => {
|
|||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`html { background-image: url("/|")`, {
|
||||
await assertCompletions(`html { background-image: url("/|")`, {
|
||||
items: [
|
||||
{ label: 'pathCompletionFixtures/', resultText: `html { background-image: url("/pathCompletionFixtures/")` }
|
||||
]
|
||||
}, testUri, folders);
|
||||
});
|
||||
|
||||
test('CSS url() Path Completion - Unquoted url', function () {
|
||||
test('CSS url() Path Completion - Unquoted url', async function () {
|
||||
let testUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: URI.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
assertCompletions('html { background-image: url(./|)', {
|
||||
await assertCompletions('html { background-image: url(./|)', {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: 'html { background-image: url(./about.html)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(./a|)', {
|
||||
await assertCompletions('html { background-image: url(./a|)', {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: 'html { background-image: url(./about.html)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(../|src/)', {
|
||||
await assertCompletions('html { background-image: url(../|src/)', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: 'html { background-image: url(../about/)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(../s|rc/)', {
|
||||
await assertCompletions('html { background-image: url(../s|rc/)', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: 'html { background-image: url(../about/)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
});
|
||||
|
||||
test('CSS @import Path completion', function () {
|
||||
test('CSS @import Path completion', async function () {
|
||||
let testUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: URI.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
assertCompletions(`@import './|'`, {
|
||||
await assertCompletions(`@import './|'`, {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: `@import './about.html'` },
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions(`@import '../|'`, {
|
||||
await assertCompletions(`@import '../|'`, {
|
||||
items: [
|
||||
{ label: 'about/', resultText: `@import '../about/'` },
|
||||
{ label: 'scss/', resultText: `@import '../scss/'` },
|
||||
|
@ -170,14 +170,14 @@ suite('Completions', () => {
|
|||
/**
|
||||
* For SCSS, `@import 'foo';` can be used for importing partial file `_foo.scss`
|
||||
*/
|
||||
test('SCSS @import Path completion', function () {
|
||||
test('SCSS @import Path completion', async function () {
|
||||
let testCSSUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: URI.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
/**
|
||||
* We are in a CSS file, so no special treatment for SCSS partial files
|
||||
*/
|
||||
assertCompletions(`@import '../scss/|'`, {
|
||||
await assertCompletions(`@import '../scss/|'`, {
|
||||
items: [
|
||||
{ label: 'main.scss', resultText: `@import '../scss/main.scss'` },
|
||||
{ label: '_foo.scss', resultText: `@import '../scss/_foo.scss'` }
|
||||
|
@ -185,18 +185,18 @@ suite('Completions', () => {
|
|||
}, testCSSUri, folders);
|
||||
|
||||
let testSCSSUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/scss/main.scss')).toString();
|
||||
assertCompletions(`@import './|'`, {
|
||||
await assertCompletions(`@import './|'`, {
|
||||
items: [
|
||||
{ label: '_foo.scss', resultText: `@import './foo'` }
|
||||
]
|
||||
}, testSCSSUri, folders, 'scss');
|
||||
});
|
||||
|
||||
test('Completion should ignore files/folders starting with dot', function () {
|
||||
test('Completion should ignore files/folders starting with dot', async function () {
|
||||
let testUri = URI.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: URI.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
assertCompletions('html { background-image: url("../|")', {
|
||||
await assertCompletions('html { background-image: url("../|")', {
|
||||
count: 4
|
||||
}, testUri, folders);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { TextDocument, DocumentLink } from 'vscode-languageserver-types';
|
|||
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
|
||||
import { getCSSLanguageService } from 'vscode-css-languageservice';
|
||||
import { getDocumentContext } from '../utils/documentContext';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
|
||||
export interface ItemDescription {
|
||||
offset: number;
|
||||
|
@ -18,7 +19,7 @@ export interface ItemDescription {
|
|||
}
|
||||
|
||||
suite('Links', () => {
|
||||
const cssLanguageService = getCSSLanguageService();
|
||||
const cssLanguageService = getCSSLanguageService({ fileSystemProvider: getNodeFSRequestService() });
|
||||
|
||||
let assertLink = function (links: DocumentLink[], expected: ItemDescription, document: TextDocument) {
|
||||
let matches = links.filter(link => {
|
||||
|
@ -31,7 +32,7 @@ suite('Links', () => {
|
|||
assert.equal(match.target, expected.target);
|
||||
};
|
||||
|
||||
function assertLinks(value: string, expected: ItemDescription[], testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): void {
|
||||
async function assertLinks(value: string, expected: ItemDescription[], testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): Promise<void> {
|
||||
const offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
|
@ -44,7 +45,7 @@ suite('Links', () => {
|
|||
const context = getDocumentContext(testUri, workspaceFolders);
|
||||
|
||||
const stylesheet = cssLanguageService.parseStylesheet(document);
|
||||
let links = cssLanguageService.findDocumentLinks(document, stylesheet, context)!;
|
||||
let links = await cssLanguageService.findDocumentLinks2(document, stylesheet, context)!;
|
||||
|
||||
assert.equal(links.length, expected.length);
|
||||
|
||||
|
@ -57,32 +58,32 @@ suite('Links', () => {
|
|||
return URI.file(resolve(__dirname, '../../test/linksTestFixtures', path)).toString();
|
||||
}
|
||||
|
||||
test('url links', function () {
|
||||
test('url links', async function () {
|
||||
|
||||
let testUri = getTestResource('about.css');
|
||||
let folders = [{ name: 'x', uri: getTestResource('') }];
|
||||
|
||||
assertLinks('html { background-image: url("hello.html|")',
|
||||
await assertLinks('html { background-image: url("hello.html|")',
|
||||
[{ offset: 29, value: '"hello.html"', target: getTestResource('hello.html') }], testUri, folders
|
||||
);
|
||||
});
|
||||
|
||||
test('node module resolving', function () {
|
||||
test('node module resolving', async function () {
|
||||
|
||||
let testUri = getTestResource('about.css');
|
||||
let folders = [{ name: 'x', uri: getTestResource('') }];
|
||||
|
||||
assertLinks('html { background-image: url("~foo/hello.html|")',
|
||||
await assertLinks('html { background-image: url("~foo/hello.html|")',
|
||||
[{ offset: 29, value: '"~foo/hello.html"', target: getTestResource('node_modules/foo/hello.html') }], testUri, folders
|
||||
);
|
||||
});
|
||||
|
||||
test('node module subfolder resolving', function () {
|
||||
test('node module subfolder resolving', async function () {
|
||||
|
||||
let testUri = getTestResource('subdir/about.css');
|
||||
let folders = [{ name: 'x', uri: getTestResource('') }];
|
||||
|
||||
assertLinks('html { background-image: url("~foo/hello.html|")',
|
||||
await assertLinks('html { background-image: url("~foo/hello.html|")',
|
||||
[{ offset: 29, value: '"~foo/hello.html"', target: getTestResource('node_modules/foo/hello.html') }], testUri, folders
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import { joinPath, normalizePath, resolvePath } from '../requests';
|
||||
|
||||
|
||||
suite('requests', () => {
|
||||
test('join', async function () {
|
||||
assert.equal(joinPath('foo://a/foo/bar', 'x'), 'foo://a/foo/bar/x');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', 'x'), 'foo://a/foo/bar/x');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', '/x'), 'foo://a/foo/bar/x');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', 'x/'), 'foo://a/foo/bar/x/');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', 'x', 'y'), 'foo://a/foo/bar/x/y');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', 'x/', '/y'), 'foo://a/foo/bar/x/y');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', '.', '/y'), 'foo://a/foo/bar/y');
|
||||
assert.equal(joinPath('foo://a/foo/bar/', 'x/y/z', '..'), 'foo://a/foo/bar/x/y');
|
||||
});
|
||||
|
||||
test('resolve', async function () {
|
||||
assert.equal(resolvePath('foo://a/foo/bar', 'x'), 'foo://a/foo/bar/x');
|
||||
assert.equal(resolvePath('foo://a/foo/bar/', 'x'), 'foo://a/foo/bar/x');
|
||||
assert.equal(resolvePath('foo://a/foo/bar/', '/x'), 'foo://a/x');
|
||||
assert.equal(resolvePath('foo://a/foo/bar/', 'x/'), 'foo://a/foo/bar/x/');
|
||||
});
|
||||
|
||||
test('normalize', async function () {
|
||||
function assertNormalize(path: string, expected: string) {
|
||||
assert.equal(normalizePath(path.split('/')), expected, path);
|
||||
}
|
||||
assertNormalize('a', 'a');
|
||||
assertNormalize('/a', '/a');
|
||||
assertNormalize('a/', 'a/');
|
||||
assertNormalize('a/b', 'a/b');
|
||||
assertNormalize('/a/foo/bar/x', '/a/foo/bar/x');
|
||||
assertNormalize('/a/foo/bar//x', '/a/foo/bar/x');
|
||||
assertNormalize('/a/foo/bar///x', '/a/foo/bar/x');
|
||||
assertNormalize('/a/foo/bar/x/', '/a/foo/bar/x/');
|
||||
assertNormalize('a/foo/bar/x/', 'a/foo/bar/x/');
|
||||
assertNormalize('a/foo/bar/x//', 'a/foo/bar/x/');
|
||||
assertNormalize('//a/foo/bar/x//', '/a/foo/bar/x/');
|
||||
assertNormalize('a/.', 'a');
|
||||
assertNormalize('a/./b', 'a/b');
|
||||
assertNormalize('a/././b', 'a/b');
|
||||
assertNormalize('a/n/../b', 'a/b');
|
||||
assertNormalize('a/n/../', 'a/');
|
||||
assertNormalize('a/n/../', 'a/');
|
||||
assertNormalize('/a/n/../..', '/');
|
||||
assertNormalize('..', '');
|
||||
assertNormalize('/..', '/');
|
||||
});
|
||||
});
|
|
@ -5,31 +5,8 @@
|
|||
|
||||
import { DocumentContext } from 'vscode-css-languageservice';
|
||||
import { endsWith, startsWith } from '../utils/strings';
|
||||
import * as url from 'url';
|
||||
import { WorkspaceFolder } from 'vscode-languageserver';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { join, dirname } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
function getModuleNameFromPath(path: string) {
|
||||
// If a scoped module (starts with @) then get up until second instance of '/', otherwise get until first isntance of '/'
|
||||
if (path[0] === '@') {
|
||||
return path.substring(0, path.indexOf('/', path.indexOf('/') + 1));
|
||||
}
|
||||
return path.substring(0, path.indexOf('/'));
|
||||
}
|
||||
|
||||
function resolvePathToModule(_moduleName: string, _relativeToFolder: string, _rootFolder: string | undefined): string | undefined {
|
||||
// resolve the module relative to the document. We can't use `require` here as the code is webpacked.
|
||||
|
||||
const packPath = join(_relativeToFolder, 'node_modules', _moduleName, 'package.json');
|
||||
if (existsSync(packPath)) {
|
||||
return URI.file(packPath).toString();
|
||||
} else if (_rootFolder && _relativeToFolder.startsWith(_rootFolder) && (_relativeToFolder.length !== _rootFolder.length)) {
|
||||
return resolvePathToModule(_moduleName, dirname(_relativeToFolder), _rootFolder);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
import { resolvePath } from '../requests';
|
||||
|
||||
export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext {
|
||||
function getRootFolder(): string | undefined {
|
||||
|
@ -46,38 +23,15 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp
|
|||
}
|
||||
|
||||
return {
|
||||
resolveReference: (ref, base = documentUri) => {
|
||||
resolveReference: (ref: string, base = documentUri) => {
|
||||
if (ref[0] === '/') { // resolve absolute path against the current workspace folder
|
||||
if (startsWith(base, 'file://')) {
|
||||
let folderUri = getRootFolder();
|
||||
if (folderUri) {
|
||||
return folderUri + ref.substr(1);
|
||||
}
|
||||
let folderUri = getRootFolder();
|
||||
if (folderUri) {
|
||||
return folderUri + ref.substr(1);
|
||||
}
|
||||
}
|
||||
// Following [css-loader](https://github.com/webpack-contrib/css-loader#url)
|
||||
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#imports)
|
||||
// convention, if an import path starts with ~ then use node module resolution
|
||||
// *unless* it starts with "~/" as this refers to the user's home directory.
|
||||
if (ref[0] === '~' && ref[1] !== '/') {
|
||||
ref = ref.substring(1);
|
||||
if (startsWith(base, 'file://')) {
|
||||
const moduleName = getModuleNameFromPath(ref);
|
||||
const rootFolderUri = getRootFolder();
|
||||
let rootFolder;
|
||||
if (rootFolderUri) {
|
||||
rootFolder = URI.parse(rootFolderUri).fsPath;
|
||||
}
|
||||
const documentFolder = dirname(URI.parse(base).fsPath);
|
||||
const modulePath = resolvePathToModule(moduleName, documentFolder, rootFolder);
|
||||
if (modulePath) {
|
||||
const pathWithinModule = ref.substring(moduleName.length + 1);
|
||||
return url.resolve(modulePath, pathWithinModule);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return url.resolve(base, ref);
|
||||
base = base.substr(0, base.lastIndexOf('/') + 1);
|
||||
return resolvePath(base, ref);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -38,30 +38,6 @@ export function runSafeAsync<T>(func: () => Thenable<T>, errorVal: T, errorMessa
|
|||
});
|
||||
}
|
||||
|
||||
export function runSafe<T, E>(func: () => T, errorVal: T, errorMessage: string, token: CancellationToken): Thenable<T | ResponseError<E>> {
|
||||
return new Promise<T | ResponseError<E>>((resolve) => {
|
||||
setImmediate(() => {
|
||||
if (token.isCancellationRequested) {
|
||||
resolve(cancelValue());
|
||||
} else {
|
||||
try {
|
||||
let result = func();
|
||||
if (token.isCancellationRequested) {
|
||||
resolve(cancelValue());
|
||||
return;
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(formatError(errorMessage, e));
|
||||
resolve(errorVal);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cancelValue<E>() {
|
||||
return new ResponseError<E>(ErrorCodes.RequestCancelled, 'Request cancelled');
|
||||
}
|
||||
|
|
|
@ -701,55 +701,55 @@ to-regex-range@^5.0.1:
|
|||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
vscode-css-languageservice@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.2.tgz#533bfeb79b38e8add07230dc67001cceb80253e8"
|
||||
integrity sha512-clIjSS940NPBvtfubZokKT/YDNfE5ST9VDwsuwdCbQSkJAVZPAbmIgfmgrz/f/o8PawYQU/ooUBEuRIvIYq3ag==
|
||||
vscode-css-languageservice@4.3.0-next.2:
|
||||
version "4.3.0-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0-next.2.tgz#a7a1289d8d68ddcdee55d4f18b12a455acaf5962"
|
||||
integrity sha512-4h/s/N7wt6If/5EUNMtfAbwWwImH6EvveqZMf9SmQdMMMqekZkRLA68E98hGzuzI13rHEiLckwlAC+RNLq6FXg==
|
||||
dependencies:
|
||||
vscode-languageserver-textdocument "^1.0.1"
|
||||
vscode-languageserver-types "^3.15.1"
|
||||
vscode-languageserver-types "3.16.0-next.2"
|
||||
vscode-nls "^4.1.2"
|
||||
vscode-uri "^2.1.1"
|
||||
vscode-uri "^2.1.2"
|
||||
|
||||
vscode-jsonrpc@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794"
|
||||
integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==
|
||||
vscode-jsonrpc@6.0.0-next.2:
|
||||
version "6.0.0-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.2.tgz#3d73f86d812304cb91b9fb1efee40ec60b09ed7f"
|
||||
integrity sha512-dKQXRYNUY6BHALQJBJlyZyv9oWlYpbJ2vVoQNNVNPLAYQ3hzNp4zy+iSo7zGx1BPXByArJQDWTKLQh8dz3dnNw==
|
||||
|
||||
vscode-languageserver-protocol@^3.15.3:
|
||||
version "3.15.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb"
|
||||
integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==
|
||||
vscode-languageserver-protocol@3.16.0-next.4:
|
||||
version "3.16.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.4.tgz#8f8b1b831d4dfd9b26aa1ba3d2a32c427a91c99f"
|
||||
integrity sha512-6GmPUp2MhJy2H1CTWp2B40Pa9BeC9glrXWmQWVG6A/0V9UbcAjVC9m56znm2GL32iyLDIprTBe8gBvvvcjbpaQ==
|
||||
dependencies:
|
||||
vscode-jsonrpc "^5.0.1"
|
||||
vscode-languageserver-types "3.15.1"
|
||||
vscode-jsonrpc "6.0.0-next.2"
|
||||
vscode-languageserver-types "3.16.0-next.2"
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f"
|
||||
integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==
|
||||
|
||||
vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1:
|
||||
version "3.15.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
|
||||
integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
|
||||
vscode-languageserver-types@3.16.0-next.2:
|
||||
version "3.16.0-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083"
|
||||
integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==
|
||||
|
||||
vscode-languageserver@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762"
|
||||
integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==
|
||||
vscode-languageserver@7.0.0-next.3:
|
||||
version "7.0.0-next.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0-next.3.tgz#3833bd09259a4a085baeba90783f1e4d06d81095"
|
||||
integrity sha512-qSt8eb546iFuoFIN+9MPl4Avru6Iz2/JP0UmS/3djf40ICa31Np/yJ7anX2j0Az5rCzb0fak8oeKwDioGeVOYg==
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "^3.15.3"
|
||||
vscode-languageserver-protocol "3.16.0-next.4"
|
||||
|
||||
vscode-nls@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
|
||||
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
|
||||
|
||||
vscode-uri@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90"
|
||||
integrity sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==
|
||||
vscode-uri@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c"
|
||||
integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==
|
||||
|
||||
which-module@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
|
|
@ -635,31 +635,31 @@ to-regex-range@^5.0.1:
|
|||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
vscode-jsonrpc@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794"
|
||||
integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==
|
||||
vscode-jsonrpc@6.0.0-next.2:
|
||||
version "6.0.0-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.2.tgz#3d73f86d812304cb91b9fb1efee40ec60b09ed7f"
|
||||
integrity sha512-dKQXRYNUY6BHALQJBJlyZyv9oWlYpbJ2vVoQNNVNPLAYQ3hzNp4zy+iSo7zGx1BPXByArJQDWTKLQh8dz3dnNw==
|
||||
|
||||
vscode-languageclient@^6.1.3:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a"
|
||||
integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==
|
||||
vscode-languageclient@7.0.0-next.5:
|
||||
version "7.0.0-next.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0-next.5.tgz#7ae84c598dff360bd2bc64322b74e10e5d0b9cd6"
|
||||
integrity sha512-ec+fJg+JiNBIdbeKbzssSuORUaVdtLValtiYdNEUCUjpYE+Y6xXPtXwiZOlS/0OB9pC/RLCMxsj16UwWncQhYQ==
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
vscode-languageserver-protocol "^3.15.3"
|
||||
vscode-languageserver-protocol "3.16.0-next.4"
|
||||
|
||||
vscode-languageserver-protocol@^3.15.3:
|
||||
version "3.15.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb"
|
||||
integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==
|
||||
vscode-languageserver-protocol@3.16.0-next.4:
|
||||
version "3.16.0-next.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.4.tgz#8f8b1b831d4dfd9b26aa1ba3d2a32c427a91c99f"
|
||||
integrity sha512-6GmPUp2MhJy2H1CTWp2B40Pa9BeC9glrXWmQWVG6A/0V9UbcAjVC9m56znm2GL32iyLDIprTBe8gBvvvcjbpaQ==
|
||||
dependencies:
|
||||
vscode-jsonrpc "^5.0.1"
|
||||
vscode-languageserver-types "3.15.1"
|
||||
vscode-jsonrpc "6.0.0-next.2"
|
||||
vscode-languageserver-types "3.16.0-next.2"
|
||||
|
||||
vscode-languageserver-types@3.15.1:
|
||||
version "3.15.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
|
||||
integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
|
||||
vscode-languageserver-types@3.16.0-next.2:
|
||||
version "3.16.0-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083"
|
||||
integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==
|
||||
|
||||
vscode-nls@^4.1.2:
|
||||
version "4.1.2"
|
||||
|
|
|
@ -148,6 +148,13 @@ export class Model implements IRemoteSourceProviderRegistry {
|
|||
}
|
||||
|
||||
private onPossibleGitRepositoryChange(uri: Uri): void {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const autoRepositoryDetection = config.get<boolean | 'subFolders' | 'openEditors'>('autoRepositoryDetection');
|
||||
|
||||
if (autoRepositoryDetection === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\.git.*$/, ''));
|
||||
}
|
||||
|
||||
|
|
|
@ -21,18 +21,6 @@ interface SessionData {
|
|||
accessToken: string;
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
interface OldSessionData {
|
||||
id: string;
|
||||
accountName: string;
|
||||
scopes: string[];
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
function isOldSessionData(x: any): x is OldSessionData {
|
||||
return !!x.accountName;
|
||||
}
|
||||
|
||||
export class GitHubAuthenticationProvider {
|
||||
private _sessions: vscode.AuthenticationSession2[] = [];
|
||||
private _githubServer = new GitHubServer();
|
||||
|
@ -96,9 +84,9 @@ export class GitHubAuthenticationProvider {
|
|||
const storedSessions = await keychain.getToken();
|
||||
if (storedSessions) {
|
||||
try {
|
||||
const sessionData: (SessionData | OldSessionData)[] = JSON.parse(storedSessions);
|
||||
const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise<vscode.AuthenticationSession2> => {
|
||||
const needsUserInfo = isOldSessionData(session) || !session.account;
|
||||
const sessionData: SessionData[] = JSON.parse(storedSessions);
|
||||
const sessionPromises = sessionData.map(async (session: SessionData): Promise<vscode.AuthenticationSession2> => {
|
||||
const needsUserInfo = !session.account;
|
||||
let userInfo: { id: string, accountName: string };
|
||||
if (needsUserInfo) {
|
||||
userInfo = await this._githubServer.getUserInfo(session.accessToken);
|
||||
|
@ -107,12 +95,8 @@ export class GitHubAuthenticationProvider {
|
|||
return {
|
||||
id: session.id,
|
||||
account: {
|
||||
displayName: isOldSessionData(session)
|
||||
? session.accountName
|
||||
: session.account?.displayName ?? userInfo!.accountName,
|
||||
id: isOldSessionData(session)
|
||||
? userInfo!.id
|
||||
: session.account?.id ?? userInfo!.id
|
||||
displayName: session.account?.displayName ?? userInfo!.accountName,
|
||||
id: session.account?.id ?? userInfo!.id
|
||||
},
|
||||
scopes: session.scopes,
|
||||
accessToken: session.accessToken
|
||||
|
|
|
@ -23,6 +23,8 @@ class UriEventHandler extends vscode.EventEmitter<vscode.Uri> implements vscode.
|
|||
|
||||
export const uriHandler = new UriEventHandler;
|
||||
|
||||
const onDidManuallyProvideToken = new vscode.EventEmitter<string>();
|
||||
|
||||
const exchangeCodeForToken: (state: string) => PromiseAdapter<vscode.Uri, string> =
|
||||
(state) => async (uri, resolve, reject) => {
|
||||
Logger.info('Exchanging code for token...');
|
||||
|
@ -80,11 +82,14 @@ export class GitHubServer {
|
|||
|
||||
const state = uuid();
|
||||
const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`));
|
||||
const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code`);
|
||||
const uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code&authServer=https://github.com`);
|
||||
|
||||
vscode.env.openExternal(uri);
|
||||
|
||||
return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state)).finally(() => {
|
||||
return Promise.race([
|
||||
promiseFromEvent(uriHandler.event, exchangeCodeForToken(state)),
|
||||
promiseFromEvent<string, string>(onDidManuallyProvideToken.event)
|
||||
]).finally(() => {
|
||||
this.updateStatusBarItem(false);
|
||||
});
|
||||
}
|
||||
|
@ -111,8 +116,9 @@ export class GitHubServer {
|
|||
if (!uri.scheme || uri.scheme === 'file') { throw new Error; }
|
||||
uriHandler.handleUri(uri);
|
||||
} catch (e) {
|
||||
Logger.error(e);
|
||||
vscode.window.showErrorMessage(localize('unexpectedInput', "The input did not matched the expected format"));
|
||||
// If it doesn't look like a URI, treat it as a token.
|
||||
Logger.info('Treating input as token');
|
||||
onDidManuallyProvideToken.fire(uriOrToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
|
|||
}
|
||||
}
|
||||
|
||||
const scopes = ['repo'];
|
||||
const scopes = ['repo', 'workflow'];
|
||||
|
||||
export async function getSession(): Promise<AuthenticationSession> {
|
||||
const authenticationSessions = await authentication.getSessions('github', scopes);
|
||||
|
|
|
@ -23,7 +23,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
|
||||
const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry);
|
||||
|
||||
context.subscriptions.push(vscode.window.registerCustomEditorProvider2(PreviewManager.viewType, previewManager, {
|
||||
context.subscriptions.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, {
|
||||
supportsMultipleEditorsPerDocument: true,
|
||||
}));
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/7cf9aa7bb76c55428063383610edc0a631230d58",
|
||||
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/a7e4475626a505472c76d18e0a1b3cfcf46f9cf9",
|
||||
"name": "Markdown",
|
||||
"scopeName": "text.html.markdown",
|
||||
"patterns": [
|
||||
|
@ -1715,6 +1715,72 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"fenced_code_block_erlang": {
|
||||
"begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(erlang)((\\s+|:|\\{)[^`~]*)?$)",
|
||||
"name": "markup.fenced_code.block.markdown",
|
||||
"end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
|
||||
"beginCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
},
|
||||
"4": {
|
||||
"name": "fenced_code.block.language.markdown"
|
||||
},
|
||||
"5": {
|
||||
"name": "fenced_code.block.language.attributes.markdown"
|
||||
}
|
||||
},
|
||||
"endCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "(^|\\G)(\\s*)(.*)",
|
||||
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
|
||||
"contentName": "meta.embedded.block.erlang",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.erlang"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"fenced_code_block_elixir": {
|
||||
"begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(elixir)((\\s+|:|\\{)[^`~]*)?$)",
|
||||
"name": "markup.fenced_code.block.markdown",
|
||||
"end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
|
||||
"beginCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
},
|
||||
"4": {
|
||||
"name": "fenced_code.block.language.markdown"
|
||||
},
|
||||
"5": {
|
||||
"name": "fenced_code.block.language.attributes.markdown"
|
||||
}
|
||||
},
|
||||
"endCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "(^|\\G)(\\s*)(.*)",
|
||||
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
|
||||
"contentName": "meta.embedded.block.elixir",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.elixir"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"fenced_code_block": {
|
||||
"patterns": [
|
||||
{
|
||||
|
@ -1867,6 +1933,12 @@
|
|||
{
|
||||
"include": "#fenced_code_block_log"
|
||||
},
|
||||
{
|
||||
"include": "#fenced_code_block_erlang"
|
||||
},
|
||||
{
|
||||
"include": "#fenced_code_block_elixir"
|
||||
},
|
||||
{
|
||||
"include": "#fenced_code_block_unknown"
|
||||
}
|
||||
|
|
|
@ -82,9 +82,6 @@ export class AzureActiveDirectoryService {
|
|||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
// TODO remove, temporary migration
|
||||
await keychain.migrateToken();
|
||||
|
||||
const storedData = await keychain.getToken();
|
||||
if (storedData) {
|
||||
try {
|
||||
|
|
|
@ -43,22 +43,6 @@ export class Keychain {
|
|||
this.keytar = keytar;
|
||||
}
|
||||
|
||||
// TODO remove, temporary migration
|
||||
async migrateToken(): Promise<void> {
|
||||
const oldServiceId = `${vscode.env.uriScheme}-vscode.login`;
|
||||
try {
|
||||
const data = await this.keytar.getPassword(oldServiceId, ACCOUNT_ID);
|
||||
if (data) {
|
||||
Logger.info('Migrating token...');
|
||||
this.setToken(data);
|
||||
await this.keytar.deletePassword(oldServiceId, ACCOUNT_ID);
|
||||
Logger.info('Migration successful');
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(`Migrating token failed: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async setToken(token: string): Promise<void> {
|
||||
try {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.0.1",
|
||||
"description": "Dependencies shared by all extensions",
|
||||
"dependencies": {
|
||||
"typescript": "3.9.3"
|
||||
"typescript": "3.9.4"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node ./postinstall"
|
||||
|
|
|
@ -432,8 +432,8 @@
|
|||
"c": "Models",
|
||||
"t": "source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
@ -476,8 +476,8 @@
|
|||
"c": "MsRestAzure",
|
||||
"t": "source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
@ -1191,8 +1191,8 @@
|
|||
"c": "ArgumentError",
|
||||
"t": "source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
@ -1345,8 +1345,8 @@
|
|||
"c": "ArgumentError",
|
||||
"t": "source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
@ -1499,8 +1499,8 @@
|
|||
"c": "ServiceClientCredentials",
|
||||
"t": "source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
@ -2269,8 +2269,8 @@
|
|||
"c": "MAVERICKS_PKG_PATH",
|
||||
"t": "source.ruby string.interpolated.ruby meta.embedded.line.ruby source.ruby variable.other.constant.ruby",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "meta.embedded: #D4D4D4",
|
||||
"light_vs": "meta.embedded: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
"variable.other.enummember"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#51B6C4",
|
||||
"foreground": "#4FC1FF",
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
"variable.other.enummember"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#328267",
|
||||
"foreground": "#0070C1",
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -2962,8 +2962,8 @@
|
|||
"c": "t",
|
||||
"t": "source.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
|
|
@ -190,8 +190,8 @@
|
|||
"c": "timeRange",
|
||||
"t": "source.ts meta.function.ts meta.block.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts",
|
||||
"r": {
|
||||
"dark_plus": "variable.other.constant: #51B6C4",
|
||||
"light_plus": "variable.other.constant: #328267",
|
||||
"dark_plus": "variable.other.constant: #4FC1FF",
|
||||
"light_plus": "variable.other.constant: #0070C1",
|
||||
"dark_vs": "default: #D4D4D4",
|
||||
"light_vs": "default: #000000",
|
||||
"hc_black": "variable: #9CDCFE"
|
||||
|
|
|
@ -18,7 +18,7 @@ import FileConfigurationManager from './fileConfigurationManager';
|
|||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface AutoFix {
|
||||
readonly code: number;
|
||||
readonly codes: Set<number>;
|
||||
readonly fixName: string;
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,12 @@ async function buildIndividualFixes(
|
|||
token: vscode.CancellationToken,
|
||||
): Promise<void> {
|
||||
for (const diagnostic of diagnostics) {
|
||||
for (const { code, fixName } of fixes) {
|
||||
for (const { codes, fixName } of fixes) {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnostic.code !== code) {
|
||||
if (!codes.has(diagnostic.code as number)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -68,12 +68,12 @@ async function buildCombinedFix(
|
|||
token: vscode.CancellationToken,
|
||||
): Promise<void> {
|
||||
for (const diagnostic of diagnostics) {
|
||||
for (const { code, fixName } of fixes) {
|
||||
for (const { codes, fixName } of fixes) {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnostic.code !== code) {
|
||||
if (!codes.has(diagnostic.code as number)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -139,12 +139,12 @@ class SourceFixAll extends SourceAction {
|
|||
this.edit = new vscode.WorkspaceEdit();
|
||||
|
||||
await buildIndividualFixes([
|
||||
{ code: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface },
|
||||
{ code: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction },
|
||||
{ codes: errorCodes.incorrectlyImplementsInterface, fixName: fixNames.classIncorrectlyImplementsInterface },
|
||||
{ codes: errorCodes.asyncOnlyAllowedInAsyncFunctions, fixName: fixNames.awaitInSyncFunction },
|
||||
], this.edit, client, file, diagnostics, token);
|
||||
|
||||
await buildCombinedFix([
|
||||
{ code: errorCodes.unreachableCode, fixName: fixNames.unreachableCode }
|
||||
{ codes: errorCodes.unreachableCode, fixName: fixNames.unreachableCode }
|
||||
], this.edit, client, file, diagnostics, token);
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class SourceRemoveUnused extends SourceAction {
|
|||
async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise<void> {
|
||||
this.edit = new vscode.WorkspaceEdit();
|
||||
await buildCombinedFix([
|
||||
{ code: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier },
|
||||
{ codes: errorCodes.variableDeclaredButNeverUsed, fixName: fixNames.unusedIdentifier },
|
||||
], this.edit, client, file, diagnostics, token);
|
||||
}
|
||||
}
|
||||
|
@ -175,8 +175,9 @@ class SourceAddMissingImports extends SourceAction {
|
|||
|
||||
async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise<void> {
|
||||
this.edit = new vscode.WorkspaceEdit();
|
||||
await buildCombinedFix(
|
||||
errorCodes.cannotFindName.map(code => ({ code, fixName: fixNames.fixImport })),
|
||||
await buildCombinedFix([
|
||||
{ codes: errorCodes.cannotFindName, fixName: fixNames.fixImport }
|
||||
],
|
||||
this.edit, client, file, diagnostics, token);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,17 @@ class CodeActionSet {
|
|||
this._actions.delete(existing);
|
||||
}
|
||||
}
|
||||
|
||||
this._actions.add(action);
|
||||
|
||||
if (action.tsAction.fixId) {
|
||||
// If we have an existing fix all action, then make sure it follows this action
|
||||
const existingFixAll = this._fixAllActions.get(action.tsAction.fixId);
|
||||
if (existingFixAll) {
|
||||
this._actions.delete(existingFixAll);
|
||||
this._actions.add(existingFixAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public addFixAllAction(fixId: {}, action: VsCodeCodeAction) {
|
||||
|
|
|
@ -25,11 +25,7 @@ namespace Experimental {
|
|||
readonly error?: string
|
||||
}
|
||||
|
||||
export type RefactorTriggerReason = RefactorInvokedReason;
|
||||
|
||||
export interface RefactorInvokedReason {
|
||||
readonly kind: 'invoked';
|
||||
}
|
||||
export type RefactorTriggerReason = 'implicit' | 'invoked';
|
||||
|
||||
export interface GetApplicableRefactorsRequestArgs extends Proto.FileRangeRequestArgs {
|
||||
readonly triggerReason?: RefactorTriggerReason;
|
||||
|
@ -275,11 +271,11 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider {
|
|||
return this.appendInvalidActions(actions);
|
||||
}
|
||||
|
||||
private toTsTriggerReason(context: vscode.CodeActionContext): Experimental.RefactorInvokedReason | undefined {
|
||||
private toTsTriggerReason(context: vscode.CodeActionContext): Experimental.RefactorTriggerReason | undefined {
|
||||
if (!context.only) {
|
||||
return;
|
||||
}
|
||||
return { kind: 'invoked' };
|
||||
return 'invoked';
|
||||
}
|
||||
|
||||
private convertApplicableRefactors(
|
||||
|
|
|
@ -115,4 +115,25 @@ suite('TypeScript Fix All', () => {
|
|||
`used();`
|
||||
));
|
||||
});
|
||||
|
||||
test('Remove unused should remove unused interfaces', async () => {
|
||||
const editor = await createTestEditor(testDocumentUri,
|
||||
`export const _ = 1;`,
|
||||
`interface Foo {}`
|
||||
);
|
||||
|
||||
await wait(2000);
|
||||
|
||||
const fixes = await vscode.commands.executeCommand<vscode.CodeAction[]>('vscode.executeCodeActionProvider',
|
||||
testDocumentUri,
|
||||
emptyRange,
|
||||
vscode.CodeActionKind.Source.append('removeUnused')
|
||||
);
|
||||
|
||||
await vscode.workspace.applyEdit(fixes![0].edit!);
|
||||
assert.strictEqual(editor.document.getText(), joinLines(
|
||||
`export const _ = 1;`,
|
||||
``
|
||||
));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -122,6 +122,32 @@ suite('TypeScript Quick Fix', () => {
|
|||
assert.strictEqual(fixes![0].title, `Implement interface 'IFoo'`);
|
||||
assert.strictEqual(fixes![1].title, `Remove unused declaration for: 'Foo'`);
|
||||
});
|
||||
|
||||
test('Add all missing imports should come after other add import fixes #98613', async () => {
|
||||
await createTestEditor(workspaceFile('foo.ts'),
|
||||
`export const foo = 1;`);
|
||||
|
||||
await createTestEditor(workspaceFile('bar.ts'),
|
||||
`export const foo = 1;`);
|
||||
|
||||
const editor = await createTestEditor(workspaceFile('index.ts'),
|
||||
`export const _ = 1;`,
|
||||
`foo$0;`,
|
||||
`foo$0;`
|
||||
);
|
||||
|
||||
await wait(3000);
|
||||
|
||||
const fixes = await vscode.commands.executeCommand<vscode.CodeAction[]>('vscode.executeCodeActionProvider',
|
||||
workspaceFile('index.ts'),
|
||||
editor.document.lineAt(1).range
|
||||
);
|
||||
|
||||
assert.strictEqual(fixes?.length, 3);
|
||||
assert.strictEqual(fixes![0].title, `Import 'foo' from module "./bar"`);
|
||||
assert.strictEqual(fixes![1].title, `Import 'foo' from module "./foo"`);
|
||||
assert.strictEqual(fixes![2].title, `Add all missing imports`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -27,15 +27,15 @@ import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
|
|||
import VersionStatus from './utils/versionStatus';
|
||||
|
||||
// Style check diagnostics that can be reported as warnings
|
||||
const styleCheckDiagnostics = [
|
||||
errorCodes.variableDeclaredButNeverUsed,
|
||||
errorCodes.propertyDeclaretedButNeverUsed,
|
||||
errorCodes.allImportsAreUnused,
|
||||
errorCodes.unreachableCode,
|
||||
errorCodes.unusedLabel,
|
||||
errorCodes.fallThroughCaseInSwitch,
|
||||
errorCodes.notAllCodePathsReturnAValue,
|
||||
];
|
||||
const styleCheckDiagnostics = new Set([
|
||||
...errorCodes.variableDeclaredButNeverUsed,
|
||||
...errorCodes.propertyDeclaretedButNeverUsed,
|
||||
...errorCodes.allImportsAreUnused,
|
||||
...errorCodes.unreachableCode,
|
||||
...errorCodes.unusedLabel,
|
||||
...errorCodes.fallThroughCaseInSwitch,
|
||||
...errorCodes.notAllCodePathsReturnAValue,
|
||||
]);
|
||||
|
||||
export default class TypeScriptServiceClientHost extends Disposable {
|
||||
|
||||
|
@ -286,6 +286,6 @@ export default class TypeScriptServiceClientHost extends Disposable {
|
|||
}
|
||||
|
||||
private isStyleCheckDiagnostic(code: number | undefined): boolean {
|
||||
return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false;
|
||||
return typeof code === 'number' && styleCheckDiagnostics.has(code);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -630,7 +630,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
}
|
||||
|
||||
public toResource(filepath: string): vscode.Uri {
|
||||
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
|
||||
if (filepath.match(/^[a-z]{2,}:/) || filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
|
||||
) {
|
||||
let resource = vscode.Uri.parse(filepath);
|
||||
const dirName = path.dirname(resource.path);
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const variableDeclaredButNeverUsed = 6133;
|
||||
export const propertyDeclaretedButNeverUsed = 6138;
|
||||
export const allImportsAreUnused = 6192;
|
||||
export const unreachableCode = 7027;
|
||||
export const unusedLabel = 7028;
|
||||
export const fallThroughCaseInSwitch = 7029;
|
||||
export const notAllCodePathsReturnAValue = 7030;
|
||||
export const incorrectlyImplementsInterface = 2420;
|
||||
export const cannotFindName = [2552, 2304];
|
||||
export const extendsShouldBeImplements = 2689;
|
||||
export const asyncOnlyAllowedInAsyncFunctions = 1308;
|
||||
export const variableDeclaredButNeverUsed = new Set([6196, 6133]);
|
||||
export const propertyDeclaretedButNeverUsed = new Set([6138]);
|
||||
export const allImportsAreUnused = new Set([6192]);
|
||||
export const unreachableCode = new Set([7027]);
|
||||
export const unusedLabel = new Set([7028]);
|
||||
export const fallThroughCaseInSwitch = new Set([7029]);
|
||||
export const notAllCodePathsReturnAValue = new Set([7030]);
|
||||
export const incorrectlyImplementsInterface = new Set([2420]);
|
||||
export const cannotFindName = new Set([2552, 2304]);
|
||||
export const extendsShouldBeImplements = new Set([2689]);
|
||||
export const asyncOnlyAllowedInAsyncFunctions = new Set([1308]);
|
||||
|
|
|
@ -48,9 +48,12 @@ class Directory implements vscode.FileStat {
|
|||
|
||||
export type Entry = File | Directory;
|
||||
|
||||
export class MemFS implements vscode.FileSystemProvider {
|
||||
export class TestFS implements vscode.FileSystemProvider {
|
||||
|
||||
readonly scheme = 'fake-fs';
|
||||
constructor(
|
||||
readonly scheme: string,
|
||||
readonly isCaseSensitive: boolean
|
||||
) { }
|
||||
|
||||
readonly root = new Directory('');
|
||||
|
||||
|
@ -161,12 +164,22 @@ export class MemFS implements vscode.FileSystemProvider {
|
|||
let parts = uri.path.split('/');
|
||||
let entry: Entry = this.root;
|
||||
for (const part of parts) {
|
||||
const partLow = part.toLowerCase();
|
||||
if (!part) {
|
||||
continue;
|
||||
}
|
||||
let child: Entry | undefined;
|
||||
if (entry instanceof Directory) {
|
||||
child = entry.entries.get(part);
|
||||
if (this.isCaseSensitive) {
|
||||
child = entry.entries.get(part);
|
||||
} else {
|
||||
for (let [key, value] of entry.entries) {
|
||||
if (key.toLowerCase() === partLow) {
|
||||
child = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!child) {
|
||||
if (!silent) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import * as vscode from 'vscode';
|
|||
import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay, withLogDisabled } from '../utils';
|
||||
import { join, posix, basename } from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { TestFS } from '../memfs';
|
||||
|
||||
suite('vscode API - workspace', () => {
|
||||
|
||||
|
@ -163,6 +164,40 @@ suite('vscode API - workspace', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('openTextDocument, actual casing first', async function () {
|
||||
|
||||
const fs = new TestFS('this-fs', false);
|
||||
const reg = vscode.workspace.registerFileSystemProvider(fs.scheme, fs, { isCaseSensitive: fs.isCaseSensitive });
|
||||
|
||||
let uriOne = vscode.Uri.parse('this-fs:/one');
|
||||
let uriTwo = vscode.Uri.parse('this-fs:/two');
|
||||
let uriONE = vscode.Uri.parse('this-fs:/ONE'); // same resource, different uri
|
||||
let uriTWO = vscode.Uri.parse('this-fs:/TWO');
|
||||
|
||||
fs.writeFile(uriOne, Buffer.from('one'), { create: true, overwrite: true });
|
||||
fs.writeFile(uriTwo, Buffer.from('two'), { create: true, overwrite: true });
|
||||
|
||||
// lower case (actual case) comes first
|
||||
let docOne = await vscode.workspace.openTextDocument(uriOne);
|
||||
assert.equal(docOne.uri.toString(), uriOne.toString());
|
||||
|
||||
let docONE = await vscode.workspace.openTextDocument(uriONE);
|
||||
assert.equal(docONE === docOne, true);
|
||||
assert.equal(docONE.uri.toString(), uriOne.toString());
|
||||
assert.equal(docONE.uri.toString() !== uriONE.toString(), true); // yep
|
||||
|
||||
// upper case (NOT the actual case) comes first
|
||||
let docTWO = await vscode.workspace.openTextDocument(uriTWO);
|
||||
assert.equal(docTWO.uri.toString(), uriTWO.toString());
|
||||
|
||||
let docTwo = await vscode.workspace.openTextDocument(uriTwo);
|
||||
assert.equal(docTWO === docTwo, true);
|
||||
assert.equal(docTwo.uri.toString(), uriTWO.toString());
|
||||
assert.equal(docTwo.uri.toString() !== uriTwo.toString(), true); // yep
|
||||
|
||||
reg.dispose();
|
||||
});
|
||||
|
||||
test('eol, read', () => {
|
||||
const a = createRandomFile('foo\nbar\nbar').then(file => {
|
||||
return vscode.workspace.openTextDocument(file).then(doc => {
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MemFS } from './memfs';
|
||||
import { TestFS } from './memfs';
|
||||
import * as assert from 'assert';
|
||||
|
||||
export function rndName() {
|
||||
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
|
||||
}
|
||||
|
||||
export const testFs = new MemFS();
|
||||
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs);
|
||||
export const testFs = new TestFS('fake-fs', true);
|
||||
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive });
|
||||
|
||||
export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise<vscode.Uri> {
|
||||
let fakeFile: vscode.Uri;
|
||||
|
|
|
@ -8,6 +8,14 @@ import * as assert from 'assert';
|
|||
import * as vscode from 'vscode';
|
||||
import { join } from 'path';
|
||||
|
||||
export function timeoutAsync(n: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, n);
|
||||
});
|
||||
}
|
||||
|
||||
export function once<T>(event: vscode.Event<T>): vscode.Event<T> {
|
||||
return (listener: any, thisArgs = null, disposables?: any) => {
|
||||
// we need this, in case the event fires during the listener call
|
||||
|
@ -39,9 +47,18 @@ async function getEventOncePromise<T>(event: vscode.Event<T>): Promise<T> {
|
|||
});
|
||||
}
|
||||
|
||||
// Since `workbench.action.splitEditor` command does await properly
|
||||
// Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves
|
||||
// The workaround here is waiting for the first visible notebook editor change event.
|
||||
async function splitEditor() {
|
||||
const once = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors);
|
||||
await vscode.commands.executeCommand('workbench.action.splitEditor');
|
||||
await once;
|
||||
}
|
||||
|
||||
suite('API tests', () => {
|
||||
test('document open/close event', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument);
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await firstDocumentOpen;
|
||||
|
@ -52,7 +69,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('shared document in notebook editors', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
let counter = 0;
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => {
|
||||
|
@ -64,7 +81,7 @@ suite('API tests', () => {
|
|||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(counter, 1);
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.splitEditor');
|
||||
await splitEditor();
|
||||
assert.equal(counter, 1);
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
assert.equal(counter, 0);
|
||||
|
@ -73,7 +90,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('editor open/close event', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors);
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await firstEditorOpen;
|
||||
|
@ -84,7 +101,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('editor open/close event', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
let count = 0;
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
disposables.push(vscode.notebook.onDidChangeVisibleNotebookEditors(() => {
|
||||
|
@ -94,15 +111,15 @@ suite('API tests', () => {
|
|||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(count, 1);
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.splitEditor');
|
||||
await splitEditor();
|
||||
assert.equal(count, 2);
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
assert.equal(count, 0);
|
||||
});
|
||||
|
||||
test('editor editing event', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
test('editor editing event 2', async function () {
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
|
||||
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
|
||||
|
@ -169,7 +186,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('editor move cell event', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
|
||||
|
@ -198,13 +215,13 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('notebook editor active/visible', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
const firstEditor = vscode.notebook.activeNotebookEditor;
|
||||
assert.equal(firstEditor?.active, true);
|
||||
assert.equal(firstEditor?.visible, true);
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.splitEditor');
|
||||
await splitEditor();
|
||||
const secondEditor = vscode.notebook.activeNotebookEditor;
|
||||
assert.equal(secondEditor?.active, true);
|
||||
assert.equal(secondEditor?.visible, true);
|
||||
|
@ -229,7 +246,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('notebook active editor change', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor);
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await firstEditorOpen;
|
||||
|
@ -243,7 +260,7 @@ suite('API tests', () => {
|
|||
});
|
||||
|
||||
test('edit API', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
|
||||
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
|
||||
|
@ -261,11 +278,28 @@ suite('API tests', () => {
|
|||
await vscode.commands.executeCommand('workbench.action.files.save');
|
||||
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||
});
|
||||
|
||||
test('initialzation should not emit cell change events.', async function () {
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
|
||||
let count = 0;
|
||||
const disposables: vscode.Disposable[] = [];
|
||||
disposables.push(vscode.notebook.onDidChangeNotebookCells(() => {
|
||||
count++;
|
||||
}));
|
||||
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(count, 0);
|
||||
|
||||
disposables.forEach(d => d.dispose());
|
||||
await vscode.commands.executeCommand('workbench.action.files.save');
|
||||
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||
});
|
||||
});
|
||||
|
||||
suite('notebook workflow', () => {
|
||||
test('notebook open', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test');
|
||||
|
@ -286,7 +320,7 @@ suite('notebook workflow', () => {
|
|||
});
|
||||
|
||||
test('notebook cell actions', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test');
|
||||
|
@ -359,7 +393,7 @@ suite('notebook workflow', () => {
|
|||
});
|
||||
|
||||
test('move cells will not recreate cells in ExtHost', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
|
||||
|
@ -380,7 +414,7 @@ suite('notebook workflow', () => {
|
|||
});
|
||||
|
||||
// test.only('document metadata is respected', async function () {
|
||||
// const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
|
||||
// assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
|
@ -404,7 +438,7 @@ suite('notebook workflow', () => {
|
|||
// });
|
||||
|
||||
test('cell runnable metadata is respected', async () => {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
const editor = vscode.notebook.activeNotebookEditor!;
|
||||
|
@ -425,7 +459,7 @@ suite('notebook workflow', () => {
|
|||
});
|
||||
|
||||
test('document runnable metadata is respected', async () => {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
const editor = vscode.notebook.activeNotebookEditor!;
|
||||
|
@ -447,7 +481,7 @@ suite('notebook workflow', () => {
|
|||
|
||||
suite('notebook dirty state', () => {
|
||||
test('notebook open', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test');
|
||||
|
@ -481,7 +515,7 @@ suite('notebook dirty state', () => {
|
|||
|
||||
suite('notebook undo redo', () => {
|
||||
test('notebook open', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, 'test');
|
||||
|
@ -524,7 +558,7 @@ suite('notebook undo redo', () => {
|
|||
|
||||
suite('notebook working copy', () => {
|
||||
test('notebook revert on close', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -545,7 +579,7 @@ suite('notebook working copy', () => {
|
|||
});
|
||||
|
||||
test('notebook revert', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -564,7 +598,7 @@ suite('notebook working copy', () => {
|
|||
});
|
||||
|
||||
test('multiple tabs: dirty + clean', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -572,7 +606,7 @@ suite('notebook working copy', () => {
|
|||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
|
||||
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
|
||||
|
||||
const secondResource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
|
||||
const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
||||
|
||||
|
@ -588,7 +622,7 @@ suite('notebook working copy', () => {
|
|||
});
|
||||
|
||||
test('multiple tabs: two dirty tabs and switching', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -596,7 +630,7 @@ suite('notebook working copy', () => {
|
|||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
|
||||
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
|
||||
|
||||
const secondResource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
|
||||
const secondResource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './second.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -623,14 +657,14 @@ suite('notebook working copy', () => {
|
|||
|
||||
test('multiple tabs: different editors with same document', async function () {
|
||||
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
const firstNotebookEditor = vscode.notebook.activeNotebookEditor;
|
||||
assert.equal(firstNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(firstNotebookEditor!.selection?.source, 'test');
|
||||
assert.equal(firstNotebookEditor!.selection?.language, 'typescript');
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.splitEditor');
|
||||
await splitEditor();
|
||||
const secondNotebookEditor = vscode.notebook.activeNotebookEditor;
|
||||
assert.equal(secondNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(secondNotebookEditor!.selection?.source, 'test');
|
||||
|
@ -638,7 +672,7 @@ suite('notebook working copy', () => {
|
|||
|
||||
assert.notEqual(firstNotebookEditor, secondNotebookEditor);
|
||||
assert.equal(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document');
|
||||
assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.parse('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.parse('./hello.png')));
|
||||
assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')));
|
||||
|
||||
await vscode.commands.executeCommand('workbench.action.files.saveAll');
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
|
@ -647,7 +681,7 @@ suite('notebook working copy', () => {
|
|||
|
||||
suite('metadata', () => {
|
||||
test('custom metadata should be supported', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
|
||||
|
@ -658,7 +692,7 @@ suite('metadata', () => {
|
|||
// TODO copy cell should not copy metadata
|
||||
|
||||
test('custom metadata should be supported', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
|
||||
|
@ -674,7 +708,7 @@ suite('metadata', () => {
|
|||
|
||||
suite('regression', () => {
|
||||
test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.source, '');
|
||||
|
@ -684,7 +718,7 @@ suite('regression', () => {
|
|||
});
|
||||
|
||||
test('#97830, #97764. Support switch to other editor types', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
|
||||
|
@ -702,7 +736,7 @@ suite('regression', () => {
|
|||
|
||||
// open text editor, pin, and then open a notebook
|
||||
test('#96105 - dirty editors', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'default');
|
||||
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
|
||||
await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' });
|
||||
|
@ -719,31 +753,42 @@ suite('regression', () => {
|
|||
});
|
||||
|
||||
suite('webview', () => {
|
||||
// for web, `asWebUri` gets `https`?
|
||||
test('asWebviewUri', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
if (vscode.env.uiKind === vscode.UIKind.Web) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
|
||||
const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.parse('./hello.png'));
|
||||
assert.equal(uri.scheme, 'vscode-webview-resource');
|
||||
const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png'));
|
||||
assert.equal(uri.scheme, 'vscode-resource');
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
});
|
||||
|
||||
test('custom renderer message', async function () {
|
||||
const resource = vscode.Uri.parse(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb'));
|
||||
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
|
||||
const editor = vscode.notebook.activeNotebookEditor;
|
||||
const promise = new Promise(resolve => {
|
||||
const messageEmitter = editor?.onDidReceiveMessage(e => {
|
||||
if (e.type === 'custom_renderer_initialize') {
|
||||
resolve();
|
||||
messageEmitter?.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
// 404 on web
|
||||
// test('custom renderer message', async function () {
|
||||
// if (vscode.env.uiKind === vscode.UIKind.Web) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
await vscode.commands.executeCommand('notebook.cell.execute');
|
||||
await promise;
|
||||
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
});
|
||||
// const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb'));
|
||||
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
|
||||
|
||||
// const editor = vscode.notebook.activeNotebookEditor;
|
||||
// const promise = new Promise(resolve => {
|
||||
// const messageEmitter = editor?.onDidReceiveMessage(e => {
|
||||
// if (e.type === 'custom_renderer_initialize') {
|
||||
// resolve();
|
||||
// messageEmitter?.dispose();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// await vscode.commands.executeCommand('notebook.cell.execute');
|
||||
// await promise;
|
||||
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
|
||||
// });
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"onFileSystem:memfs",
|
||||
"onDebug"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"browser": "./out/extension",
|
||||
"engines": {
|
||||
"vscode": "^1.25.0"
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
typescript@3.9.3:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
typescript@3.9.4:
|
||||
version "3.9.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.4.tgz#5aa0a54904b51b96dfd67870ce2db70251802f10"
|
||||
integrity sha512-9OL+r0KVHqsYVH7K18IBR9hhC82YwLNlpSZfQDupGcfg8goB9p/s/9Okcy+ztnTeHR2U68xq21/igW9xpoGTgA==
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.46.0",
|
||||
"distro": "d4eced2b3485a4ef446032a8141459a9856c18ad",
|
||||
"distro": "ebf621f403497559e97da316516d55d16e4bf964",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
@ -99,7 +99,7 @@
|
|||
"css-loader": "^3.2.0",
|
||||
"debounce": "^1.0.0",
|
||||
"deemon": "^1.4.0",
|
||||
"electron": "7.3.0",
|
||||
"electron": "7.3.1",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-plugin-jsdoc": "^19.1.0",
|
||||
"event-stream": "3.3.4",
|
||||
|
@ -160,7 +160,7 @@
|
|||
"vinyl": "^2.0.0",
|
||||
"vinyl-fs": "^3.0.0",
|
||||
"vsce": "1.48.0",
|
||||
"vscode-debugprotocol": "^1.40.0",
|
||||
"vscode-debugprotocol": "1.41.0",
|
||||
"vscode-nls-dev": "^3.3.1",
|
||||
"webpack": "^4.16.5",
|
||||
"webpack-cli": "^3.3.8",
|
||||
|
@ -181,4 +181,4 @@
|
|||
"windows-mutex": "0.3.0",
|
||||
"windows-process-tree": "0.2.4"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,13 +23,14 @@
|
|||
"reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new",
|
||||
"urlProtocol": "code-oss",
|
||||
"extensionAllowedProposedApi": [
|
||||
"ms-vscode.vscode-js-profile-flame",
|
||||
"ms-vscode.vscode-js-profile-table",
|
||||
"ms-vscode.references-view"
|
||||
],
|
||||
"builtInExtensions": [
|
||||
{
|
||||
"name": "ms-vscode.node-debug",
|
||||
"version": "1.44.5",
|
||||
"version": "1.44.6",
|
||||
"repo": "https://github.com/Microsoft/vscode-node-debug",
|
||||
"metadata": {
|
||||
"id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6",
|
||||
|
@ -74,7 +75,7 @@
|
|||
},
|
||||
{
|
||||
"name": "ms-vscode.js-debug-companion",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"repo": "https://github.com/microsoft/vscode-js-debug-companion",
|
||||
"metadata": {
|
||||
"id": "99cb0b7f-7354-4278-b8da-6cc79972169d",
|
||||
|
@ -89,7 +90,7 @@
|
|||
},
|
||||
{
|
||||
"name": "ms-vscode.js-debug-nightly",
|
||||
"version": "2020.5.1917",
|
||||
"version": "2020.6.208",
|
||||
"repo": "https://github.com/Microsoft/vscode-js-debug",
|
||||
"metadata": {
|
||||
"id": "7acbb4ce-c85a-49d4-8d95-a8054406ae97",
|
||||
|
@ -104,7 +105,7 @@
|
|||
},
|
||||
{
|
||||
"name": "ms-vscode.vscode-js-profile-table",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.5",
|
||||
"repo": "https://github.com/Microsoft/vscode-js-debug",
|
||||
"metadata": {
|
||||
"id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb",
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
if [ "$VSCODE_WSL_DEBUG_INFO" = true ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
COMMIT="@@COMMIT@@"
|
||||
APP_NAME="@@APPNAME@@"
|
||||
QUALITY="@@QUALITY@@"
|
||||
|
@ -11,7 +15,7 @@ VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")"
|
|||
ELECTRON="$VSCODE_PATH/$NAME.exe"
|
||||
if grep -qi Microsoft /proc/version; then
|
||||
# in a wsl shell
|
||||
WSL_BUILD=$(uname -r | sed -E 's/^[0-9.]+-([0-9]+)-Microsoft|([0-9]+).([0-9]+).([0-9]+)-microsoft-standard|.*/\1\2\3\4/')
|
||||
WSL_BUILD=$(uname -r | sed -E 's/^[0-9.]+-([0-9]+)-Microsoft.*|([0-9]+).([0-9]+).([0-9]+)-microsoft-standard.*|.*/\1\2\3\4/')
|
||||
if [ -z "$WSL_BUILD" ]; then
|
||||
WSL_BUILD=0
|
||||
fi
|
||||
|
|
|
@ -30,7 +30,6 @@ if not exist out yarn compile
|
|||
set ELECTRON_RUN_AS_NODE=1
|
||||
set NODE_ENV=development
|
||||
set VSCODE_DEV=1
|
||||
REM set ELECTRON_DEFAULT_ERROR_MODE=1 TODO@ben to investigate if this helps with builds reporting stacks if renderer crashes
|
||||
set ELECTRON_ENABLE_LOGGING=1
|
||||
set ELECTRON_ENABLE_STACK_DUMPING=1
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ const path = require('path');
|
|||
const util = require('util');
|
||||
const opn = require('opn');
|
||||
const minimist = require('minimist');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const APP_ROOT = path.dirname(__dirname);
|
||||
const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions');
|
||||
|
@ -21,6 +22,7 @@ const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench'
|
|||
|
||||
const args = minimist(process.argv, {
|
||||
boolean: [
|
||||
'watch',
|
||||
'no-launch',
|
||||
'help'
|
||||
],
|
||||
|
@ -35,6 +37,7 @@ const args = minimist(process.argv, {
|
|||
if (args.help) {
|
||||
console.log(
|
||||
'yarn web [options]\n' +
|
||||
' --watch Watch extensions that require browser specific builds\n' +
|
||||
' --no-launch Do not open VSCode web in the browser\n' +
|
||||
' --scheme Protocol (https or http)\n' +
|
||||
' --host Remote host\n' +
|
||||
|
@ -53,6 +56,104 @@ const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
|
|||
const HOST = args.host || 'localhost';
|
||||
const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
|
||||
|
||||
const exists = (path) => util.promisify(fs.exists)(path);
|
||||
const readFile = (path) => util.promisify(fs.readFile)(path);
|
||||
const CharCode_PC = '%'.charCodeAt(0);
|
||||
|
||||
async function initialize() {
|
||||
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
|
||||
|
||||
const staticExtensions = [];
|
||||
|
||||
const webpackConfigs = [];
|
||||
|
||||
await Promise.all(extensionFolders.map(async extensionFolder => {
|
||||
const packageJSONPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json');
|
||||
if (await exists(packageJSONPath)) {
|
||||
try {
|
||||
const packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
|
||||
if (packageJSON.main && !packageJSON.browser) {
|
||||
return; // unsupported
|
||||
}
|
||||
|
||||
if (packageJSON.browser) {
|
||||
packageJSON.main = packageJSON.browser;
|
||||
const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js');
|
||||
if ((await exists(webpackConfigPath))) {
|
||||
const configOrFnOrArray = require(webpackConfigPath);
|
||||
function addConfig(configOrFn) {
|
||||
if (typeof configOrFn === 'function') {
|
||||
webpackConfigs.push(configOrFn({}, {}));
|
||||
} else {
|
||||
webpackConfigs.push(configOrFn);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(configOrFnOrArray)) {
|
||||
configOrFnOrArray.forEach(addConfig);
|
||||
} else {
|
||||
addConfig(configOrFnOrArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packageNlsPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'package.nls.json');
|
||||
if (await exists(packageNlsPath)) {
|
||||
const packageNls = JSON.parse((await readFile(packageNlsPath)).toString());
|
||||
const translate = (obj) => {
|
||||
for (let key in obj) {
|
||||
const val = obj[key];
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach(translate);
|
||||
} else if (val && typeof val === 'object') {
|
||||
translate(val);
|
||||
} else if (typeof val === 'string' && val.charCodeAt(0) === CharCode_PC && val.charCodeAt(val.length - 1) === CharCode_PC) {
|
||||
const translated = packageNls[val.substr(1, val.length - 2)];
|
||||
if (translated) {
|
||||
obj[key] = translated;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
translate(packageJSON);
|
||||
}
|
||||
packageJSON.extensionKind = ['web']; // enable for Web
|
||||
staticExtensions.push({
|
||||
packageJSON,
|
||||
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (args.watch) {
|
||||
webpack(webpackConfigs).watch({}, (err, stats) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
reject();
|
||||
} else {
|
||||
console.log(stats.toString());
|
||||
resolve(staticExtensions);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
webpack(webpackConfigs).run((err, stats) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
reject();
|
||||
} else {
|
||||
console.log(stats.toString());
|
||||
resolve(staticExtensions);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const staticExtensionsPromise = initialize();
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const parsedUrl = url.parse(req.url, true);
|
||||
const pathname = parsedUrl.pathname;
|
||||
|
@ -139,35 +240,23 @@ function handleStaticExtension(req, res, parsedUrl) {
|
|||
* @param {import('http').ServerResponse} res
|
||||
*/
|
||||
async function handleRoot(req, res) {
|
||||
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
|
||||
const mapExtensionFolderToExtensionPackageJSON = new Map();
|
||||
|
||||
await Promise.all(extensionFolders.map(async extensionFolder => {
|
||||
try {
|
||||
const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString());
|
||||
if (packageJSON.main && packageJSON.name !== 'vscode-web-playground') {
|
||||
return; // unsupported
|
||||
}
|
||||
packageJSON.extensionKind = ['web']; // enable for Web
|
||||
|
||||
mapExtensionFolderToExtensionPackageJSON.set(extensionFolder, packageJSON);
|
||||
} catch (error) {
|
||||
return null;
|
||||
const match = req.url && req.url.match(/\?([^#]+)/);
|
||||
let ghPath;
|
||||
if (match) {
|
||||
const qs = new URLSearchParams(match[1]);
|
||||
ghPath = qs.get('gh');
|
||||
if (ghPath && !ghPath.startsWith('/')) {
|
||||
ghPath = '/' + ghPath;
|
||||
}
|
||||
}
|
||||
|
||||
const staticExtensions = await staticExtensionsPromise;
|
||||
const webConfiguration = escapeAttribute(JSON.stringify({
|
||||
staticExtensions, folderUri: ghPath
|
||||
? { scheme: 'github', authority: 'github.com', path: ghPath }
|
||||
: { scheme: 'memfs', path: `/sample-folder` }
|
||||
}));
|
||||
|
||||
const staticExtensions = [];
|
||||
|
||||
// Built in extensions
|
||||
mapExtensionFolderToExtensionPackageJSON.forEach((packageJSON, extensionFolder) => {
|
||||
staticExtensions.push({
|
||||
packageJSON,
|
||||
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
|
||||
});
|
||||
});
|
||||
|
||||
const webConfiguration = escapeAttribute(JSON.stringify({ staticExtensions, folderUri: { scheme: 'memfs', path: `/sample-folder` }}));
|
||||
|
||||
const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
|
||||
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfiguration) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
|
||||
.replace('{{WEBVIEW_ENDPOINT}}', '')
|
||||
|
|
|
@ -29,7 +29,6 @@ if not exist out yarn compile
|
|||
set NODE_ENV=development
|
||||
set VSCODE_DEV=1
|
||||
set VSCODE_CLI=1
|
||||
REM set ELECTRON_DEFAULT_ERROR_MODE=1 TODO@ben to investigate if this helps with builds reporting stacks if renderer crashes
|
||||
set ELECTRON_ENABLE_LOGGING=1
|
||||
set ELECTRON_ENABLE_STACK_DUMPING=1
|
||||
set VSCODE_LOGS=
|
||||
|
|
|
@ -22,6 +22,7 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" (
|
|||
call yarn gulp compile-extension:vscode-api-tests^
|
||||
compile-extension:vscode-colorize-tests^
|
||||
compile-extension:markdown-language-features^
|
||||
compile-extension:vscode-notebook-tests^
|
||||
compile-extension:emmet^
|
||||
compile-extension:css-language-features-server^
|
||||
compile-extension:html-language-features-server^
|
||||
|
@ -42,6 +43,9 @@ if %errorlevel% neq 0 exit /b %errorlevel%
|
|||
|
||||
:: Tests in the extension host
|
||||
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
|
|
|
@ -60,4 +60,4 @@ fi
|
|||
cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js
|
||||
cd $ROOT/extensions/html-language-features/server && $ROOT/scripts/node-electron.sh test/index.js
|
||||
|
||||
rm -r $VSCODEUSERDATADIR
|
||||
rm -rf $VSCODEUSERDATADIR
|
||||
|
|
6
src/bootstrap-fork.js
vendored
6
src/bootstrap-fork.js
vendored
|
@ -109,6 +109,9 @@ function pipeLoggingToParent() {
|
|||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ type: string; severity: string; arguments: string; }} arg
|
||||
*/
|
||||
function safeSend(arg) {
|
||||
try {
|
||||
process.send(arg);
|
||||
|
@ -117,6 +120,9 @@ function pipeLoggingToParent() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} obj
|
||||
*/
|
||||
function isObject(obj) {
|
||||
return typeof obj === 'object'
|
||||
&& obj !== null
|
||||
|
|
7
src/bootstrap-window.js
vendored
7
src/bootstrap-window.js
vendored
|
@ -18,7 +18,6 @@ exports.assign = function assign(destination, source) {
|
|||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string[]} modulePaths
|
||||
* @param {(result, configuration: object) => any} resultCallback
|
||||
* @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options
|
||||
|
@ -198,6 +197,10 @@ function registerDeveloperKeybindings(disallowReloadKeybinding) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | Error} error
|
||||
* @param {boolean} enableDeveloperTools
|
||||
*/
|
||||
function onUnexpectedError(error, enableDeveloperTools) {
|
||||
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
|
@ -208,7 +211,7 @@ function onUnexpectedError(error, enableDeveloperTools) {
|
|||
|
||||
console.error('[uncaught exception]: ' + error);
|
||||
|
||||
if (error && error.stack) {
|
||||
if (error && typeof error !== 'string' && error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
}
|
||||
|
|
29
src/bootstrap.js
vendored
29
src/bootstrap.js
vendored
|
@ -13,7 +13,6 @@ Error.stackTraceLimit = 100;
|
|||
|
||||
// Workaround for Electron not installing a handler to ignore SIGPIPE
|
||||
// (https://github.com/electron/electron/issues/13254)
|
||||
// @ts-ignore
|
||||
process.on('SIGPIPE', () => {
|
||||
console.error(new Error('Unexpected SIGPIPE'));
|
||||
});
|
||||
|
@ -27,7 +26,6 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
|
|||
throw new Error('Missing injectPath');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const Module = require('module');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -51,12 +49,12 @@ exports.injectNodeModuleLookupPath = function (injectPath) {
|
|||
return paths;
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Remove global paths from the node lookup paths
|
||||
|
||||
exports.removeGlobalNodeModuleLookupPaths = function() {
|
||||
// @ts-ignore
|
||||
exports.removeGlobalNodeModuleLookupPaths = function () {
|
||||
const Module = require('module');
|
||||
// @ts-ignore
|
||||
const globalPaths = Module.globalPaths;
|
||||
|
@ -74,15 +72,15 @@ exports.removeGlobalNodeModuleLookupPaths = function() {
|
|||
return paths.slice(0, paths.length - commonSuffixLength);
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Add support for using node_modules.asar
|
||||
|
||||
/**
|
||||
* @param {string=} nodeModulesPath
|
||||
*/
|
||||
exports.enableASARSupport = function (nodeModulesPath) {
|
||||
|
||||
// @ts-ignore
|
||||
const Module = require('module');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -111,9 +109,11 @@ exports.enableASARSupport = function (nodeModulesPath) {
|
|||
return paths;
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region URI helpers
|
||||
|
||||
/**
|
||||
* @param {string} _path
|
||||
* @returns {string}
|
||||
|
@ -136,9 +136,11 @@ exports.uriFromPath = function (_path) {
|
|||
|
||||
return uri.replace(/#/g, '%23');
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region FS helpers
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<string>}
|
||||
|
@ -185,9 +187,11 @@ exports.mkdirp = function mkdirp(dir) {
|
|||
|
||||
return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region NLS helpers
|
||||
|
||||
/**
|
||||
* @returns {{locale?: string, availableLanguages: {[lang: string]: string;}, pseudo?: boolean }}
|
||||
*/
|
||||
|
@ -235,14 +239,15 @@ exports.setupNLS = function () {
|
|||
|
||||
return nlsConfig;
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Portable helpers
|
||||
|
||||
/**
|
||||
* @returns {{ portableDataPath: string, isPortable: boolean }}
|
||||
*/
|
||||
exports.configurePortable = function () {
|
||||
// @ts-ignore
|
||||
const product = require('../product.json');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
@ -270,6 +275,7 @@ exports.configurePortable = function () {
|
|||
return path.join(getApplicationPath(), 'data');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
|
||||
return path.join(path.dirname(getApplicationPath()), portableDataName);
|
||||
}
|
||||
|
@ -299,16 +305,17 @@ exports.configurePortable = function () {
|
|||
isPortable
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region ApplicationInsights
|
||||
/**
|
||||
* Prevents appinsights from monkey patching modules.
|
||||
* This should be called before importing the applicationinsights module
|
||||
*/
|
||||
|
||||
// Prevents appinsights from monkey patching modules.
|
||||
// This should be called before importing the applicationinsights module
|
||||
exports.avoidMonkeyPatchFromAppInsights = function () {
|
||||
// @ts-ignore
|
||||
process.env['APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL'] = true; // Skip monkey patching of 3rd party modules by appinsights
|
||||
global['diagnosticsSource'] = {}; // Prevents diagnostic channel (which patches "require") from initializing entirely
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
|
24
src/main.js
24
src/main.js
|
@ -16,9 +16,8 @@ const fs = require('fs');
|
|||
const os = require('os');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const paths = require('./paths');
|
||||
// @ts-ignore
|
||||
/** @type {any} */
|
||||
const product = require('../product.json');
|
||||
// @ts-ignore
|
||||
const { app, protocol } = require('electron');
|
||||
|
||||
// Enable portable support
|
||||
|
@ -42,6 +41,11 @@ let crashReporterDirectory = args['crash-reporter-directory'];
|
|||
if (crashReporterDirectory) {
|
||||
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
||||
|
||||
if (!path.isAbsolute(crashReporterDirectory)) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(crashReporterDirectory)) {
|
||||
try {
|
||||
fs.mkdirSync(crashReporterDirectory);
|
||||
|
@ -58,9 +62,11 @@ if (crashReporterDirectory) {
|
|||
|
||||
// Start crash reporter
|
||||
const { crashReporter } = require('electron');
|
||||
const productName = (product.crashReporter && product.crashReporter.productName) || product.nameShort;
|
||||
const companyName = (product.crashReporter && product.crashReporter.companyName) || 'Microsoft';
|
||||
crashReporter.start({
|
||||
companyName: 'Microsoft',
|
||||
productName: product.nameShort,
|
||||
companyName: companyName,
|
||||
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
|
||||
submitURL: '',
|
||||
uploadToServer: false
|
||||
});
|
||||
|
@ -80,6 +86,13 @@ setCurrentWorkingDirectory();
|
|||
// Register custom schemes with privileges
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: 'vscode-resource',
|
||||
privileges: {
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true,
|
||||
}
|
||||
}, {
|
||||
scheme: 'vscode-webview-resource',
|
||||
privileges: {
|
||||
secure: true,
|
||||
|
@ -122,7 +135,6 @@ if (locale) {
|
|||
// Load our code once ready
|
||||
app.once('ready', function () {
|
||||
if (args['trace']) {
|
||||
// @ts-ignore
|
||||
const contentTracing = require('electron').contentTracing;
|
||||
|
||||
const traceOptions = {
|
||||
|
@ -469,6 +481,7 @@ function getNodeCachedDir() {
|
|||
}
|
||||
|
||||
//#region NLS Support
|
||||
|
||||
/**
|
||||
* Resolve the NLS configuration
|
||||
*
|
||||
|
@ -564,4 +577,5 @@ function getLegacyUserDefinedLocaleSync(localeConfigPath) {
|
|||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
// @ts-expect-error
|
||||
const pkg = require('../package.json');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
|
|
@ -188,6 +188,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
}));
|
||||
}
|
||||
|
||||
// Use our own sanitizer so that we can let through only spans.
|
||||
// Otherwise, we'd be letting all html be rendered.
|
||||
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
|
||||
markedOptions.sanitizer = (html: string): string => {
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
|
||||
return match ? html : '';
|
||||
};
|
||||
markedOptions.sanitize = true;
|
||||
markedOptions.renderer = renderer;
|
||||
|
||||
|
@ -203,18 +210,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
markedOptions
|
||||
);
|
||||
|
||||
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
if (token.tag === 'span' && markdown.isTrusted) {
|
||||
if (token.attrs['style'] && Object.keys(token.attrs).length === 1) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
element.innerHTML = insane(renderedMarkdown, {
|
||||
allowedSchemes,
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
|
||||
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'tr', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
|
||||
allowedAttributes: {
|
||||
'a': ['href', 'name', 'target', 'data-href'],
|
||||
'iframe': ['allowfullscreen', 'frameborder', 'src'],
|
||||
'img': ['src', 'title', 'alt', 'width', 'height'],
|
||||
'div': ['class', 'data-code'],
|
||||
'span': ['class'],
|
||||
'span': ['class', 'style'],
|
||||
// https://github.com/microsoft/vscode/issues/95937
|
||||
'th': ['align'],
|
||||
'td': ['align']
|
||||
}
|
||||
},
|
||||
filter
|
||||
});
|
||||
|
||||
signalInnerHTML!();
|
||||
|
|
|
@ -405,6 +405,7 @@ export interface IActionBarOptions {
|
|||
ariaLabel?: string;
|
||||
animated?: boolean;
|
||||
triggerKeys?: ActionTrigger;
|
||||
allowContextMenu?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: IActionBarOptions = {
|
||||
|
@ -634,9 +635,11 @@ export class ActionBar extends Disposable implements IActionRunner {
|
|||
actionViewItemElement.setAttribute('role', 'presentation');
|
||||
|
||||
// Prevent native context menu on actions
|
||||
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}));
|
||||
if (!this.options.allowContextMenu) {
|
||||
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}));
|
||||
}
|
||||
|
||||
let item: IActionViewItem | undefined;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface ICheckboxOpts extends ICheckboxStyles {
|
|||
|
||||
export interface ICheckboxStyles {
|
||||
inputActiveOptionBorder?: Color;
|
||||
inputActiveOptionForeground?: Color;
|
||||
inputActiveOptionBackground?: Color;
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ export interface ISimpleCheckboxStyles {
|
|||
|
||||
const defaultOpts = {
|
||||
inputActiveOptionBorder: Color.fromHex('#007ACC00'),
|
||||
inputActiveOptionForeground: Color.fromHex('#FFFFFF'),
|
||||
inputActiveOptionBackground: Color.fromHex('#0E639C50')
|
||||
};
|
||||
|
||||
|
@ -170,6 +172,9 @@ export class Checkbox extends Widget {
|
|||
if (styles.inputActiveOptionBorder) {
|
||||
this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
}
|
||||
if (styles.inputActiveOptionForeground) {
|
||||
this._opts.inputActiveOptionForeground = styles.inputActiveOptionForeground;
|
||||
}
|
||||
if (styles.inputActiveOptionBackground) {
|
||||
this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground;
|
||||
}
|
||||
|
@ -179,6 +184,7 @@ export class Checkbox extends Widget {
|
|||
protected applyStyles(): void {
|
||||
if (this.domNode) {
|
||||
this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : 'transparent';
|
||||
this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit';
|
||||
this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : 'transparent';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ export class Dialog extends Disposable {
|
|||
this.modal = this.container.appendChild($(`.monaco-dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
|
||||
this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
|
||||
this.element = this.shadowElement.appendChild($('.monaco-dialog-box'));
|
||||
this.element.setAttribute('role', 'dialog');
|
||||
hide(this.element);
|
||||
|
||||
// If no button is provided, default to OK
|
||||
|
@ -109,6 +110,28 @@ export class Dialog extends Disposable {
|
|||
this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar'));
|
||||
}
|
||||
|
||||
private getAriaLabel(): string {
|
||||
let typeLabel = nls.localize('dialogInfoMessage', 'Info');
|
||||
switch (this.options.type) {
|
||||
case 'error':
|
||||
nls.localize('dialogErrorMessage', 'Error');
|
||||
break;
|
||||
case 'warning':
|
||||
nls.localize('dialogWarningMessage', 'Warning');
|
||||
break;
|
||||
case 'pending':
|
||||
nls.localize('dialogPendingMessage', 'In Progress');
|
||||
break;
|
||||
case 'none':
|
||||
case 'info':
|
||||
case 'question':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return `${typeLabel}: ${this.message} ${this.options.detail || ''}`;
|
||||
}
|
||||
|
||||
updateMessage(message: string): void {
|
||||
if (this.messageDetailElement) {
|
||||
this.messageDetailElement.innerText = message;
|
||||
|
@ -242,7 +265,7 @@ export class Dialog extends Disposable {
|
|||
|
||||
this.applyStyles();
|
||||
|
||||
this.element.setAttribute('aria-label', this.message);
|
||||
this.element.setAttribute('aria-label', this.getAriaLabel());
|
||||
show(this.element);
|
||||
|
||||
// Focus first element
|
||||
|
|
|
@ -35,6 +35,7 @@ export interface IFindInputOptions extends IFindInputStyles {
|
|||
|
||||
export interface IFindInputStyles extends IInputBoxStyles {
|
||||
inputActiveOptionBorder?: Color;
|
||||
inputActiveOptionForeground?: Color;
|
||||
inputActiveOptionBackground?: Color;
|
||||
}
|
||||
|
||||
|
@ -51,6 +52,7 @@ export class FindInput extends Widget {
|
|||
private fixFocusOnOptionClickEnabled = true;
|
||||
|
||||
private inputActiveOptionBorder?: Color;
|
||||
private inputActiveOptionForeground?: Color;
|
||||
private inputActiveOptionBackground?: Color;
|
||||
private inputBackground?: Color;
|
||||
private inputForeground?: Color;
|
||||
|
@ -101,6 +103,7 @@ export class FindInput extends Widget {
|
|||
this.label = options.label || NLS_DEFAULT_LABEL;
|
||||
|
||||
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
|
||||
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
|
||||
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
|
||||
this.inputBackground = options.inputBackground;
|
||||
this.inputForeground = options.inputForeground;
|
||||
|
@ -155,6 +158,7 @@ export class FindInput extends Widget {
|
|||
appendTitle: appendRegexLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground
|
||||
}));
|
||||
this._register(this.regex.onChange(viaKeyboard => {
|
||||
|
@ -172,6 +176,7 @@ export class FindInput extends Widget {
|
|||
appendTitle: appendWholeWordsLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground
|
||||
}));
|
||||
this._register(this.wholeWords.onChange(viaKeyboard => {
|
||||
|
@ -186,6 +191,7 @@ export class FindInput extends Widget {
|
|||
appendTitle: appendCaseSensitiveLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground
|
||||
}));
|
||||
this._register(this.caseSensitive.onChange(viaKeyboard => {
|
||||
|
@ -301,6 +307,7 @@ export class FindInput extends Widget {
|
|||
|
||||
public style(styles: IFindInputStyles): void {
|
||||
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
|
||||
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
|
||||
this.inputBackground = styles.inputBackground;
|
||||
this.inputForeground = styles.inputForeground;
|
||||
|
@ -323,6 +330,7 @@ export class FindInput extends Widget {
|
|||
if (this.domNode) {
|
||||
const checkBoxStyles: ICheckboxStyles = {
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground,
|
||||
};
|
||||
this.regex.style(checkBoxStyles);
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface IFindInputCheckboxOpts {
|
|||
readonly appendTitle: string;
|
||||
readonly isChecked: boolean;
|
||||
readonly inputActiveOptionBorder?: Color;
|
||||
readonly inputActiveOptionForeground?: Color;
|
||||
readonly inputActiveOptionBackground?: Color;
|
||||
}
|
||||
|
||||
|
@ -26,6 +27,7 @@ export class CaseSensitiveCheckbox extends Checkbox {
|
|||
title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + opts.appendTitle,
|
||||
isChecked: opts.isChecked,
|
||||
inputActiveOptionBorder: opts.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: opts.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: opts.inputActiveOptionBackground
|
||||
});
|
||||
}
|
||||
|
@ -38,6 +40,7 @@ export class WholeWordsCheckbox extends Checkbox {
|
|||
title: NLS_WHOLE_WORD_CHECKBOX_LABEL + opts.appendTitle,
|
||||
isChecked: opts.isChecked,
|
||||
inputActiveOptionBorder: opts.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: opts.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: opts.inputActiveOptionBackground
|
||||
});
|
||||
}
|
||||
|
@ -50,6 +53,7 @@ export class RegexCheckbox extends Checkbox {
|
|||
title: NLS_REGEX_CHECKBOX_LABEL + opts.appendTitle,
|
||||
isChecked: opts.isChecked,
|
||||
inputActiveOptionBorder: opts.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: opts.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: opts.inputActiveOptionBackground
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles {
|
|||
|
||||
export interface IReplaceInputStyles extends IInputBoxStyles {
|
||||
inputActiveOptionBorder?: Color;
|
||||
inputActiveOptionForeground?: Color;
|
||||
inputActiveOptionBackground?: Color;
|
||||
}
|
||||
|
||||
|
@ -47,6 +48,7 @@ export class PreserveCaseCheckbox extends Checkbox {
|
|||
title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle,
|
||||
isChecked: opts.isChecked,
|
||||
inputActiveOptionBorder: opts.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: opts.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: opts.inputActiveOptionBackground
|
||||
});
|
||||
}
|
||||
|
@ -63,6 +65,7 @@ export class ReplaceInput extends Widget {
|
|||
private fixFocusOnOptionClickEnabled = true;
|
||||
|
||||
private inputActiveOptionBorder?: Color;
|
||||
private inputActiveOptionForeground?: Color;
|
||||
private inputActiveOptionBackground?: Color;
|
||||
private inputBackground?: Color;
|
||||
private inputForeground?: Color;
|
||||
|
@ -109,6 +112,7 @@ export class ReplaceInput extends Widget {
|
|||
this.label = options.label || NLS_DEFAULT_LABEL;
|
||||
|
||||
this.inputActiveOptionBorder = options.inputActiveOptionBorder;
|
||||
this.inputActiveOptionForeground = options.inputActiveOptionForeground;
|
||||
this.inputActiveOptionBackground = options.inputActiveOptionBackground;
|
||||
this.inputBackground = options.inputBackground;
|
||||
this.inputForeground = options.inputForeground;
|
||||
|
@ -160,6 +164,7 @@ export class ReplaceInput extends Widget {
|
|||
appendTitle: '',
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground,
|
||||
}));
|
||||
this._register(this.preserveCase.onChange(viaKeyboard => {
|
||||
|
@ -271,6 +276,7 @@ export class ReplaceInput extends Widget {
|
|||
|
||||
public style(styles: IReplaceInputStyles): void {
|
||||
this.inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
this.inputActiveOptionForeground = styles.inputActiveOptionForeground;
|
||||
this.inputActiveOptionBackground = styles.inputActiveOptionBackground;
|
||||
this.inputBackground = styles.inputBackground;
|
||||
this.inputForeground = styles.inputForeground;
|
||||
|
@ -293,6 +299,7 @@ export class ReplaceInput extends Widget {
|
|||
if (this.domNode) {
|
||||
const checkBoxStyles: ICheckboxStyles = {
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionForeground: this.inputActiveOptionForeground,
|
||||
inputActiveOptionBackground: this.inputActiveOptionBackground,
|
||||
};
|
||||
this.preserveCase.style(checkBoxStyles);
|
||||
|
|
|
@ -707,7 +707,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(this.keyboardNavigationEventFilter || (() => true))
|
||||
.filter(() => this.automaticKeyboardNavigation || this.triggered)
|
||||
.filter(e => this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
|
||||
.filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
|
||||
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
|
||||
.event;
|
||||
|
||||
|
|
1
src/vs/base/common/insane/insane.d.ts
vendored
1
src/vs/base/common/insane/insane.d.ts
vendored
|
@ -9,6 +9,7 @@ export function insane(
|
|||
readonly allowedSchemes?: readonly string[],
|
||||
readonly allowedTags?: readonly string[],
|
||||
readonly allowedAttributes?: { readonly [key: string]: string[] },
|
||||
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
|
||||
},
|
||||
strict?: boolean,
|
||||
): string;
|
||||
|
|
|
@ -182,7 +182,9 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe
|
|||
}
|
||||
|
||||
// the actual key
|
||||
result.push(key);
|
||||
if (key !== '') {
|
||||
result.push(key);
|
||||
}
|
||||
|
||||
return result.join(labels.separator);
|
||||
}
|
||||
|
|
|
@ -335,3 +335,13 @@ export function getMediaMime(path: string): string | undefined {
|
|||
const ext = extname(path);
|
||||
return mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
|
||||
export function getExtensionForMimeType(mimeType: string): string | undefined {
|
||||
for (const extension in mapExtToMediaMimes) {
|
||||
if (mapExtToMediaMimes[extension] === mimeType) {
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ export namespace Schemas {
|
|||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
|
||||
export const oldVscodeWebviewResource = 'vscode-resource';
|
||||
|
||||
export const vscodeWebviewResource = 'vscode-webview-resource';
|
||||
}
|
||||
|
||||
|
|
|
@ -6,17 +6,23 @@
|
|||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { URI, uriToFsPath } from 'vs/base/common/uri';
|
||||
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
|
||||
import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
|
||||
export function originalFSPath(uri: URI): string {
|
||||
return uriToFsPath(uri, true);
|
||||
}
|
||||
|
||||
//#region IExtUri
|
||||
|
||||
export interface IExtUri {
|
||||
|
||||
// --- identity
|
||||
|
||||
/**
|
||||
* Compares two uris.
|
||||
*
|
||||
|
@ -33,7 +39,16 @@ export interface IExtUri {
|
|||
* @param uri2 Uri
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
isEqual(uri1: URI, uri2: URI, ignoreFragment?: boolean): boolean;
|
||||
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
|
||||
*
|
||||
* @param base A uri which is "longer"
|
||||
* @param parentCandidate A uri which is "shorter" then `base`
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
|
@ -42,6 +57,80 @@ export interface IExtUri {
|
|||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
|
||||
|
||||
// --- path math
|
||||
|
||||
basenameOrAuthority(resource: URI): string;
|
||||
|
||||
/**
|
||||
* Returns the basename of the path component of an uri.
|
||||
* @param resource
|
||||
*/
|
||||
basename(resource: URI): string;
|
||||
|
||||
/**
|
||||
* Returns the extension of the path component of an uri.
|
||||
* @param resource
|
||||
*/
|
||||
extname(resource: URI): string;
|
||||
/**
|
||||
* Return a URI representing the directory of a URI path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @returns The URI representing the directory of the input URI.
|
||||
*/
|
||||
dirname(resource: URI): URI;
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
joinPath(resource: URI, ...pathFragment: string[]): URI
|
||||
/**
|
||||
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
|
||||
*
|
||||
* @param resource The URI to normalize the path.
|
||||
* @returns The URI with the normalized path.
|
||||
*/
|
||||
normalizePath(resource: URI): URI;
|
||||
/**
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
relativePath(from: URI, to: URI): string | undefined;
|
||||
/**
|
||||
* Resolves an absolute or relative path against a base URI.
|
||||
* The path can be relative or absolute posix or a Windows path
|
||||
*/
|
||||
resolvePath(base: URI, path: string): URI;
|
||||
|
||||
// --- misc
|
||||
|
||||
/**
|
||||
* Returns true if the URI path is absolute.
|
||||
*/
|
||||
isAbsolutePath(resource: URI): boolean;
|
||||
/**
|
||||
* Tests whether the two authorities are the same
|
||||
*/
|
||||
isEqualAuthority(a1: string, a2: string): boolean;
|
||||
/**
|
||||
* Returns true if the URI path has a trailing path separator
|
||||
*/
|
||||
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
|
||||
/**
|
||||
* Removes a trailing path separator, if there's one.
|
||||
* Important: Doesn't remove the first slash, it would make the URI invalid
|
||||
*/
|
||||
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
|
||||
/**
|
||||
* Adds a trailing path separator to the URI if there isn't one already.
|
||||
* For example, c:\ would be unchanged, but c:\users would become c:\users\
|
||||
*/
|
||||
addTrailingPathSeparator(resource: URI, sep?: string): URI;
|
||||
}
|
||||
|
||||
export class ExtUri implements IExtUri {
|
||||
|
@ -49,33 +138,178 @@ export class ExtUri implements IExtUri {
|
|||
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
|
||||
|
||||
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
|
||||
// scheme
|
||||
let ret = strCompare(uri1.scheme, uri2.scheme);
|
||||
if (ret === 0) {
|
||||
// authority
|
||||
ret = compareIgnoreCase(uri1.authority, uri2.authority);
|
||||
if (ret === 0) {
|
||||
// path
|
||||
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
|
||||
// query
|
||||
if (ret === 0) {
|
||||
ret = strCompare(uri1.query, uri2.query);
|
||||
// fragment
|
||||
if (ret === 0 && !ignoreFragment) {
|
||||
ret = strCompare(uri1.fragment, uri2.fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uri1 === uri2) {
|
||||
return 0;
|
||||
}
|
||||
return ret;
|
||||
return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment));
|
||||
}
|
||||
|
||||
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
|
||||
if (uri1 === uri2) {
|
||||
return true;
|
||||
}
|
||||
if (!uri1 || !uri2) {
|
||||
return false;
|
||||
}
|
||||
return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment);
|
||||
}
|
||||
|
||||
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
|
||||
return getComparisonKey(uri, this._ignorePathCasing(uri), ignoreFragment);
|
||||
return uri.with({
|
||||
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
|
||||
fragment: ignoreFragment ? null : undefined
|
||||
}).toString();
|
||||
}
|
||||
|
||||
isEqual(uri1: URI, uri2: URI, ignoreFragment: boolean = false): boolean {
|
||||
return isEqual(uri1, uri2, this._ignorePathCasing(uri1), ignoreFragment);
|
||||
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
|
||||
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- path math
|
||||
|
||||
joinPath(resource: URI, ...pathFragment: string[]): URI {
|
||||
return URI.joinPath(resource, ...pathFragment);
|
||||
}
|
||||
|
||||
basenameOrAuthority(resource: URI): string {
|
||||
return basename(resource) || resource.authority;
|
||||
}
|
||||
|
||||
basename(resource: URI): string {
|
||||
return paths.posix.basename(resource.path);
|
||||
}
|
||||
|
||||
extname(resource: URI): string {
|
||||
return paths.posix.extname(resource.path);
|
||||
}
|
||||
|
||||
dirname(resource: URI): URI {
|
||||
if (resource.path.length === 0) {
|
||||
return resource;
|
||||
}
|
||||
let dirname;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
|
||||
} else {
|
||||
dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
}
|
||||
}
|
||||
return resource.with({
|
||||
path: dirname
|
||||
});
|
||||
}
|
||||
|
||||
normalizePath(resource: URI): URI {
|
||||
if (!resource.path.length) {
|
||||
return resource;
|
||||
}
|
||||
let normalizedPath: string;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
|
||||
} else {
|
||||
normalizedPath = paths.posix.normalize(resource.path);
|
||||
}
|
||||
return resource.with({
|
||||
path: normalizedPath
|
||||
});
|
||||
}
|
||||
|
||||
relativePath(from: URI, to: URI): string | undefined {
|
||||
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
|
||||
return undefined;
|
||||
}
|
||||
if (from.scheme === Schemas.file) {
|
||||
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
|
||||
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
|
||||
}
|
||||
let fromPath = from.path || '/', toPath = to.path || '/';
|
||||
if (this._ignorePathCasing(from)) {
|
||||
// make casing of fromPath match toPath
|
||||
let i = 0;
|
||||
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
|
||||
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
|
||||
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPath = toPath.substr(0, i) + fromPath.substr(i);
|
||||
}
|
||||
return paths.posix.relative(fromPath, toPath);
|
||||
}
|
||||
|
||||
resolvePath(base: URI, path: string): URI {
|
||||
if (base.scheme === Schemas.file) {
|
||||
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
|
||||
return base.with({
|
||||
authority: newURI.authority,
|
||||
path: newURI.path
|
||||
});
|
||||
}
|
||||
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
|
||||
path = extpath.toSlashes(path);
|
||||
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
|
||||
path = '/' + path;
|
||||
}
|
||||
}
|
||||
return base.with({
|
||||
path: paths.posix.resolve(base.path, path)
|
||||
});
|
||||
}
|
||||
|
||||
// --- misc
|
||||
|
||||
isAbsolutePath(resource: URI): boolean {
|
||||
return !!resource.path && resource.path[0] === '/';
|
||||
}
|
||||
|
||||
isEqualAuthority(a1: string, a2: string) {
|
||||
return a1 === a2 || equalsIgnoreCase(a1, a2);
|
||||
}
|
||||
|
||||
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
|
||||
} else {
|
||||
const p = resource.path;
|
||||
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
|
||||
}
|
||||
}
|
||||
|
||||
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
|
||||
if (hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
let isRootSep: boolean = false;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
|
||||
} else {
|
||||
sep = '/';
|
||||
const p = resource.path;
|
||||
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
|
||||
}
|
||||
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path + '/' });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,266 +320,41 @@ export class ExtUri implements IExtUri {
|
|||
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
|
||||
* ```
|
||||
*/
|
||||
export const exturi = new ExtUri(() => false);
|
||||
export const extUri = new ExtUri(() => false);
|
||||
|
||||
/**
|
||||
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
|
||||
* understand what you are doing.
|
||||
*
|
||||
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
|
||||
* knows when path casing matters and when not.
|
||||
*/
|
||||
export const extUriIgnorePathCase = new ExtUri(_ => true);
|
||||
|
||||
//#endregion
|
||||
|
||||
export function originalFSPath(uri: URI): string {
|
||||
return uriToFsPath(uri, true);
|
||||
}
|
||||
|
||||
// DO NOT EXPORT, DO NOT USE
|
||||
function _ignorePathCasingGuess(resource: URI | undefined): boolean {
|
||||
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
|
||||
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
|
||||
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
|
||||
return resource && resource.scheme === Schemas.file ? !isLinux : true;
|
||||
}
|
||||
return uri && uri.scheme === Schemas.file ? !isLinux : true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
*
|
||||
* @param resource Uri
|
||||
* @param ignorePathCasing Ignore casing when comparing path component (defaults mostly to `true`)
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
export function getComparisonKey(resource: URI, ignorePathCasing: boolean = _ignorePathCasingGuess(resource), ignoreFragment: boolean = false): string {
|
||||
return resource.with({
|
||||
path: ignorePathCasing ? resource.path.toLowerCase() : undefined,
|
||||
fragment: ignoreFragment ? null : undefined
|
||||
}).toString();
|
||||
}
|
||||
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
|
||||
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
|
||||
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
|
||||
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
|
||||
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
|
||||
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
|
||||
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
|
||||
export const joinPath = extUri.joinPath.bind(extUri);
|
||||
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
|
||||
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
|
||||
/**
|
||||
* Tests whether two uris are equal
|
||||
*
|
||||
* @param first Uri
|
||||
* @param second Uri
|
||||
* @param ignorePathCasing Ignore casing when comparing path component (defaults mostly to `true`)
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
export function isEqual(first: URI | undefined, second: URI | undefined, ignorePathCasing: boolean = _ignorePathCasingGuess(first), ignoreFragment: boolean = false): boolean {
|
||||
if (first === second) {
|
||||
return true;
|
||||
}
|
||||
if (!first || !second) {
|
||||
return false;
|
||||
}
|
||||
if (first.scheme !== second.scheme || !isEqualAuthority(first.authority, second.authority)) {
|
||||
return false;
|
||||
}
|
||||
const p1 = first.path, p2 = second.path;
|
||||
return (p1 === p2 || ignorePathCasing && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
|
||||
*
|
||||
* @param base A uri which is "longer"
|
||||
* @param parentCandidate A uri which is "shorter" then `base`
|
||||
* @param ignorePathCasing Ignore casing when comparing path component (defaults mostly to `true`)
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
export function isEqualOrParent(base: URI, parentCandidate: URI, ignorePathCasing: boolean = _ignorePathCasingGuess(base), ignoreFragment: boolean = false): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignorePathCasing) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
|
||||
return extpath.isEqualOrParent(base.path, parentCandidate.path, ignorePathCasing, '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export function basenameOrAuthority(resource: URI): string {
|
||||
return basename(resource) || resource.authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the two authorities are the same
|
||||
*/
|
||||
export function isEqualAuthority(a1: string, a2: string) {
|
||||
return a1 === a2 || equalsIgnoreCase(a1, a2);
|
||||
}
|
||||
|
||||
export function basename(resource: URI): string {
|
||||
return paths.posix.basename(resource.path);
|
||||
}
|
||||
|
||||
export function extname(resource: URI): string {
|
||||
return paths.posix.extname(resource.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URI representing the directory of a URI path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @returns The URI representing the directory of the input URI.
|
||||
*/
|
||||
export function dirname(resource: URI): URI {
|
||||
if (resource.path.length === 0) {
|
||||
return resource;
|
||||
}
|
||||
let dirname;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
|
||||
} else {
|
||||
dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
}
|
||||
}
|
||||
return resource.with({
|
||||
path: dirname
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
|
||||
let joinedPath: string;
|
||||
if (resource.scheme === 'file') {
|
||||
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
|
||||
} else {
|
||||
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
|
||||
}
|
||||
return resource.with({
|
||||
path: joinedPath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
|
||||
*
|
||||
* @param resource The URI to normalize the path.
|
||||
* @returns The URI with the normalized path.
|
||||
*/
|
||||
export function normalizePath(resource: URI): URI {
|
||||
if (!resource.path.length) {
|
||||
return resource;
|
||||
}
|
||||
let normalizedPath: string;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
|
||||
} else {
|
||||
normalizedPath = paths.posix.normalize(resource.path);
|
||||
}
|
||||
return resource.with({
|
||||
path: normalizedPath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the URI path is absolute.
|
||||
*/
|
||||
export function isAbsolutePath(resource: URI): boolean {
|
||||
return !!resource.path && resource.path[0] === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the URI path has a trailing path separator
|
||||
*/
|
||||
export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
|
||||
} else {
|
||||
const p = resource.path;
|
||||
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a trailing path separator, if there's one.
|
||||
* Important: Doesn't remove the first slash, it would make the URI invalid
|
||||
*/
|
||||
export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
|
||||
if (hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing path separator to the URI if there isn't one already.
|
||||
* For example, c:\ would be unchanged, but c:\users would become c:\users\
|
||||
*/
|
||||
export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
let isRootSep: boolean = false;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
|
||||
} else {
|
||||
sep = '/';
|
||||
const p = resource.path;
|
||||
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
|
||||
}
|
||||
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path + '/' });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned.
|
||||
* The returned relative path always uses forward slashes.
|
||||
*/
|
||||
export function relativePath(from: URI, to: URI, ignorePathCasing = _ignorePathCasingGuess(from)): string | undefined {
|
||||
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
|
||||
return undefined;
|
||||
}
|
||||
if (from.scheme === Schemas.file) {
|
||||
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
|
||||
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
|
||||
}
|
||||
let fromPath = from.path || '/', toPath = to.path || '/';
|
||||
if (ignorePathCasing) {
|
||||
// make casing of fromPath match toPath
|
||||
let i = 0;
|
||||
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
|
||||
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
|
||||
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPath = toPath.substr(0, i) + fromPath.substr(i);
|
||||
}
|
||||
return paths.posix.relative(fromPath, toPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an absolute or relative path against a base URI.
|
||||
* The path can be relative or absolute posix or a Windows path
|
||||
*/
|
||||
export function resolvePath(base: URI, path: string): URI {
|
||||
if (base.scheme === Schemas.file) {
|
||||
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
|
||||
return base.with({
|
||||
authority: newURI.authority,
|
||||
path: newURI.path
|
||||
});
|
||||
}
|
||||
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
|
||||
path = extpath.toSlashes(path);
|
||||
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
|
||||
path = '/' + path;
|
||||
}
|
||||
}
|
||||
return base.with({
|
||||
path: paths.posix.resolve(base.path, path)
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
|
||||
const distinctParents: T[] = [];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, getComparisonKey, exturi } from 'vs/base/common/resources';
|
||||
import { dirname, basename, distinctParents, joinPath, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { toSlashes } from 'vs/base/common/extpath';
|
||||
|
@ -235,16 +235,19 @@ suite('Resources', () => {
|
|||
}
|
||||
});
|
||||
|
||||
function assertEqualURI(actual: URI, expected: URI, message?: string) {
|
||||
if (!isEqual(expected, actual, undefined, false)) {
|
||||
function assertEqualURI(actual: URI, expected: URI, message?: string, ignoreCase?: boolean) {
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
if (!util.isEqual(expected, actual)) {
|
||||
assert.equal(actual.toString(), expected.toString(), message);
|
||||
}
|
||||
}
|
||||
|
||||
function assertRelativePath(u1: URI, u2: URI, expectedPath: string | undefined, ignoreJoin?: boolean, ignoreCase?: boolean) {
|
||||
assert.equal(relativePath(u1, u2, ignoreCase), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
|
||||
assert.equal(util.relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
|
||||
if (expectedPath !== undefined && !ignoreJoin) {
|
||||
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal');
|
||||
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal', ignoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,13 +350,16 @@ suite('Resources', () => {
|
|||
});
|
||||
|
||||
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean | undefined, expected: boolean) {
|
||||
assert.equal(isEqual(u1, u2, ignoreCase), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
|
||||
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
|
||||
assert.equal(util.isEqual(u1, u2), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
|
||||
assert.equal(util.compare(u1, u2) === 0, expected);
|
||||
assert.equal(util.getComparisonKey(u1) === util.getComparisonKey(u2), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
|
||||
assert.equal(util.isEqualOrParent(u1, u2), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
|
||||
if (!ignoreCase) {
|
||||
assert.equal(exturi.compare(u1, u2) === 0, expected);
|
||||
assert.equal(u1.toString() === u2.toString(), expected);
|
||||
}
|
||||
assert.equal(getComparisonKey(u1, ignoreCase) === getComparisonKey(u2, ignoreCase), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
|
||||
assert.equal(isEqualOrParent(u1, u2, ignoreCase), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
|
||||
}
|
||||
|
||||
|
||||
|
@ -393,34 +399,35 @@ suite('Resources', () => {
|
|||
});
|
||||
|
||||
test('isEqualOrParent', () => {
|
||||
|
||||
let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar');
|
||||
let fileURI2 = isWindows ? URI.file('c:\\foo') : URI.file('/foo');
|
||||
let fileURI2b = isWindows ? URI.file('C:\\Foo\\') : URI.file('/Foo/');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI, true), true, '1');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI, false), true, '2');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2, true), true, '3');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2, false), true, '4');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2b, true), true, '5');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2b, false), false, '6');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI), true, '1');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI), true, '2');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2), true, '3');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2), true, '4');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2b), true, '5');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2b), false, '6');
|
||||
|
||||
assert.equal(isEqualOrParent(fileURI2, fileURI, false), false, '7');
|
||||
assert.equal(isEqualOrParent(fileURI2b, fileURI2, true), true, '8');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI2, fileURI), false, '7');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI2b, fileURI2), true, '8');
|
||||
|
||||
let fileURI3 = URI.parse('foo://server:453/foo/bar/goo');
|
||||
let fileURI4 = URI.parse('foo://server:453/foo/');
|
||||
let fileURI5 = URI.parse('foo://server:453/foo');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI3, true), true, '11');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI3, false), true, '12');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI4, true), true, '13');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15');
|
||||
assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI3, true), true, '11');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI3), true, '12');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI4, true), true, '13');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI4), true, '14');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI, true), false, '15');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI5, fileURI5, true), true, '16');
|
||||
|
||||
let fileURI6 = URI.parse('foo://server:453/foo?q=1');
|
||||
let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI5, true), false, '17');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI6, true), true, '18');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI6, true), true, '19');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI5, true), false, '20');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI5), false, '17');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI6), true, '18');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI6), true, '19');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI5), false, '20');
|
||||
});
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue