From 7e29a30b29313329775183c479b627a5301c6676 Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Thu, 28 Dec 2017 00:10:03 +0100 Subject: [PATCH] enhancements --- CHANGELOG.md | 8 ++ package-lock.json | 2 +- package.json | 2 +- src/extension.ts | 153 ++++++++++++++++++++++++++++++------ src/gui.ts | 132 +++++++++++++++++++++++++++++++ src/i18.ts | 16 ++++ src/lang/de.ts | 16 ++++ src/lang/en.ts | 16 ++++ src/packages.ts | 48 +++++++++-- src/targets.ts | 48 +++++++++-- src/tools.ts | 29 ++++++- src/tools/quickexecution.ts | 77 +++++++++++++++--- src/tools/sendfile.ts | 11 ++- 13 files changed, 502 insertions(+), 56 deletions(-) create mode 100644 src/gui.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 894bbbb..cce0ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log (vscode-deploy-reloaded) +## 0.4.0 (December 28th, 2017; enhancements) + +* bugfixes +* code improvements +* display network information +* more information for the output channel +* tool actions, packages and targets are sorted and displayed by usage in the GUI now + ## 0.3.0 (December 27th, 2017; tools) * bugfixes diff --git a/package-lock.json b/package-lock.json index d271811..0064b98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-deploy-reloaded", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 51cea6b..e5da18d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-deploy-reloaded", "displayName": "Deploy (Reloaded)", "description": "Deploys files of a workspace to a destination.", - "version": "0.3.0", + "version": "0.4.0", "publisher": "mkloubert", "engines": { "vscode": "^1.19.0" diff --git a/src/extension.ts b/src/extension.ts index 912102a..491f395 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,7 @@ const CompareVersion = require('compare-versions'); import * as deploy_commands from './commands'; import * as deploy_compare from './compare'; import * as deploy_contracts from './contracts'; +import * as deploy_gui from './gui'; import * as deploy_helpers from './helpers'; import * as deploy_html from './html'; import * as deploy_log from './log'; @@ -36,6 +37,7 @@ import * as deploy_workspaces from './workspaces'; import * as Enumerable from 'node-enumerable'; import * as i18 from './i18'; import * as Moment from 'moment'; +import * as OS from 'os'; import * as Path from 'path'; import * as vscode from 'vscode'; @@ -299,10 +301,20 @@ async function reloadWorkspaceFolders(added: vscode.WorkspaceFolder[], removed?: } if (removeWorkspace) { - if (deploy_helpers.tryDispose(WS)) { + outputChannel.append(i18.t('workspaces.removing', + WS.folder.uri.fsPath) + ' '); + try { + WS.dispose(); + WORKSPACES.splice(i, 1); hasBeenRemoved = true; + + outputChannel.appendLine(`[${i18.t('ok')}]`); } + catch (e) { + outputChannel.appendLine(`[${i18.t('error', e)}]`); + } + outputChannel.appendLine(''); } if (!hasBeenRemoved) { @@ -346,24 +358,29 @@ async function reloadWorkspaceFolders(added: vscode.WorkspaceFolder[], removed?: newWorkspace = new deploy_workspaces.Workspace( nextWorkspaceId--, WSF, CTX ); + + outputChannel.append(i18.t('workspaces.initializing', + WSF.uri.fsPath) + ' '); try { const HAS_BEEN_INITIALIZED = await newWorkspace.initialize(); if (HAS_BEEN_INITIALIZED) { WORKSPACES.push(newWorkspace); + + outputChannel.appendLine(`[${i18.t('ok')}]`); } else { - deploy_helpers.showErrorMessage( + throw new Error( i18.t('workspaces.errors.notInitialized', WSF.uri.fsPath) ); } } catch (err) { - deploy_log.CONSOLE - .trace(err, 'extension.reloadWorkspaceFolders(2)'); - deploy_helpers.tryDispose(newWorkspace); + + outputChannel.appendLine(`[${i18.t('error', err)}]`); } + outputChannel.appendLine(''); } catch (e) { deploy_log.CONSOLE @@ -637,7 +654,9 @@ async function activateExtension(context: vscode.ExtensionContext) { // output channel WF.next(() => { - outputChannel = vscode.window.createOutputChannel('Deploy (Reloaded)'); + context.subscriptions.push( + outputChannel = vscode.window.createOutputChannel('Deploy Reloaded') + ); }); // active workspace provider @@ -676,6 +695,12 @@ async function activateExtension(context: vscode.ExtensionContext) { outputChannel.appendLine(`GitHub : https://github.com/mkloubert/vscode-deploy-reloaded`); outputChannel.appendLine(`Twitter: https://twitter.com/mjkloubert`); outputChannel.appendLine(`Donate : https://paypal.me/MarcelKloubert`); + + outputChannel.appendLine(''); + outputChannel.appendLine(''); + outputChannel.appendLine(i18.t('extension.initializing')); + outputChannel.appendLine(''); + outputChannel.appendLine(''); }); // commands @@ -752,6 +777,7 @@ async function activateExtension(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.deploy.reloaded.deployWorkspace', async () => { try { const PKG = await deploy_packages.showPackageQuickPick( + context, getAllPackagesSorted(), { placeHolder: i18.t('packages.selectPackage'), @@ -836,6 +862,7 @@ async function activateExtension(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.deploy.reloaded.pullWorkspace', async () => { try { const PKG = await deploy_packages.showPackageQuickPick( + context, getAllPackagesSorted(), { placeHolder: i18.t('packages.selectPackage'), @@ -919,6 +946,7 @@ async function activateExtension(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.deploy.reloaded.deletePackage', async () => { try { const PKG = await deploy_packages.showPackageQuickPick( + context, getAllPackagesSorted(), { placeHolder: i18.t('packages.selectPackage'), @@ -965,6 +993,7 @@ async function activateExtension(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.deploy.reloaded.listDirectory', async () => { try { const TARGET = await deploy_targets.showTargetQuickPick( + context, getAllTargetsSorted(), { placeHolder: i18.t('listDirectory.selectSource'), @@ -990,7 +1019,7 @@ async function activateExtension(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.deploy.reloaded.quickExecution', async () => { try { await deploy_tools_quick_execution._1b87f2ee_b636_45b6_807c_0e2d25384b02_1409614337( - currentContext, + context, getAllWorkspacesSorted(), activeWorkspaces.map(aws => aws), ); @@ -1100,6 +1129,7 @@ async function activateExtension(context: vscode.ExtensionContext) { }, label: '$(code) ' + i18.t('tools.quickExecution.label'), description: i18.t('tools.quickExecution.description'), + state: 0, }, { @@ -1110,49 +1140,57 @@ async function activateExtension(context: vscode.ExtensionContext) { }, label: '$(plus) ' + i18.t('tools.createDeployScript.label'), description: i18.t('tools.createDeployScript.description'), + state: 1, }, { action: async () => { await deploy_tools.createDeployOperationScript( + context, getActiveWorkspacesOrAll() ); }, label: '$(plus) ' + i18.t('tools.createDeployOperationScript.label'), description: i18.t('tools.createDeployOperationScript.description'), + state: 2, }, { action: async () => { await deploy_tools.showPackageFiles( + context, getAllPackagesSorted() ); }, label: '$(microscope) ' + i18.t('tools.showPackageFiles.label'), description: i18.t('tools.showPackageFiles.description'), + state: 3, }, { action: async () => { - await deploy_tools_send_file.sendOrReceiveFile(currentContext); + await deploy_tools_send_file.sendOrReceiveFile(context); }, label: '$(broadcast) ' + i18.t('tools.sendOrReceiveFile.label'), description: i18.t('tools.sendOrReceiveFile.description'), + state: 4, } ]; const SELECTED_ITEM = await vscode.window.showQuickPick( - deploy_helpers.sortByLabel( + deploy_gui.sortQuickPicksByUsage( QUICK_PICKS, - i => { - // skip icons - return i.label.substr( - i.label.indexOf(' ') - ).toLowerCase() - .trim(); + context.workspaceState, + deploy_tools.KEY_TOOL_USAGE, + (i) => { + // remove icon + return i.label + .substr(i.label.indexOf(' ')) + .trim(); } ) ); + if (SELECTED_ITEM) { await Promise.resolve( SELECTED_ITEM.action() @@ -1172,7 +1210,7 @@ async function activateExtension(context: vscode.ExtensionContext) { // receive file vscode.commands.registerCommand('extension.deploy.reloaded.receiveFile', async () => { try { - await deploy_tools_send_file.receiveFile(currentContext); + await deploy_tools_send_file.receiveFile(context); } catch (e) { deploy_log.CONSOLE @@ -1187,7 +1225,7 @@ async function activateExtension(context: vscode.ExtensionContext) { // send file vscode.commands.registerCommand('extension.deploy.reloaded.sendFile', async () => { try { - await deploy_tools_send_file.sendFile(currentContext); + await deploy_tools_send_file.sendFile(context); } catch (e) { deploy_log.CONSOLE @@ -1251,16 +1289,19 @@ async function activateExtension(context: vscode.ExtensionContext) { } }); - // reload plugins + // load plugins WF.next(async () => { await reloadPlugins(); - outputChannel.appendLine(''); + let pluginInfo = ''; - outputChannel.appendLine(`Loaded ${PLUGINS.length} plugins:`); + pluginInfo += `${i18.t('plugins.__loaded', PLUGINS.length)}\n`; PLUGINS.forEach((pi) => { - outputChannel.appendLine(`- ${pi.__type}`); + pluginInfo += `- ${pi.__type}\n`; }); + + deploy_log.CONSOLE + .info(pluginInfo, 'extension.deploy.reloaded.loadPlugins'); }); // global VSCode events @@ -1283,6 +1324,10 @@ async function activateExtension(context: vscode.ExtensionContext) { }), vscode.workspace.onDidChangeConfiguration((e) => { + deploy_packages.resetPackageUsage(context); + deploy_targets.resetTargetUsage(context); + deploy_tools.resetToolUsage(context); + onDidChangeConfiguration(e).then(() => { }).catch((err) => { deploy_log.CONSOLE @@ -1409,12 +1454,76 @@ async function activateExtension(context: vscode.ExtensionContext) { } catch (e) { deploy_log.CONSOLE - .trace(e, 'extension.checkForNewVersion(2)'); + .trace(e, 'extension.checkForNewVersion(2)'); + } + } + } + }); + + // display network info + WF.next(() => { + try { + outputChannel.appendLine(i18.t('network.hostname', + OS.hostname())); + + const NETWORK_INTERFACES = OS.networkInterfaces(); + + const LIST_OF_IFNAMES = Object.keys(NETWORK_INTERFACES).sort((x, y) => { + return deploy_helpers.compareValuesBy(x, y, n => { + return deploy_helpers.normalizeString(n); + }); + }); + + if (Object.keys(NETWORK_INTERFACES).length > 0) { + outputChannel.appendLine(i18.t('network.interfaces.list')); + + for (const IFNAME of LIST_OF_IFNAMES) { + const IFACES = NETWORK_INTERFACES[IFNAME].filter(x => { + return !x.internal; + }).filter(x => { + let addr = deploy_helpers.normalizeString(x.address); + + if ('IPv4' === x.family) { + return !/^(127\.[\d.]+|[0:]+1|localhost)$/.test(addr); + } + + if ('IPv6' === x.family) { + return '::1' !== addr; + } + + return true; + }).sort((x, y) => { + return deploy_helpers.compareValuesBy(x, y, (i) => { + return 'IPv4' === i.family ? 0 : 1; + }); + }); + + if (IFACES.length > 0) { + outputChannel.appendLine(` - '${IFNAME}':`); + IFACES.forEach(x => { + outputChannel.appendLine(` [${x.family}] '${x.address}' / '${x.netmask}' ('${x.mac}')`); + }); + + outputChannel.appendLine(''); + } } } + else { + outputChannel.appendLine(''); + } + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'extension.displayNetworkInfo()'); } }); + WF.next(() => { + outputChannel.appendLine(''); + outputChannel.appendLine(i18.t('extension.initialized')); + outputChannel.appendLine(''); + }); + if (!isDeactivating) { await WF.start(); } diff --git a/src/gui.ts b/src/gui.ts new file mode 100644 index 0000000..07b11b6 --- /dev/null +++ b/src/gui.ts @@ -0,0 +1,132 @@ +/** + * This file is part of the vscode-deploy-reloaded distribution. + * Copyright (c) Marcel Joachim Kloubert. + * + * vscode-deploy-reloaded is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, version 3. + * + * vscode-deploy-reloaded is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + +import * as deploy_contracts from './contracts'; +import * as deploy_helpers from './helpers'; +import * as deploy_log from './log'; +import * as Enumerable from 'node-enumerable'; +import * as vscode from 'vscode'; + + +interface LastExecutedActions { + executionCount: { [ id: string ]: any }, + lastExecuted: string | false; +}; + + +/** + * Sorts quick pick items by usage by using the 'state' property as ID value. + * + * @param {IItem|TItem[]} items The item(s) to sort. + * @param {vscode.vscode.Memento} state The memento where to store the states and counters. + * @param {string} key The key inside the memento. + * @param {Function} [labelResolver] The custom function that resolves the label value of an item. + * + * @return {TItem[]} The sorted items. + */ +export function sortQuickPicksByUsage ( + items: TItem | TItem[], + state: vscode.Memento, + key: string, + labelResolver?: (item: TItem) => string +) : deploy_contracts.ActionQuickPick[] +{ + items = deploy_helpers.asArray(items); + key = deploy_helpers.toStringSafe(key); + + if (!labelResolver) { + labelResolver = (item) => { + return deploy_helpers.toStringSafe(item.label) + .trim(); + }; + } + + let le: LastExecutedActions; + const UPDATE_ITEM = (id: any) => { + try { + id = deploy_helpers.toStringSafe(id); + + le.lastExecuted = id; + le.executionCount[id] = isNaN(le.executionCount[id]) ? 1 + : (le.executionCount[id] + 1); + + state.update(key, le).then(() => { + }, (err) => { + deploy_log.CONSOLE + .trace(err, 'gui.sortQuickPicksByUsage().UPDATE_LAST_EXECUTED_QP(3)'); + }); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'gui.sortQuickPicksByUsage().UPDATE_LAST_EXECUTED_QP(2)'); + } + }; + + try { + le = state.get(key); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'gui.sortQuickPicksByUsage(1)'); + } + + if (!deploy_helpers.isObject(le)) { + le = { + executionCount: {}, + lastExecuted: false, + }; + } + + try { + return Enumerable.from( items ).select(i => { + return deploy_helpers.cloneObjectFlat(i); + }).pipe(i => { + const BASE_ACTION = i.action; + + (i).action = async function() { + UPDATE_ITEM(i.state); + + if (BASE_ACTION) { + return await Promise.resolve( + BASE_ACTION.apply(i, arguments), + ); + } + }; + }).orderBy(i => { + // first if item has been executed last + const ID = deploy_helpers.toStringSafe(i.state); + + return le.lastExecuted === ID ? 0 : 1; + }).thenByDescending(i => { + const ID = deploy_helpers.toStringSafe(i.state); + + return isNaN(le.executionCount[ID]) ? 0 + : le.executionCount[ID]; + }).thenBy(i => { + return deploy_helpers.normalizeString( + labelResolver(i) + ); + }).toArray(); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'gui.sortQuickPicksByUsage(2)'); + + return items; + } +} diff --git a/src/i18.ts b/src/i18.ts index 53d09fd..058be11 100644 --- a/src/i18.ts +++ b/src/i18.ts @@ -141,6 +141,10 @@ export interface Translation { }; }; error?: string; + extension?: { + initialized?: string; + initializing?: string; + }; file?: string; files?: string; ftp?: { @@ -176,6 +180,12 @@ export interface Translation { size?: string; }; maxDepthReached?: string; + network?: { + hostname?: string; + interfaces?: { + list?: string; + }; + }; no?: string; noFiles?: string; notFound?: { @@ -194,6 +204,7 @@ export interface Translation { virtualTarget?: string; }; plugins?: { + __loaded?: string; compiler?: { invalidDirectory?: string; }; @@ -390,6 +401,9 @@ export interface Translation { result?: { title?: string; }, + uuid?: { + notSupported?: string; + }; }; sendOrReceiveFile?: { description?: string; @@ -441,8 +455,10 @@ export interface Translation { cannotUseTargetForFile?: string; notInitialized?: string; }; + initializing?: string; noneFound?: string; noSelected?: string; + removing?: string; selectWorkspace?: string; }; yes?: string; diff --git a/src/lang/de.ts b/src/lang/de.ts index 62ec829..18b63e7 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -134,6 +134,10 @@ export const translation: Translation = { }, }, error: "FEHLER:{0:trim,surround,leading_space}", + extension: { + initialized: "Erweiterung wurde erfolgreich initialisiert.", + initializing: "Erweiterung wird initialisiert ...", + }, file: "Datei", files: "Dateien", ftp: { @@ -169,6 +173,12 @@ export const translation: Translation = { size: "Grösse:{0:trim,leading_space}", }, maxDepthReached: "Maximale Tiefe von{0:trim,leading_space} erreicht!", + network: { + hostname: 'Name dieses Computers:{0:trim,surround,leading_space}', + interfaces: { + list: 'Netzwerk-Adapter:', + } + }, no: "Nein", noFiles: "Keine Dateien gefunden!", notFound: { @@ -186,6 +196,7 @@ export const translation: Translation = { virtualTarget: "Virtuelles Ziel für Paket{0:trim,surround,leading_space}", }, plugins: { + __loaded: "{0:trim,ending_space}Plug-Ins wurden geladen:", compiler: { invalidDirectory: "{0:trim,surround,ending_space}ist ein ungültiges Verzeichnis!", }, @@ -384,6 +395,9 @@ export const translation: Translation = { result: { title: "Resultat der Ausführung", }, + uuid: { + notSupported: "UUID Version{0:trim,surround,leading_space} wird nicht unterstützt!", + }, }, sendOrReceiveFile: { description: "Sendet oder empfängt eine Dateien an/von einen/einem entfernten Editor", @@ -435,8 +449,10 @@ export const translation: Translation = { cannotUseTargetForFile: "Kann das Ziel{0:trim,surround,leading_space} nicht für die Datei{1:trim,surround,leading_space} verwenden!", notInitialized: "Der Arbeitsbereich{0:trim,surround,leading_space} wurde nicht initialisiert!", }, + initializing: "Initialisiere Arbeitsbereich{0:trim,surround,leading_space} ...", noneFound: "Keine Arbeitsbereiche gefunden!", noSelected: "kein Arbeitsbereich ausgewählt", + removing: "Schliesse Arbeitsbereich{0:trim,surround,leading_space} ...", selectWorkspace: "Wählen Sie einen Arbeitsbereich ...", }, yes: 'Ja', diff --git a/src/lang/en.ts b/src/lang/en.ts index d9aff8b..ab61cd4 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -134,6 +134,10 @@ export const translation: Translation = { }, }, error: "ERROR:{0:trim,surround,leading_space}", + extension: { + initialized: "Extension has been initialized.", + initializing: "Extension is initializing ...", + }, file: "File", files: "Files", ftp: { @@ -169,6 +173,12 @@ export const translation: Translation = { size: "Size:{0:trim,leading_space}", }, maxDepthReached: "Cannot go deeper than{0:trim,leading_space}!", + network: { + hostname: 'Your hostname:{0:trim,surround,leading_space}', + interfaces: { + list: 'Your network interfaces:', + } + }, no: "No", noFiles: "No files found!", notFound: { @@ -187,6 +197,7 @@ export const translation: Translation = { virtualTarget: "Virtual target for package{0:trim,surround,leading_space}", }, plugins: { + __loaded: "Loaded{0:trim,leading_space} plugins:", compiler: { invalidDirectory: "{0:trim,surround,ending_space}is an invalid directory!", }, @@ -385,6 +396,9 @@ export const translation: Translation = { result: { title: "'Quick execution' result", }, + uuid: { + notSupported: "UUID version{0:trim,surround,leading_space} is not supported!", + }, }, sendOrReceiveFile: { description: "Sends or receives a file to/from a remote editor", @@ -436,8 +450,10 @@ export const translation: Translation = { cannotUseTargetForFile: "Cannot use target{0:trim,surround,leading_space} for file{1:trim,surround,leading_space}!", notInitialized: "Workspace{0:trim,surround,leading_space} has not been initialized!", }, + initializing: "Initializing workspace{0:trim,surround,leading_space} ...", noneFound: "No workspaces found!", noSelected: "no workspace selected", + removing: "Closing workspace{0:trim,surround,leading_space} ...", selectWorkspace: "Select a workspace ...", }, yes: 'Yes', diff --git a/src/packages.ts b/src/packages.ts index a745d2e..07a4bc8 100644 --- a/src/packages.ts +++ b/src/packages.ts @@ -15,7 +15,9 @@ * along with this program. If not, see . */ +import * as Crypto from 'crypto'; import * as deploy_contracts from './contracts'; +import * as deploy_gui from './gui'; import * as deploy_helpers from './helpers'; import * as deploy_log from './log'; import * as deploy_targets from './targets'; @@ -120,6 +122,8 @@ export interface SyncWhenOpenSetting extends deploy_contracts.FileFilter { } +const KEY_PACKAGE_USAGE = 'vscdrLastExecutedPackageActions'; + /** * Handles an "auto deploy" of a file. * @@ -347,25 +351,46 @@ export function getTargetsOfPackage(pkg: Package): deploy_targets.Target[] | fal return targets; } + +/** + * Resets the package usage statistics. + * + * @param {vscode.ExtensionContext} context The extension context. + */ +export function resetPackageUsage(context: vscode.ExtensionContext) { + context.workspaceState.update(KEY_PACKAGE_USAGE, undefined).then(() => { + }, (err) => { + deploy_log.CONSOLE + .trace(err, 'packages.resetPackageUsage()'); + }); +} + /** * Shows a quick pick for a list of packages. * + * @param {vscode.ExtensionContext} context The extension context. * @param {Package|Package[]} packages One or more packages. * @param {vscode.QuickPickOptions} [opts] Custom options for the quick picks. * * @return {Promise} The promise that contains the selected package (if selected) * or (false) if no package is available. */ -export async function showPackageQuickPick(packages: Package | Package[], +export async function showPackageQuickPick(context: vscode.ExtensionContext, + packages: Package | Package[], opts?: vscode.QuickPickOptions): Promise { - const QUICK_PICKS: deploy_contracts.ActionQuickPick[] = deploy_helpers.asArray(packages).map(pkg => { + const QUICK_PICKS: deploy_contracts.ActionQuickPick[] = deploy_helpers.asArray(packages).map(pkg => { const WORKSPACE = pkg.__workspace; return { - label: getPackageName(pkg), + action: () => { + return pkg; + }, + label: '$(gift) ' + getPackageName(pkg), description: deploy_helpers.toStringSafe(pkg.description), detail: WORKSPACE.rootPath, - state: pkg, + state: Crypto.createHash('sha256') + .update( new Buffer(deploy_helpers.toStringSafe(pkg.__id), 'utf8') ) + .digest('hex'), }; }); @@ -377,17 +402,26 @@ export async function showPackageQuickPick(packages: Package | Package[], return false; } - let selectedItem: deploy_contracts.ActionQuickPick; + let selectedItem: deploy_contracts.ActionQuickPick; if (1 === QUICK_PICKS.length) { selectedItem = QUICK_PICKS[0]; } else { selectedItem = await vscode.window.showQuickPick( - QUICK_PICKS, opts + deploy_gui.sortQuickPicksByUsage(QUICK_PICKS, + context.workspaceState, + KEY_PACKAGE_USAGE, + (i) => { + // remove icon + return i.label + .substr(i.label.indexOf(' ')) + .trim(); + }), + opts, ); } if (selectedItem) { - return selectedItem.state; + return selectedItem.action(); } } diff --git a/src/targets.ts b/src/targets.ts index 4b792d9..add890a 100644 --- a/src/targets.ts +++ b/src/targets.ts @@ -15,8 +15,10 @@ * along with this program. If not, see . */ +import * as Crypto from 'crypto'; import * as deploy_contracts from './contracts'; import * as deploy_helpers from './helpers'; +import * as deploy_gui from './gui'; import * as deploy_log from './log'; import * as deploy_mappings from './mappings'; import * as deploy_packages from './packages'; @@ -256,6 +258,9 @@ export interface TargetProvider { readonly targets?: string | string[]; } + +const KEY_TARGET_USAGE = 'vscdrLastExecutedTargetActions'; + /** * The default type or a target operation. */ @@ -652,25 +657,45 @@ export function normalizeTargetType(target: Target): string { : 'local'; // default } +/** + * Resets the target usage statistics. + * + * @param {vscode.ExtensionContext} context The extension context. + */ +export function resetTargetUsage(context: vscode.ExtensionContext) { + context.workspaceState.update(KEY_TARGET_USAGE, undefined).then(() => { + }, (err) => { + deploy_log.CONSOLE + .trace(err, 'targets.resetTargetUsage()'); + }); +} + /** * Shows a quick pick for a list of targets. * + * @param {vscode.ExtensionContext} context The extension context. * @param {Target|Target[]} targets One or more targets. * @param {vscode.QuickPickOptions} [opts] Custom options for the quick picks. * * @return {Promise} The promise that contains the selected target (if selected) * or (false) if no target is available. */ -export async function showTargetQuickPick(targets: Target | Target[], +export async function showTargetQuickPick(context: vscode.ExtensionContext, + targets: Target | Target[], opts?: vscode.QuickPickOptions): Promise { - const QUICK_PICKS: deploy_contracts.ActionQuickPick[] = deploy_helpers.asArray(targets).map(t => { + const QUICK_PICKS: deploy_contracts.ActionQuickPick[] = deploy_helpers.asArray(targets).map(t => { const WORKSPACE = t.__workspace; return { - label: getTargetName(t), + action: () => { + return t; + }, + label: '$(telescope) ' + getTargetName(t), description: deploy_helpers.toStringSafe(t.description), detail: WORKSPACE.rootPath, - state: t, + state: Crypto.createHash('sha256') + .update( new Buffer(deploy_helpers.toStringSafe(t.__id), 'utf8') ) + .digest('hex'), }; }); @@ -682,18 +707,27 @@ export async function showTargetQuickPick(targets: Target | Target[], return false; } - let selectedItem: deploy_contracts.ActionQuickPick; + let selectedItem: deploy_contracts.ActionQuickPick; if (1 === QUICK_PICKS.length) { selectedItem = QUICK_PICKS[0]; } else { selectedItem = await vscode.window.showQuickPick( - QUICK_PICKS, opts + deploy_gui.sortQuickPicksByUsage(QUICK_PICKS, + context.workspaceState, + KEY_TARGET_USAGE, + (i) => { + // remove icon + return i.label + .substr(i.label.indexOf(' ')) + .trim(); + }), + opts, ); } if (selectedItem) { - return selectedItem.state; + return selectedItem.action(); } } diff --git a/src/tools.ts b/src/tools.ts index 91b926b..a9a689c 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -18,6 +18,7 @@ import * as deploy_contracts from './contracts'; import * as deploy_helpers from './helpers'; import * as deploy_html from './html'; +import * as deploy_log from './log'; import * as deploy_packages from './packages'; import * as deploy_targets from './targets'; import * as deploy_workspaces from './workspaces'; @@ -28,6 +29,11 @@ import * as Path from 'path'; import * as vscode from 'vscode'; +/** + * The memento key for the tool usage statistics. + */ +export const KEY_TOOL_USAGE = 'vscdrLastExecutedToolActions'; + /** * Creates a deploy script. * @@ -446,9 +452,11 @@ function pullFiles(args) { /** * Creates a script for a deploy operation. * + * @param {vscode.ExtensionContext} context The extension context. * @param {deploy_workspaces.Workspace|deploy_workspaces.Workspace[]} workspaces One or more workspaces. */ -export async function createDeployOperationScript(workspaces: deploy_workspaces.Workspace | deploy_workspaces.Workspace[]) { +export async function createDeployOperationScript(context: vscode.ExtensionContext, + workspaces: deploy_workspaces.Workspace | deploy_workspaces.Workspace[]) { workspaces = deploy_helpers.asArray(workspaces); const QUICK_PICKS: deploy_contracts.ActionQuickPick[] = workspaces.map(ws => { @@ -637,6 +645,7 @@ exports.execute = function(args) { if (SELECTED_ITEM) { if (1 === SELECTED_ITEM.value) { const selectedTarget = await deploy_targets.showTargetQuickPick( + context, WORKSPACE_TARGETS, { placeHolder: selectedWorkspace.t('tools.createDeployOperationScript.selectTarget') @@ -748,13 +757,29 @@ exports.execute = function(args) { ); } +/** + * Resets the package usage statistics. + * + * @param {vscode.ExtensionContext} context The extension context. + */ +export function resetToolUsage(context: vscode.ExtensionContext) { + context.workspaceState.update(KEY_TOOL_USAGE, undefined).then(() => { + }, (err) => { + deploy_log.CONSOLE + .trace(err, 'tools.resetToolUsage()'); + }); +} + /** * Shows the list of files of a package. * + * @param {context: vscode.ExtensionContext} context The extension context. * @param {deploy_packages.Package|deploy_packages.Package[]} packages The available packages. */ -export async function showPackageFiles(packages: deploy_packages.Package | deploy_packages.Package[]) { +export async function showPackageFiles(context: vscode.ExtensionContext, + packages: deploy_packages.Package | deploy_packages.Package[]) { const PACKAGE = await deploy_packages.showPackageQuickPick( + context, packages, { placeHolder: i18.t('packages.selectPackage'), diff --git a/src/tools/quickexecution.ts b/src/tools/quickexecution.ts index 23f5aac..9c530ac 100644 --- a/src/tools/quickexecution.ts +++ b/src/tools/quickexecution.ts @@ -110,14 +110,23 @@ export async function _1b87f2ee_b636_45b6_807c_0e2d25384b02_1409614337( }; // resolve() - const $res = async (val: any, func: (v: any) => any) => { + const $res = async (val: any, ...funcs: ((v: any) => any)[]) => { val = await $unwrap(val); - - if (func) { - return Promise.resolve( - func(val) - ); + + let lastResult: any = val; + + for (const F of $h.asArray(funcs)) { + let result: any; + if (F) { + result = await Promise.resolve( + F(lastResult) + ); + } + + lastResult = await $unwrap(result); } + + return lastResult; }; // toStringSafe() @@ -152,16 +161,49 @@ export async function _1b87f2ee_b636_45b6_807c_0e2d25384b02_1409614337( return Path.resolve(p); }; - const $guid = async (ver?: string, ...args: any[]) => { + // executeCommand() + const $c = async (id: string, ...cmdArgs: any[]) => { + id = $h.toStringSafe( + await $unwrap(id) + ); + cmdArgs = $h.asArray( + await $unwrap(cmdArgs), + false, + ); + + const ARGS = []; + if (cmdArgs) { + for (const A of cmdArgs) { + ARGS.push( + await $unwrap(A) + ); + } + } + + return await Promise.resolve( + $vs.commands.executeCommand + .apply(null, [ id ].concat( ARGS )) + ); + }; + + const $guid = async (ver?: string, ...guidArgs: any[]) => { const UUID = require('uuid'); ver = $h.normalizeString( await $unwrap(ver) ); - args = $h.asArray( - await $unwrap(args), - false, + guidArgs = $h.normalizeString( + await $unwrap(guidArgs) ); + + const ARGS = []; + if (guidArgs) { + for (const A of guidArgs) { + ARGS.push( + await $unwrap(A) + ); + } + } let func: (...a: any[]) => string; switch (ver) { @@ -183,10 +225,11 @@ export async function _1b87f2ee_b636_45b6_807c_0e2d25384b02_1409614337( } if (!func) { - throw new Error(`UUID version '${ver}' not supported!`); + throw new Error($i18.t('tools.quickExecution.uuid.notSupported', + ver)); } - return func.apply(null, args); + return func.apply(null, ARGS); }; const $hash = async (algo: string, val: any, asBinary?: boolean) => { @@ -414,6 +457,13 @@ function _27adf674_b653_4ee0_a33d_4f60be7859d2() { help += "## Functions\n"; + // $c + help += "### $e\n"; + help += "Executes a Visual Studio Code command.\n"; + help += "```javascript\n"; + help += "$c('editor.action.selectAll')\n"; + help += "```\n"; + help += "\n"; // $e help += "### $e\n"; help += "Executes code.\n"; @@ -489,7 +539,8 @@ function _27adf674_b653_4ee0_a33d_4f60be7859d2() { help += "### $res\n"; help += "Resolves a wrapped value.\n"; help += "```javascript\n"; - help += "$res( $dl.download('https://example.com', (data) => data.toString('utf8')) )\n"; + help += "$res( $dl.download('https://example.com'), (data) => data.toString('utf8') )\n"; + help += "$res( $dl.download('https://example.com'), (data) => data.toString('utf8'), (str) => str.toUpperCase() )\n"; help += "```\n"; help += "\n"; // $rf diff --git a/src/tools/sendfile.ts b/src/tools/sendfile.ts index 3424881..e007d13 100644 --- a/src/tools/sendfile.ts +++ b/src/tools/sendfile.ts @@ -21,6 +21,7 @@ import * as deploy_log from '../log'; import * as i18 from '../i18'; import * as Path from 'path'; import * as Net from 'net'; +import * as SanitizeFilename from 'sanitize-filename'; import * as SimpleSocket from 'node-simple-socket'; import * as vscode from 'vscode'; @@ -146,11 +147,15 @@ export async function receiveFile(context: vscode.ExtensionContext) { try { remoteConnection.readJSON().then((file) => { try { + let fileName = deploy_helpers.toStringSafe(file.name); + let prefix: string; let postfix: string; - if (!deploy_helpers.isEmptyString(file.name)) { - postfix = Path.extname(file.name); - prefix = Path.basename(file.name, postfix) + '-'; + if (!deploy_helpers.isEmptyString(fileName)) { + fileName = SanitizeFilename(fileName); + + postfix = Path.extname(fileName); + prefix = Path.basename(fileName, postfix) + '-'; } deploy_helpers.invokeForTempFile(async (tmpFile) => {