diff --git a/src/client/RPCClient.ts b/src/client/RPCClient.ts index dba3edc..91f1a38 100644 --- a/src/client/RPCClient.ts +++ b/src/client/RPCClient.ts @@ -1,22 +1,28 @@ const { Client } = require('discord-rpc'); import { Disposable, + StatusBarItem, + window, workspace } from 'vscode'; -import Acivity from '../structures/Activity'; +import Activity from '../structures/Activity'; import Logger from '../structures/Logger'; +let activityTimer: NodeJS.Timer; + export default class RPCClient implements Disposable { - private _rpc: any = new Client({ transport: 'ipc' }); + public statusBarIcon: StatusBarItem; + public config = workspace.getConfiguration('discord'); - private _activity = new Acivity(); + private _rpc: any; - private _config = workspace.getConfiguration('discord'); + private _activity = new Activity(); private _clientId: string; - public constructor(clientId: string) { + public constructor(clientId: string, statusBarIcon: StatusBarItem) { this._clientId = clientId; + this.statusBarIcon = statusBarIcon; } public get client() { @@ -31,12 +37,41 @@ export default class RPCClient implements Disposable { } public async login() { + if (this._rpc) return; + this._rpc = new Client({ transport: 'ipc' }); Logger.log('Logging into RPC.'); - return this._rpc.login({ clientId: this._clientId }); + this._rpc.once('ready', () => { + Logger.log('Successfully connected to Discord.'); + if (!this.config.get('silent')) window.showInformationMessage('Successfully connected to Discord RPC'); + + this.statusBarIcon.hide(); + + this.statusBarIcon.text = '$(plug) Reconnect to Discord'; + this.statusBarIcon.command = 'discord.reconnect'; + + if (activityTimer) clearInterval(activityTimer); + this.setActivity(); + + this._rpc.transport.once('close', async () => { + if (!this.config.get('enabled')) return; + await this.dispose(); + this.statusBarIcon.show(); + }); + + activityTimer = setInterval(() => { + this.config = workspace.getConfiguration('discord'); + this.setActivity(this.config.get('workspaceElapsedTime')); + }, 10000); + }); + await this._rpc.login({ clientId: this._clientId }); } public async dispose() { this._activity.dispose(); - await this._rpc.destroy(); + if (this._rpc) { + await this._rpc.destroy(); + this._rpc = null; + } + clearInterval(activityTimer); } } diff --git a/src/constants.ts b/src/constants.ts index cdef67c..08233b4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,4 +5,4 @@ export const LIVE_SHARE_COMMANDS = { START: 'liveshare.start', END: 'liveshare.end', JOIN: 'liveshare.join' -} +}; diff --git a/src/extension.ts b/src/extension.ts index 5b8016f..d6323ee 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,50 +1,26 @@ -import RPCClient from './client/RPCClient'; -import Logger from './structures/Logger'; import { commands, ExtensionContext, - StatusBarItem, StatusBarAlignment, + StatusBarItem, window, workspace } from 'vscode'; -import { setInterval, clearInterval } from 'timers'; +import RPCClient from './client/RPCClient'; +import Logger from './structures/Logger'; -let activityTimer: NodeJS.Timer; -let statusBarIcon: StatusBarItem; +const statusBarIcon: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); +statusBarIcon.text = '$(pulse) Connecting...'; +statusBarIcon.command = 'discord.reconnect'; const config = workspace.getConfiguration('discord'); -const rpc = new RPCClient(config.get('clientID')!); +const rpc = new RPCClient(config.get('clientID')!, statusBarIcon); export async function activate(context: ExtensionContext) { Logger.log('Discord Presence activated!'); - - rpc.client.once('ready', () => { - Logger.log('Successfully connected to Discord.'); - if (!config.get('silent')) window.showInformationMessage('Successfully reconnected to Discord RPC'); - if (statusBarIcon) statusBarIcon.dispose(); - if (activityTimer) clearInterval(activityTimer); - rpc.setActivity(); - - rpc.client.transport.once('close', async () => { - if (!config.get('enabled')) return; - await rpc.dispose(); - await rpc.login(); - if (!statusBarIcon) { - statusBarIcon = window.createStatusBarItem(StatusBarAlignment.Left); - statusBarIcon.text = '$(plug) Reconnect to Discord'; - statusBarIcon.command = 'discord.reconnect'; - statusBarIcon.show(); - } - }); - - activityTimer = setInterval(() => { - rpc.setActivity(config.get('workspaceElapsedTime')); - }, 10000); - }) - if (config.get('enabled')) { + statusBarIcon.show(); try { await rpc.login(); } catch (error) { @@ -53,24 +29,25 @@ export async function activate(context: ExtensionContext) { if (error.message.includes('ENOENT')) window.showErrorMessage('No Discord Client detected!'); else window.showErrorMessage(`Couldn't connect to Discord via RPC: ${error.toString()}`); } - if (!statusBarIcon) { - statusBarIcon = window.createStatusBarItem(StatusBarAlignment.Left); - statusBarIcon.text = '$(plug) Reconnect to Discord'; - statusBarIcon.command = 'discord.reconnect'; - statusBarIcon.show(); - } + rpc.statusBarIcon.text = '$(pulse) Reconnect'; + rpc.statusBarIcon.command = 'discord.reconnect'; + rpc.statusBarIcon.show(); } } const enabler = commands.registerCommand('discord.enable', async () => { await rpc.dispose(); await config.update('enabled', true); + rpc._config = workspace.getConfiguration('discord'); + rpc.statusBarIcon.text = '$(pulse) Connecting...'; + rpc.statusBarIcon.show(); await rpc.login(); window.showInformationMessage('Enabled Discord Rich Presence for this workspace.'); }); const disabler = commands.registerCommand('discord.disable', async () => { await config.update('enabled', false); + rpc.config = workspace.getConfiguration('discord'); await rpc.dispose(); window.showInformationMessage('Disabled Discord Rich Presence for this workspace.'); }); @@ -79,14 +56,14 @@ export async function activate(context: ExtensionContext) { await rpc.dispose(); await rpc.login(); if (!config.get('silent')) window.showInformationMessage('Reconnecting to Discord RPC...'); - if (statusBarIcon) statusBarIcon.text = '$(pulse) reconnecting...'; + rpc.statusBarIcon.text = '$(pulse) Reconnecting...'; + rpc.statusBarIcon.command = undefined; }); context.subscriptions.push(enabler, disabler, reconnecter); } export async function deactivate() { - clearInterval(activityTimer); await rpc.dispose(); } diff --git a/src/structures/Activity.ts b/src/structures/Activity.ts index b0d5720..a75e1f3 100644 --- a/src/structures/Activity.ts +++ b/src/structures/Activity.ts @@ -1,3 +1,5 @@ +import { statSync } from 'fs'; +import { basename, parse, sep } from 'path'; import { debug, Disposable, @@ -5,13 +7,14 @@ import { window, workspace } from 'vscode'; -import { basename, sep, parse } from 'path'; -import { statSync } from 'fs'; const lang = require('../data/languages.json'); const knownExtentions: { [key: string]: { image: string } } = lang.knownExtentions; const knownLanguages: string[] = lang.knownLanguages; -interface Activity { +const empty = '\u200b\u200b'; +const sizes = [' bytes', 'kb', 'mb', 'gb', 'tb']; + +interface State { details?: string; state?: string; startTimestamp?: number | null; @@ -29,8 +32,8 @@ interface FileDetail { currentColumn?: string; } -export default class Acivity implements Disposable { - private _state: Activity | null = null; +export default class Activity implements Disposable { + private _state: State | null = null; private _config = workspace.getConfiguration('discord'); @@ -47,8 +50,8 @@ export default class Acivity implements Disposable { return this._state = { ...this._state, details: this._generateDetails('detailsDebugging', 'detailsEditing', 'detailsIdle', this._state!.largeImageKey), - state: this._generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', this._state!.largeImageKey), - smallImageKey: debug.activeDebugSession ? 'debug' : env.appName.includes('Insiders') ? 'vscode-insiders' : 'vscode' + smallImageKey: debug.activeDebugSession ? 'debug' : env.appName.includes('Insiders') ? 'vscode-insiders' : 'vscode', + state: this._generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', this._state!.largeImageKey) }; } this._lastKnownFile = window.activeTextEditor.document.fileName; @@ -59,7 +62,7 @@ export default class Acivity implements Disposable { if (!match) return false; const regex = new RegExp(match[1], match[2]); return regex.test(filename); - })!] || (knownLanguages.includes(window.activeTextEditor.document.languageId) ? window.activeTextEditor.document.languageId : null) + })!] || (knownLanguages.includes(window.activeTextEditor.document.languageId) ? window.activeTextEditor.document.languageId : null); } let previousTimestamp = null; @@ -67,8 +70,8 @@ export default class Acivity implements Disposable { this._state = { details: this._generateDetails('detailsDebugging', 'detailsEditing', 'detailsIdle', largeImageKey), - state: this._generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', largeImageKey), startTimestamp: window.activeTextEditor && previousTimestamp && workspaceElapsedTime ? previousTimestamp : window.activeTextEditor ? new Date().getTime() : null, + state: this._generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', largeImageKey), largeImageKey: largeImageKey ? largeImageKey.image || largeImageKey : 'txt', largeImageText: window.activeTextEditor ? this._config.get('largeImage')! @@ -85,9 +88,13 @@ export default class Acivity implements Disposable { return this._state; } + public dispose() { + this._state = null; + this._lastKnownFile = ''; + } + private _generateDetails(debugging: string, editing: string, idling: string, largeImageKey: any) { - const empty = '\u200b\u200b'; - let raw = this._config.get(idling); + let raw: string = this._config.get(idling)!.replace('{null}', empty); let filename = null; let dirname = null; let checkState = false; @@ -112,10 +119,11 @@ export default class Acivity implements Disposable { } if (debug.activeDebugSession) { - raw = this._config.get(debugging); + raw = this._config.get(debugging)!; } else { - raw = this._config.get(editing); + raw = this._config.get(editing)!; } + const { totalLines, size, currentLine, currentColumn } = this._generateFileDetails(raw); raw = raw! .replace('{null}', empty) @@ -144,14 +152,16 @@ export default class Acivity implements Disposable { if (str.includes('{totallines}')) { fileDetail.totalLines = window.activeTextEditor.document.lineCount.toLocaleString(); } + if (str.includes('{currentline}')) { fileDetail.currentLine = (window.activeTextEditor.selection.active.line + 1).toLocaleString(); } + if (str.includes('{currentcolumn}')) { fileDetail.currentColumn = (window.activeTextEditor.selection.active.character + 1).toLocaleString(); } + if (str.includes('{filesize}')) { - const sizes = [' bytes', 'kb', 'mb', 'gb', 'tb']; let currentDivision = 0; let { size } = statSync(window.activeTextEditor.document.fileName); const originalSize = size; @@ -166,12 +176,7 @@ export default class Acivity implements Disposable { fileDetail.size = `${originalSize > 1000 ? size.toFixed(2) : size}${sizes[currentDivision]}`; } } - + return fileDetail; } - - public dispose() { - this._state = null; - this._lastKnownFile = ''; - } }