Shortening the path name for scripts

This commit is contained in:
Erich Gamma 2017-07-20 10:53:07 +02:00
parent 08f2a93b77
commit f911acfa49

View file

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//tslint:disable
'use strict';
import * as path from 'path';
@ -12,8 +13,7 @@ type AutoDetect = 'on' | 'off';
let taskProvider: vscode.Disposable | undefined;
export function activate(_context: vscode.ExtensionContext): void {
let workspaceRoot = vscode.workspace.rootPath;
if (!workspaceRoot) {
if (!vscode.workspace.workspaceFolders) {
return;
}
@ -100,15 +100,19 @@ async function provideNpmScripts(): Promise<vscode.Task[]> {
return emptyTasks;
}
const singleRoot = allTasks.length === 1;
for (const folder of folders) {
let tasks = await provideNpmScriptsForFolder(folder, singleRoot);
let folderPaths = folders.map(each => each.uri.fsPath);
let shortPaths = shorten(folderPaths);
const isSingleRoot = allTasks.length === 1;
for (let i = 0; i < folders.length; i++) {
let tasks = await provideNpmScriptsForFolder(folders[i], shortPaths[i], isSingleRoot);
allTasks.push(...tasks);
}
return allTasks;
}
async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder, singleRoot: boolean): Promise<vscode.Task[]> {
async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder, shortPath: string, singleRoot: boolean): Promise<vscode.Task[]> {
let rootPath = folder.uri.fsPath;
let emptyTasks: vscode.Task[] = [];
@ -126,7 +130,7 @@ async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder, single
const result: vscode.Task[] = [];
Object.keys(json.scripts).filter(isNotPreOrPostScript).forEach(each => {
const task = createTask(each, `run ${each}`, rootPath, singleRoot);
const task = createTask(each, `run ${each}`, rootPath, shortPath, singleRoot);
const lowerCaseTaskName = each.toLowerCase();
if (isBuildTask(lowerCaseTaskName)) {
task.group = vscode.TaskGroup.Build;
@ -136,20 +140,20 @@ async function provideNpmScriptsForFolder(folder: vscode.WorkspaceFolder, single
result.push(task);
});
// always add npm install (without a problem matcher)
result.push(createTask('install', 'install', rootPath, singleRoot, []));
result.push(createTask('install', 'install', rootPath, shortPath, singleRoot, []));
return result;
} catch (e) {
return emptyTasks;
}
}
function createTask(script: string, cmd: string, rootPath: string, singleRoot: boolean, matcher?: any): vscode.Task {
function createTask(script: string, cmd: string, rootPath: string, shortPath: string, singleRoot: boolean, matcher?: any): vscode.Task {
function getTaskName(script: string, rootPath: string, singleRoot: boolean) {
function getTaskName(script: string, shortPath: string, singleRoot: boolean) {
if (singleRoot) {
return script;
}
return `${script} - ${rootPath}`;
return `${script} - ${shortPath}`;
}
function getNpmCommandLine(cmd: string): string {
@ -164,7 +168,312 @@ function createTask(script: string, cmd: string, rootPath: string, singleRoot: b
script: script,
path: rootPath
};
let taskName = getTaskName(script, rootPath, singleRoot);
let taskName = getTaskName(script, shortPath, singleRoot);
return new vscode.Task(kind, taskName, 'npm', new vscode.ShellExecution(getNpmCommandLine(cmd), { cwd: rootPath }), matcher);
}
// tslint:disable:no-unused-variable
// TODO code to shorten paths - this should be available as a utility module/API
// copied from base/platform.ts
// --- THIS FILE IS TEMPORARY UNTIL ENV.TS IS CLEANED UP. IT CAN SAFELY BE USED IN ALL TARGET EXECUTION ENVIRONMENTS (node & dom) ---
let _isWindows = false;
let _isMacintosh = false;
let _isLinux = false;
let _isRootUser = false;
let _isNative = false;
let _isWeb = false;
let _locale: string = '';
let _language: string = '';
interface NLSConfig {
locale: string;
availableLanguages: { [key: string]: string; };
}
export interface IProcessEnvironment {
[key: string]: string;
}
interface INodeProcess {
platform: string;
env: IProcessEnvironment;
getuid(): number;
}
declare let process: INodeProcess;
declare let global: any;
interface INavigator {
userAgent: string;
language: string;
}
declare let navigator: INavigator;
declare let self: any;
export const LANGUAGE_DEFAULT = 'en';
// OS detection
if (typeof process === 'object') {
_isWindows = (process.platform === 'win32');
_isMacintosh = (process.platform === 'darwin');
_isLinux = (process.platform === 'linux');
_isRootUser = !_isWindows && (process.getuid() === 0);
let rawNlsConfig = process.env['VSCODE_NLS_CONFIG'];
if (rawNlsConfig) {
try {
let nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);
let resolved = nlsConfig.availableLanguages['*'];
_locale = nlsConfig.locale;
// VSCode's default language is 'en'
_language = resolved ? resolved : LANGUAGE_DEFAULT;
} catch (e) {
}
}
_isNative = true;
} else if (typeof navigator === 'object') {
let userAgent = navigator.userAgent;
_isWindows = userAgent.indexOf('Windows') >= 0;
_isMacintosh = userAgent.indexOf('Macintosh') >= 0;
_isLinux = userAgent.indexOf('Linux') >= 0;
_isWeb = true;
_locale = navigator.language;
_language = _locale;
}
export enum Platform {
Web,
Mac,
Linux,
Windows
}
let _platform: Platform = Platform.Web;
if (_isNative) {
if (_isMacintosh) {
_platform = Platform.Mac;
} else if (_isWindows) {
_platform = Platform.Windows;
} else if (_isLinux) {
_platform = Platform.Linux;
}
}
export const isWindows = _isWindows;
export const isMacintosh = _isMacintosh;
export const isLinux = _isLinux;
export const isRootUser = _isRootUser;
export const isNative = _isNative;
export const isWeb = _isWeb;
export const platform = _platform;
/**
* The language used for the user interface. The format of
* the string is all lower case (e.g. zh-tw for Traditional
* Chinese)
*/
export const language = _language;
/**
* The OS locale or the locale specified by --locale. The format of
* the string is all lower case (e.g. zh-tw for Traditional
* Chinese). The UI is not necessarily shown in the provided locale.
*/
export const locale = _locale;
export interface TimeoutToken {
}
export interface IntervalToken {
}
interface IGlobals {
Worker?: any;
setTimeout(callback: (...args: any[]) => void, delay: number, ...args: any[]): TimeoutToken;
clearTimeout(token: TimeoutToken): void;
setInterval(callback: (...args: any[]) => void, delay: number, ...args: any[]): IntervalToken;
clearInterval(token: IntervalToken): void;
}
const _globals = <IGlobals>(typeof self === 'object' ? self : global);
export const globals: any = _globals;
export function hasWebWorkerSupport(): boolean {
return typeof _globals.Worker !== 'undefined';
}
export const setTimeout = _globals.setTimeout.bind(_globals);
export const clearTimeout = _globals.clearTimeout.bind(_globals);
export const setInterval = _globals.setInterval.bind(_globals);
export const clearInterval = _globals.clearInterval.bind(_globals);
export const enum OperatingSystem {
Windows = 1,
Macintosh = 2,
Linux = 3
}
export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux));
export const enum AccessibilitySupport {
/**
* This should be the browser case where it is not known if a screen reader is attached or no.
*/
Unknown = 0,
Disabled = 1,
Enabled = 2
}
// copied from vscode/src/base/common/label.ts
const nativeSep = isWindows ? '\\' : '/';
/**
* Shortens the paths but keeps them easy to distinguish.
* Replaces not important parts with ellipsis.
* Every shorten path matches only one original path and vice versa.
*
* Algorithm for shortening paths is as follows:
* 1. For every path in list, find unique substring of that path.
* 2. Unique substring along with ellipsis is shortened path of that path.
* 3. To find unique substring of path, consider every segment of length from 1 to path.length of path from end of string
* and if present segment is not substring to any other paths then present segment is unique path,
* else check if it is not present as suffix of any other path and present segment is suffix of path itself,
* if it is true take present segment as unique path.
* 4. Apply ellipsis to unique segment according to whether segment is present at start/in-between/end of path.
*
* Example 1
* 1. consider 2 paths i.e. ['a\\b\\c\\d', 'a\\f\\b\\c\\d']
* 2. find unique path of first path,
* a. 'd' is present in path2 and is suffix of path2, hence not unique of present path.
* b. 'c' is present in path2 and 'c' is not suffix of present path, similarly for 'b' and 'a' also.
* c. 'd\\c' is suffix of path2.
* d. 'b\\c' is not suffix of present path.
* e. 'a\\b' is not present in path2, hence unique path is 'a\\b...'.
* 3. for path2, 'f' is not present in path1 hence unique is '...\\f\\...'.
*
* Example 2
* 1. consider 2 paths i.e. ['a\\b', 'a\\b\\c'].
* a. Even if 'b' is present in path2, as 'b' is suffix of path1 and is not suffix of path2, unique path will be '...\\b'.
* 2. for path2, 'c' is not present in path1 hence unique path is '..\\c'.
*/
const ellipsis = '\u2026';
const unc = '\\\\';
export function shorten(paths: string[]): string[] {
const shortenedPaths: string[] = new Array(paths.length);
// for every path
let match = false;
for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) {
let path = paths[pathIndex];
if (path === '') {
shortenedPaths[pathIndex] = `.${nativeSep}`;
continue;
}
if (!path) {
shortenedPaths[pathIndex] = path;
continue;
}
match = true;
// trim for now and concatenate unc path (e.g. \\network) or root path (/etc) later
let prefix = '';
if (path.indexOf(unc) === 0) {
prefix = path.substr(0, path.indexOf(unc) + unc.length);
path = path.substr(path.indexOf(unc) + unc.length);
} else if (path.indexOf(nativeSep) === 0) {
prefix = path.substr(0, path.indexOf(nativeSep) + nativeSep.length);
path = path.substr(path.indexOf(nativeSep) + nativeSep.length);
}
// pick the first shortest subpath found
const segments: string[] = path.split(nativeSep);
for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) {
for (let start = segments.length - subpathLength; match && start >= 0; start--) {
match = false;
let subpath = segments.slice(start, start + subpathLength).join(nativeSep);
// that is unique to any other path
for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) {
// suffix subpath treated specially as we consider no match 'x' and 'x/...'
if (otherPathIndex !== pathIndex && paths[otherPathIndex] && paths[otherPathIndex].indexOf(subpath) > -1) {
const isSubpathEnding: boolean = (start + subpathLength === segments.length);
// Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string.
// prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories.
const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(nativeSep) > -1) ? nativeSep + subpath : subpath;
const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep);
match = !isSubpathEnding || isOtherPathEnding;
}
}
// found unique subpath
if (!match) {
let result = '';
// preserve disk drive or root prefix
if (endsWith(segments[0], ':') || prefix !== '') {
if (start === 1) {
// extend subpath to include disk drive prefix
start = 0;
subpathLength++;
subpath = segments[0] + nativeSep + subpath;
}
if (start > 0) {
result = segments[0] + nativeSep;
}
result = prefix + result;
}
// add ellipsis at the beginning if neeeded
if (start > 0) {
result = result + ellipsis + nativeSep;
}
result = result + subpath;
// add ellipsis at the end if needed
if (start + subpathLength < segments.length) {
result = result + nativeSep + ellipsis;
}
shortenedPaths[pathIndex] = result;
}
}
}
if (match) {
shortenedPaths[pathIndex] = path; // use full path if no unique subpaths found
}
}
return shortenedPaths;
}
// copied from base/strings.ts
/**
* Determines if haystack ends with needle.
*/
export function endsWith(haystack: string, needle: string): boolean {
let diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.indexOf(needle, diff) === diff;
} else if (diff === 0) {
return haystack === needle;
} else {
return false;
}
}