From c8bfca420546b1889835d0d560c5e884e44ea741 Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Tue, 10 Apr 2018 06:30:25 +0200 Subject: [PATCH] global buttons --- CHANGELOG.md | 6 + README.md | 1 + package-lock.json | 2 +- package.json | 55 ++++++- src/buttons.ts | 392 ++++++++++++++++++++++++++++++++++++++++++++++ src/contracts.ts | 5 + src/delete.ts | 35 ++++- src/deploy.ts | 35 ++++- src/extension.ts | 1 + src/i18.ts | 4 + src/lang/de.ts | 4 + src/lang/en.ts | 4 + src/log.ts | 109 ++++++++++++- src/pull.ts | 35 ++++- src/workspaces.ts | 215 ++++--------------------- 15 files changed, 714 insertions(+), 189 deletions(-) create mode 100644 src/buttons.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a4896..32d5774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ [![Share via Facebook](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Facebook.png)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded"e=vscode-deploy-reloaded) [![Share via Twitter](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Twitter.png)](https://twitter.com/intent/tweet?source=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&text=vscode-deploy-reloaded:%20https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&via=mjkloubert) [![Share via Google+](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Google+.png)](https://plus.google.com/share?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) [![Share via Pinterest](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Pinterest.png)](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&media=https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/demo1.gif&description=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.) [![Share via Reddit](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Reddit.png)](https://www.reddit.com/submit?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&title=vscode-deploy-reloaded) [![Share via LinkedIn](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/LinkedIn.png)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&title=vscode-deploy-reloaded&summary=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.&source=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) [![Share via Wordpress](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Wordpress.png)](https://wordpress.com/press-this.php?u=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded"e=vscode-deploy-reloaded&s=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.&i=https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/demo1.gif) [![Share via Email](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Email.png)](mailto:?subject=vscode-deploy-reloaded&body=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20'vs-deploy'%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.:%20https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) +## 0.69.0 (April 10th, 2018; log files) + +* added `Deploy Reloaded: Log files ...` command, which can open an extension's log file now +* can define [global buttons](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/buttons) now, s. [issue #52](https://github.com/mkloubert/vscode-deploy-reloaded/issues/52) +* code cleanups and improvements + ## 0.68.1 (April 9th, 2018; improved output) ![Demo Emoji icon output](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/demo28.gif) diff --git a/README.md b/README.md index f1bc33c..86e5845 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,7 @@ Press `F1` and enter one of the following commands: | `Deploy Reloaded: Delete ...` | Commands for deleting files. | | `Deploy Reloaded: Deploy ...` | List of commands for deploying files. | | `Deploy Reloaded: List directory ...` | Lists a (remote) directory. | +| `Deploy Reloaded: Log files ...` | Opens a log file of that extension. | | `Deploy Reloaded: Proxies ...` | Handles [TCP proxies](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/proxies). | | `Deploy Reloaded: Pull ...` | Pull or download files from remote. | | `Deploy Reloaded: Select deploy operation ...` | Lets the user select an operation for the active document. | diff --git a/package-lock.json b/package-lock.json index c4ee381..5876165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-deploy-reloaded", - "version": "0.68.1", + "version": "0.69.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f0f2775..83abce8 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.68.1", + "version": "0.69.0", "publisher": "mkloubert", "engines": { "vscode": "^1.22.0" @@ -57,6 +57,11 @@ "title": "List directory ...", "category": "Deploy Reloaded" }, + { + "command": "extension.deploy.reloaded.logFiles", + "title": "Log files ...", + "category": "Deploy Reloaded" + }, { "command": "extension.deploy.reloaded.proxies", "title": "Proxies ...", @@ -322,6 +327,54 @@ } } }, + "buttons": { + "type": "array", + "description": "Defines one or more global buttons, which execute a command.", + "items": { + "description": "Settings for a button.", + "type": "object", + "properties": { + "arguments": { + "description": "One or more argument for the underlying command.", + "type": "array" + }, + "color": { + "description": "The (text) color for the button when proxy is running.", + "type": "string", + "default": "button.foreground" + }, + "command": { + "description": "The ID of the command to execute.", + "type": "string" + }, + "enabled": { + "description": "Enable button or not.", + "type": "boolean", + "default": true + }, + "isRight": { + "description": "Put button on the right side or not.", + "type": "boolean", + "default": false + }, + "priority": { + "description": "The priority.", + "type": "integer" + }, + "text": { + "description": "The custom display text.", + "type": "string" + }, + "tooltip": { + "description": "The custom tooltip.", + "type": "string" + } + }, + "required": [ + "command" + ] + } + }, "checkForRequirements": { "description": "Defines one or more conditions by (JavaScript) code.", "type": "array", diff --git a/src/buttons.ts b/src/buttons.ts new file mode 100644 index 0000000..20a1932 --- /dev/null +++ b/src/buttons.ts @@ -0,0 +1,392 @@ +/** + * 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 deploy_workspaces from './workspaces'; +import * as vscode from 'vscode'; + + +/** + * Description for a global button. + */ +export interface Button extends deploy_contracts.ButtonWithCustomCommand { + /** + * One or more arguments for the underlying command of that button. + */ + readonly arguments?: any[]; +} + +interface FinishedButton extends vscode.Disposable { + readonly button: vscode.StatusBarItem; + readonly command: vscode.Disposable; +} + +interface GlobalButton extends vscode.Disposable { + readonly button: vscode.StatusBarItem; + readonly command: vscode.Disposable; +} + + +const KEY_FINISHED_BTNS = 'finished_buttons'; +const KEY_FINISHED_BTN_DELETE = 'finish_delete'; +const KEY_FINISHED_BTN_DEPLOY = 'finish_deploy'; +const KEY_FINISHED_BTN_PULL = 'finish_pull'; +let nextFinishedBtnIds = Number.MIN_SAFE_INTEGER; +let nextGlobalBtnId = Number.MIN_SAFE_INTEGER; + +function createFinishedButton(state: deploy_contracts.KeyValuePairs, key: string, id: number): FinishedButton { + const WORKSPACE: deploy_workspaces.Workspace = this; + + let btn: vscode.StatusBarItem; + let cmd: vscode.Disposable; + try { + btn = vscode.window.createStatusBarItem(); + btn.hide(); + + const CMD_ID = `extension.deploy.reloaded.buttons.finishedButtons.${key}${id}`; + cmd = vscode.commands.registerCommand(CMD_ID, () => { + WORKSPACE.output.show(); + + btn.hide(); + }); + + btn.command = CMD_ID; + } + catch (e) { + deploy_helpers.tryDispose(btn); + deploy_helpers.tryDispose(cmd); + + throw e; + } + + return { + button: btn, + command: cmd, + dispose: function() { + const BUTTONS = state['buttons']; + + let timeouts: deploy_contracts.KeyValuePairs; + if (state['timeouts']) { + timeouts = state['timeouts'][ KEY_FINISHED_BTNS ]; + } + + deploy_helpers.tryDispose( this.button ); + deploy_helpers.tryDispose( this.command ); + + if (BUTTONS) { + delete BUTTONS[ key ]; + } + + if (timeouts) { + deploy_helpers.tryDispose( timeouts[key] ); + + delete timeouts[ key ]; + } + } + }; +} + +/** + * Disposes all global buttons of the underlying workspace. + */ +export function disposeButtons() { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + const BUTTONS: deploy_contracts.KeyValuePairs = STATE['buttons']; + if (!BUTTONS) { + return; + } + + const GLOBAL_BUTTONS: GlobalButton[] = deploy_helpers.asArray( BUTTONS['global'] ); + for (const GB of GLOBAL_BUTTONS) { + deploy_helpers.tryDispose(GB); + } + + delete BUTTONS['global']; +} + +/** + * Disposes the "finished buttons" of the underlying workspace. + */ +export function disposeFinishedButtons() { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + const BUTTONS = STATE['buttons']; + if (!BUTTONS) { + return; + } + + const KEYS = [ + KEY_FINISHED_BTN_DEPLOY, + KEY_FINISHED_BTN_PULL, + KEY_FINISHED_BTN_DELETE, + ]; + + for (const K of KEYS) { + deploy_helpers.tryDispose(BUTTONS[ K ]); + } +} + +/** + * Returns a status bar button of the underlying workspace, that shows if a deploy operation has been finished. + * + * @param {deploy_contracts.DeployOperation} operation The operation type. + * + * @return {vscode.StatusBarItem} The button. + */ +export function getFinishedButton(operation: deploy_contracts.DeployOperation): vscode.StatusBarItem { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const STATE = WORKSPACE.workspaceSessionState; + if (STATE) { + const BUTTONS = STATE['buttons']; + if (BUTTONS) { + let btn: FinishedButton; + + switch (operation) { + case deploy_contracts.DeployOperation.Deploy: + btn = BUTTONS[ KEY_FINISHED_BTN_DEPLOY ]; + break; + + case deploy_contracts.DeployOperation.Pull: + btn = BUTTONS[ KEY_FINISHED_BTN_PULL ]; + break; + + case deploy_contracts.DeployOperation.Delete: + btn = BUTTONS[ KEY_FINISHED_BTN_DELETE ]; + break; + } + + if (btn) { + return btn.button; + } + } + } +} + +/** + * Initializes the "finished button" for the underlying workspace. + * + * @param {deploy_contracts.KeyValuePairs} state The new session state object of the workspace. + */ +export function initFinishedButtons(state: deploy_contracts.KeyValuePairs) { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const ID = nextFinishedBtnIds++; + + // dispose old + deploy_helpers.applyFuncFor( + disposeFinishedButtons, WORKSPACE + )(); + + state['timeouts'][ KEY_FINISHED_BTNS ] = {}; + + // deploy + state['buttons'][KEY_FINISHED_BTN_DEPLOY] = deploy_helpers.applyFuncFor( + createFinishedButton, WORKSPACE, + )(state, KEY_FINISHED_BTN_DEPLOY, ID); + + // delete + state['buttons'][KEY_FINISHED_BTN_DELETE] = deploy_helpers.applyFuncFor( + createFinishedButton, WORKSPACE, + )(state, KEY_FINISHED_BTN_DELETE, ID); + + // pull + state['buttons'][KEY_FINISHED_BTN_PULL] = deploy_helpers.applyFuncFor( + createFinishedButton, WORKSPACE, + )(state, KEY_FINISHED_BTN_PULL, ID); +} + +/** + * Reloads global buttons for the underlying workspace. + */ +export async function reloadButtons() { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const CFG = WORKSPACE.config; + if (!CFG) { + return; + } + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + let buttons = STATE['buttons']; + if (!buttons) { + STATE['buttons'] = buttons = {}; + } + + const NEW_BUTTONS: GlobalButton[] = []; + + await deploy_helpers.forEachAsync(deploy_helpers.asArray(CFG.buttons), async (b, i) => { + if (!deploy_helpers.toBooleanSafe(b.enabled, true)) { + return; + } + + const ID = nextGlobalBtnId++; + + let btn: vscode.StatusBarItem; + let cmd: vscode.Disposable; + try { + btn = await deploy_helpers.createButton(b, async (nb, btnDesc) => { + const REAL_CMD = deploy_helpers.toStringSafe(btnDesc.command).trim(); + + nb.text = WORKSPACE.replaceWithValues(btnDesc.text); + if (deploy_helpers.isEmptyString(nb.text)) { + nb.text = REAL_CMD; + } + + nb.tooltip = WORKSPACE.replaceWithValues(btnDesc.tooltip); + if (deploy_helpers.isEmptyString(nb.tooltip)) { + nb.tooltip = undefined; + } + + const GET_ARGUMENTS = async () => { + return deploy_helpers.asArray( btnDesc.arguments, false ); + }; + + const BTN_CMD_ID = `extension.deploy.reloaded.buttons.globalButtons${ID}`; + cmd = vscode.commands.registerCommand(BTN_CMD_ID, async function() { + try { + return await Promise.resolve( + vscode.commands.executeCommand + .apply(null, [ REAL_CMD ].concat( await GET_ARGUMENTS() )), + ); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, `${BTN_CMD_ID}(${i}.'${REAL_CMD}')`); + + WORKSPACE.showErrorMessage( + WORKSPACE.t('error', e) + ); + } + }); + + nb.command = BTN_CMD_ID; + }); + + NEW_BUTTONS.push({ + button: btn, + command: cmd, + dispose: function() { + deploy_helpers.tryDispose( this.button ); + deploy_helpers.tryDispose( this.command ); + } + }); + + btn.show(); + } + catch (e) { + deploy_helpers.tryDispose( btn ); + deploy_helpers.tryDispose( cmd ); + + deploy_log.CONSOLE + .trace(e, 'buttons.reloadButtons(1)'); + } + }); + + buttons['global'] = NEW_BUTTONS; +} + +/** + * Sets a timeout for a "finished button" of the underlying workspace. + * + * @param {deploy_contracts.DeployOperation} operation The type of deploy operation. + * @param {Function} callback The callback. + * @param {number} [ms] The custom number of milliseconds. + * + * @return {boolean} Operation was successful or not. + */ +export function setTimeoutForFinishedButton( + operation: deploy_contracts.DeployOperation, + callback: (btn: vscode.StatusBarItem) => any, + ms = 60000, +) { + const WORKSPACE: deploy_workspaces.Workspace = this; + + const STATE = WORKSPACE.workspaceSessionState; + if (STATE) { + let timeouts: deploy_contracts.KeyValuePairs; + if (STATE['timeouts']) { + timeouts = STATE['timeouts'][ KEY_FINISHED_BTNS ]; + } + + if (timeouts) { + let key: string | false = false; + + switch (operation) { + case deploy_contracts.DeployOperation.Deploy: + key = KEY_FINISHED_BTN_DEPLOY; + break; + + case deploy_contracts.DeployOperation.Pull: + key = KEY_FINISHED_BTN_PULL; + break; + + case deploy_contracts.DeployOperation.Delete: + key = KEY_FINISHED_BTN_DELETE; + break; + } + + if (false !== key) { + deploy_helpers.tryDispose( timeouts[key] ); + delete timeouts[key]; + + const BTN = deploy_helpers.applyFuncFor( + getFinishedButton, WORKSPACE + )(operation); + + if (BTN) { + timeouts[key] = deploy_helpers.createTimeout(() => { + try { + if (callback) { + Promise.resolve( callback(BTN) ).then(() => { + }, (err) => { + WORKSPACE.logger + .trace(err, 'buttons.setTimeoutForFinishedButton(2)'); + }); + } + } + catch (e) { + WORKSPACE.logger + .trace(e, 'buttons.setTimeoutForFinishedButton(1)'); + } + }, ms); + + return true; + } + } + } + } + + return false; +} diff --git a/src/contracts.ts b/src/contracts.ts index a267da7..744f0bb 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -16,6 +16,7 @@ */ import * as deploy_api from './api'; +import * as deploy_buttons from './buttons'; import * as deploy_commands from './commands'; import * as deploy_gui from './gui'; import * as deploy_log from './log'; @@ -154,6 +155,10 @@ export interface Configuration extends deploy_packages.WithFastFileCheckSettings * Defines of or more command for the editor to register. */ readonly commands?: { [command: string]: deploy_commands.ScriptCommand | string }; + /** + * A list of one or more global buttons. + */ + readonly buttons?: deploy_buttons.Button | deploy_buttons.Button[]; /** * Activates or deactivates 'deploy on change' feature for all packages. */ diff --git a/src/delete.ts b/src/delete.ts index 97c118f..bb000ec 100644 --- a/src/delete.ts +++ b/src/delete.ts @@ -174,7 +174,7 @@ export async function deleteFilesIn(files: string[], }, { location: vscode.ProgressLocation.Notification, cancellable: true, - title: ME.t('DELETE.deletingFiles'), + title: `🗑️ ` + ME.t('DELETE.deletingFiles'), }); } @@ -818,5 +818,38 @@ export function registerDeleteCommands(context: vscode.ExtensionContext) { ); } }), + + // delete package files + vscode.commands.registerCommand('extension.deploy.reloaded.deletePackageFiles', async (packageNames: string | string[], targetNames: string | string[]) => { + packageNames = deploy_helpers.asArray( packageNames ).map(p => { + return deploy_helpers.normalizeString(p); + }).filter(p => '' !== p); + + targetNames = deploy_helpers.asArray( targetNames ).map(t => { + return deploy_helpers.normalizeString(t); + }).filter(t => '' !== t); + + const ALL_WORKSPACES = deploy_workspaces.getAllWorkspaces(); + + for (const PN of packageNames) { + const MATCHING_PACKAGES = deploy_helpers.from( ALL_WORKSPACES ).selectMany(ws => { + return ws.getPackages(); + }).where(p => { + return PN === deploy_helpers.normalizeString(p.name); + }); + + for (const MP of MATCHING_PACKAGES) { + try { + await deploy_helpers.applyFuncFor( + deletePackage, MP.__workspace + )(MP, () => targetNames); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'extension.deploy.reloaded.deletePackageFiles(1)'); + } + } + } + }), ); } diff --git a/src/deploy.ts b/src/deploy.ts index e09c6f5..a8c9422 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -419,7 +419,7 @@ export async function deployFilesTo(files: string[], }, { location: vscode.ProgressLocation.Notification, cancellable: true, - title: ME.t('deploy.deployingFiles'), + title: `🚀 ` + ME.t('deploy.deployingFiles'), }); } @@ -1450,6 +1450,39 @@ export function registerDeployCommands(context: vscode.ExtensionContext) { ); } }), + + // deploy package files + vscode.commands.registerCommand('extension.deploy.reloaded.deployPackageFiles', async (packageNames: string | string[], targetNames: string | string[]) => { + packageNames = deploy_helpers.asArray( packageNames ).map(p => { + return deploy_helpers.normalizeString(p); + }).filter(p => '' !== p); + + targetNames = deploy_helpers.asArray( targetNames ).map(t => { + return deploy_helpers.normalizeString(t); + }).filter(t => '' !== t); + + const ALL_WORKSPACES = deploy_workspaces.getAllWorkspaces(); + + for (const PN of packageNames) { + const MATCHING_PACKAGES = deploy_helpers.from( ALL_WORKSPACES ).selectMany(ws => { + return ws.getPackages(); + }).where(p => { + return PN === deploy_helpers.normalizeString(p.name); + }); + + for (const MP of MATCHING_PACKAGES) { + try { + await deploy_helpers.applyFuncFor( + deployPackage, MP.__workspace + )(MP, () => targetNames); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'extension.deploy.reloaded.deployPackageFiles(1)'); + } + } + } + }), ); } diff --git a/src/extension.ts b/src/extension.ts index 3ea35d0..5afdf6e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -949,6 +949,7 @@ async function activateExtension(context: vscode.ExtensionContext) { }), ); + deploy_log.registerLogCommands(context); deploy_deploy.registerDeployCommands(context); deploy_pull.registerPullCommands(context); deploy_delete.registerDeleteCommands(context); diff --git a/src/i18.ts b/src/i18.ts index ba2de75..3b1fd3d 100644 --- a/src/i18.ts +++ b/src/i18.ts @@ -311,6 +311,10 @@ export interface Translation { selectSource?: string; size?: string; }; + log?: { + noFileFound?: string; + selectLogFile?: string; + }; maxDepthReached?: string; network?: { hostname?: string; diff --git a/src/lang/de.ts b/src/lang/de.ts index 6230976..62053f6 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -306,6 +306,10 @@ export const translation: Translation = { selectSource: "Wählen Sie eine Quelle ...", size: "Grösse:{0:trim,leading_space}", }, + log: { + noFileFound: "Keine Log-Datei gefunden!", + selectLogFile: "Wählen Sie die Log-Datei, die geöffnet werden soll ...", + }, maxDepthReached: "Maximale Tiefe von{0:trim,leading_space} erreicht!", network: { hostname: 'Name dieses Computers:{0:trim,surround,leading_space}', diff --git a/src/lang/en.ts b/src/lang/en.ts index dd60c21..361521e 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -306,6 +306,10 @@ export const translation: Translation = { selectSource: "Select the source from where to start listening ...", size: "Size:{0:trim,leading_space}", }, + log: { + noFileFound: "No log files found!", + selectLogFile: "Select the log file, that should be opened ...", + }, maxDepthReached: "Cannot go deeper than{0:trim,leading_space}!", network: { hostname: 'Your hostname:{0:trim,surround,leading_space}', diff --git a/src/log.ts b/src/log.ts index cca3cee..cef7fb9 100644 --- a/src/log.ts +++ b/src/log.ts @@ -16,16 +16,24 @@ */ import * as _ from 'lodash'; +import * as deploy_contracts from './contracts'; import * as deploy_helpers from './helpers'; import * as FS from 'fs'; import * as FSExtra from 'fs-extra'; +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'; import * as vscode_helpers from 'vscode-helpers'; export { ActionLogger, Logger } from 'vscode-helpers'; +interface LogFile { + mtime: Moment.Moment; + path: string; +} + const NEW_CONSOLE_LOGGER = vscode_helpers.createLogger(); // write to console NEW_CONSOLE_LOGGER.addAction((ctx) => { @@ -158,7 +166,7 @@ export async function cleanupLogFilesInHomeDirectory(maxLifeTime?: number) { const LOG_FILES = await deploy_helpers.glob('/*.log', { cwd: LOGS_DIR, - root: LOGS_DIR, + root: LOGS_DIR, }); for (const LF of LOG_FILES) { @@ -179,3 +187,102 @@ export async function cleanupLogFilesInHomeDirectory(maxLifeTime?: number) { catch (e) { /* ignore */ } } } + +async function openLogFile() { + let logFilePaths: string[]; + + const LOGS_DIR = deploy_helpers.getExtensionLogDirInHome(); + if (await deploy_helpers.isDirectory(LOGS_DIR)) { + logFilePaths = await deploy_helpers.glob('/*.log', { + cwd: LOGS_DIR, + root: LOGS_DIR, + }); + } + + logFilePaths = deploy_helpers.asArray(logFilePaths); + + let logFiles: LogFile[] = []; + for (const LFP of logFilePaths) { + try { + const STATS = await FSExtra.lstat(LFP); + + const F: LogFile = { + mtime: deploy_helpers.asUTC( STATS.mtime ), + path: LFP, + }; + + logFiles.push(F); + } + catch (e) { + CONSOLE.trace(e, 'log.openLogFile(1)'); + } + } + + logFiles = deploy_helpers.from(logFiles).orderByDescending(lf => { + return lf.mtime.unix(); + }).thenBy(lf => { + return deploy_helpers.normalizeString( Path.basename(lf.path) ); + }).thenBy(lf => { + return deploy_helpers.normalizeString( Path.dirname(lf.path) ); + }).toArray(); + + const ITEMS: deploy_contracts.ActionQuickPick[] = logFiles.map(lf => { + return { + action: async () => { + await vscode.window.showTextDocument( + await vscode.workspace.openTextDocument(lf.path) + ); + }, + description: lf.mtime.format( i18.t('time.dateTimeWithSeconds') ), + detail: Path.dirname(lf.path), + label: '$(file-text) ' + Path.basename(lf.path), + }; + }); + + if (ITEMS.length < 1) { + deploy_helpers.showWarningMessage( + i18.t('log.noFileFound'), + ); + + return; + } + + let selectedItem: deploy_contracts.ActionQuickPick; + if (1 === ITEMS.length) { + selectedItem = ITEMS[0]; + } + else { + selectedItem = await vscode.window.showQuickPick( + ITEMS, + { + placeHolder: i18.t('log.selectLogFile'), + } + ); + } + + if (selectedItem) { + await selectedItem.action(); + } +} + +/** + * Registers commands for log (file) operations. + * + * @param {vscode.ExtensionContext} context The extension context. + */ +export function registerLogCommands(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand('extension.deploy.reloaded.logFiles', async () => { + try { + await openLogFile(); + } + catch (e) { + CONSOLE.trace(e, 'extension.deploy.reloaded.logFiles'); + + deploy_helpers.showErrorMessage( + i18.t('error', e) + ); + } + }), + ); +} diff --git a/src/pull.ts b/src/pull.ts index 1f4d9af..331e252 100644 --- a/src/pull.ts +++ b/src/pull.ts @@ -436,7 +436,7 @@ export async function pullFilesFrom(files: string[], }, { location: vscode.ProgressLocation.Notification, cancellable: true, - title: ME.t('pull.pullingFiles'), + title: `🚚 ` + ME.t('pull.pullingFiles'), }); } @@ -1161,5 +1161,38 @@ export function registerPullCommands(context: vscode.ExtensionContext) { ); } }), + + // pull package files + vscode.commands.registerCommand('extension.deploy.reloaded.pullPackgeFiles', async (packageNames: string | string[], sourceNames: string | string[]) => { + packageNames = deploy_helpers.asArray( packageNames ).map(p => { + return deploy_helpers.normalizeString(p); + }).filter(p => '' !== p); + + sourceNames = deploy_helpers.asArray( sourceNames ).map(s => { + return deploy_helpers.normalizeString(s); + }).filter(s => '' !== s); + + const ALL_WORKSPACES = deploy_workspaces.getAllWorkspaces(); + + for (const PN of packageNames) { + const MATCHING_PACKAGES = deploy_helpers.from( ALL_WORKSPACES ).selectMany(ws => { + return ws.getPackages(); + }).where(p => { + return PN === deploy_helpers.normalizeString(p.name); + }); + + for (const MP of MATCHING_PACKAGES) { + try { + await deploy_helpers.applyFuncFor( + pullPackage, MP.__workspace + )(MP, () => sourceNames); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'extension.deploy.reloaded.pullPackgeFiles(1)'); + } + } + } + }), ); } diff --git a/src/workspaces.ts b/src/workspaces.ts index 670bdc2..c00bd31 100644 --- a/src/workspaces.ts +++ b/src/workspaces.ts @@ -20,6 +20,7 @@ import * as _ from 'lodash'; import * as ChildProcess from 'child_process'; import * as Crypto from 'crypto'; import * as deploy_api from './api'; +import * as deploy_buttons from './buttons'; import * as deploy_code from './code'; import * as deploy_commands from './commands'; import * as deploy_contracts from './contracts'; @@ -73,11 +74,6 @@ export interface DeactivateAutoDeployOperationsForOptions { readonly noRemoveOnChange?: boolean; } -interface FinishedButton extends vscode.Disposable { - readonly button: vscode.StatusBarItem; - readonly command: vscode.Disposable; -} - interface PackageWithButton { readonly button: vscode.StatusBarItem; readonly command: vscode.Disposable; @@ -301,12 +297,7 @@ export interface WorkspaceSettings extends deploy_contracts.Configuration { let activeWorkspaceProvider: WorkspaceProvider; let allWorkspacesProvider: WorkspaceProvider; const FILES_CHANGES: { [path: string]: deploy_contracts.FileChangeType } = {}; -const KEY_FINISHED_BTNS = 'finished_buttons'; -const KEY_FINISHED_BTN_DELETE = 'finish_delete'; -const KEY_FINISHED_BTN_DEPLOY = 'finish_deploy'; -const KEY_FINISHED_BTN_PULL = 'finish_pull'; const KEY_WORKSPACE_USAGE = 'vscdrLastExecutedWorkspaceActions'; -let nextFinishedBtnIds = Number.MIN_SAFE_INTEGER; let nextPackageButtonId = Number.MIN_SAFE_INTEGER; let nextTcpProxyButtonId = Number.MIN_SAFE_INTEGER; let nextSwitchButtonId = Number.MIN_SAFE_INTEGER; @@ -717,58 +708,6 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co return this._configSource; } - private createFinishedButton(state: deploy_contracts.KeyValuePairs, key: string, id: number): FinishedButton { - const ME = this; - - let btn: vscode.StatusBarItem; - let cmd: vscode.Disposable; - try { - btn = vscode.window.createStatusBarItem(); - btn.hide(); - - const CMD_ID = `extension.deploy.reloaded.buttons.finishedButtons.${key}${id}`; - cmd = vscode.commands.registerCommand(CMD_ID, () => { - ME.output.show(); - - btn.hide(); - }); - - btn.command = CMD_ID; - } - catch (e) { - deploy_helpers.tryDispose(btn); - deploy_helpers.tryDispose(cmd); - - throw e; - } - - return { - button: btn, - command: cmd, - dispose: function() { - const BUTTONS = state['buttons']; - - let timeouts: deploy_contracts.KeyValuePairs; - if (state['timeouts']) { - timeouts = state['timeouts'][ KEY_FINISHED_BTNS ]; - } - - deploy_helpers.tryDispose( this.button ); - deploy_helpers.tryDispose( this.command ); - - if (BUTTONS) { - delete BUTTONS[ key ]; - } - - if (timeouts) { - deploy_helpers.tryDispose( timeouts[key] ); - - delete timeouts[ key ]; - } - } - }; - } - /** * Creates a new git client (if possible). * @@ -856,7 +795,9 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co deploy_targets.KEY_TARGETS_IN_PROGRESS ] = {}; - this.initFinishedButtons(NEW_SESSION_STATE); + deploy_helpers.applyFuncFor( + deploy_buttons.initFinishedButtons, this + )(NEW_SESSION_STATE); return NEW_SESSION_STATE; } @@ -1059,28 +1000,6 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co } } - private disposeFinishedButtons() { - const STATE = this.workspaceSessionState; - if (!STATE) { - return; - } - - const BUTTONS = STATE['buttons']; - if (!BUTTONS) { - return; - } - - const KEYS = [ - KEY_FINISHED_BTN_DEPLOY, - KEY_FINISHED_BTN_PULL, - KEY_FINISHED_BTN_DELETE, - ]; - - for (const K of KEYS) { - deploy_helpers.tryDispose(BUTTONS[ K ]); - } - } - private disposeTcpProxies() { // loggers while (this._TCP_PROXY_LOGGERS.length > 0) { @@ -1551,31 +1470,9 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co * @return {vscode.StatusBarItem} The button. */ public getFinishedButton(operation: deploy_contracts.DeployOperation): vscode.StatusBarItem { - const STATE = this.workspaceSessionState; - if (STATE) { - const BUTTONS = STATE['buttons']; - if (BUTTONS) { - let btn: FinishedButton; - - switch (operation) { - case deploy_contracts.DeployOperation.Deploy: - btn = BUTTONS[ KEY_FINISHED_BTN_DEPLOY ]; - break; - - case deploy_contracts.DeployOperation.Pull: - btn = BUTTONS[ KEY_FINISHED_BTN_PULL ]; - break; - - case deploy_contracts.DeployOperation.Delete: - btn = BUTTONS[ KEY_FINISHED_BTN_DELETE ]; - break; - } - - if (btn) { - return btn.button; - } - } - } + return deploy_helpers.applyFuncFor( + deploy_buttons.getFinishedButton, this + )(operation); } /** @@ -2221,24 +2118,6 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co }); } - private initFinishedButtons(state: deploy_contracts.KeyValuePairs) { - const ID = nextFinishedBtnIds++; - - this.disposeFinishedButtons(); - - state['timeouts'][ KEY_FINISHED_BTNS ] = {}; - - const DEPLOY_BTN: FinishedButton = state['buttons'][KEY_FINISHED_BTN_DEPLOY] = this.createFinishedButton( - state, KEY_FINISHED_BTN_DEPLOY, ID - ); - const DELETE_BTN: FinishedButton = state['buttons'][KEY_FINISHED_BTN_DELETE] = this.createFinishedButton( - state, KEY_FINISHED_BTN_DELETE, ID - ); - const PULL_BTN: FinishedButton = state['buttons'][KEY_FINISHED_BTN_PULL] = this.createFinishedButton( - state, KEY_FINISHED_BTN_PULL, ID - ); - } - /** * Initializes that workspace. * @@ -3012,7 +2891,13 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co this.disposeConfigFileWatchers(); deploy_helpers.tryDispose(this.context.fileWatcher); - this.disposeFinishedButtons(); + // dispose buttons + deploy_helpers.applyFuncFor( + deploy_buttons.disposeButtons, this + )(); + deploy_helpers.applyFuncFor( + deploy_buttons.disposeFinishedButtons, this + )(); // output channel deploy_helpers.tryDispose(this._OUTPUT_CHANNEL); @@ -3194,6 +3079,11 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME._isReloadingConfig = true; this.disposeConfigFileWatchers(); + // dispose global buttons + deploy_helpers.applyFuncFor( + deploy_buttons.disposeButtons, ME + )(); + const SCOPES = ME.getSettingScopes(); let finalizer: () => any; @@ -3377,6 +3267,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.output.appendLine(''); ME.output.appendLine( + `💤 ` + ME.t('deploy.onChange.waitingBeforeActivate', Math.round(TIME_TO_WAIT_BEFORE_ACTIVATE_DEPLOY_ON_CHANGE / 1000.0), ME.rootPath) @@ -3388,6 +3279,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.output.appendLine(''); ME.output.appendLine( + `▶️ `+ ME.t('deploy.onChange.activated', ME.rootPath) ); @@ -3415,6 +3307,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.output.appendLine(''); ME.output.appendLine( + `💤 ` + ME.t('DELETE.onChange.waitingBeforeActivate', Math.round(TIME_TO_WAIT_BEFORE_ACTIVATE_REMOVE_ON_CHANGE / 1000.0), ME.rootPath) @@ -3426,6 +3319,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.output.appendLine(''); ME.output.appendLine( + `▶️ `+ ME.t('DELETE.onChange.activated', ME.rootPath) ); @@ -3455,6 +3349,11 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co await ME.reloadTcpProxies(); + // global buttons + await deploy_helpers.applyFuncFor( + deploy_buttons.reloadButtons, ME, + )(); + await ME.initConfigFileWatchers(IMPORTED_LOCAL_FILES); }; } @@ -4352,60 +4251,10 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co callback: (btn: vscode.StatusBarItem) => any, ms = 60000, ) { - const ME = this; - - const STATE = ME.workspaceSessionState; - if (STATE) { - let timeouts: deploy_contracts.KeyValuePairs; - if (STATE['timeouts']) { - timeouts = STATE['timeouts'][ KEY_FINISHED_BTNS ]; - } - - if (timeouts) { - let key: string | false = false; - - switch (operation) { - case deploy_contracts.DeployOperation.Deploy: - key = KEY_FINISHED_BTN_DEPLOY; - break; - - case deploy_contracts.DeployOperation.Pull: - key = KEY_FINISHED_BTN_PULL; - break; - - case deploy_contracts.DeployOperation.Delete: - key = KEY_FINISHED_BTN_DELETE; - break; - } - - if (false !== key) { - deploy_helpers.tryDispose( timeouts[key] ); - - const BTN = ME.getFinishedButton(operation); - if (BTN) { - timeouts[key] = deploy_helpers.createTimeout(() => { - try { - if (callback) { - Promise.resolve( callback(BTN) ).then(() => { - }, (err) => { - ME.logger - .trace(err, 'workspaces.Workspace.setTimeoutForFinishedButton(2)'); - }); - } - } - catch (e) { - ME.logger - .trace(e, 'workspaces.Workspace.setTimeoutForFinishedButton(1)'); - } - }, ms); - - return true; - } - } - } - } - - return false; + return deploy_helpers.applyFuncFor( + deploy_buttons.setTimeoutForFinishedButton, this + )(operation, + callback, ms); } /**