rewrite (#101)
* rewrite: initial commit * feat: add constants for live share * feat: add first letter uppercase #88 * chore: remove automatic reconnects * chore: add troubleshooting to readme
This commit is contained in:
parent
916c596478
commit
0195cde103
14 changed files with 2073 additions and 558 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -18,5 +18,5 @@ test/
|
|||
!.vscode/tasks.json
|
||||
.vscode-test/
|
||||
dist/
|
||||
out/
|
||||
typings/
|
||||
package-lock.json
|
||||
|
|
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
|
@ -1,4 +1,3 @@
|
|||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
{
|
||||
"version": "0.5.0",
|
||||
"configurations": [
|
||||
|
@ -10,8 +9,8 @@
|
|||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/src/**/*.js" ],
|
||||
"preLaunchTask": "npm"
|
||||
"outFiles": [ "${workspaceRoot}/dist/**/*.js" ],
|
||||
"preLaunchTask": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
41
.vscode/tasks.json
vendored
41
.vscode/tasks.json
vendored
|
@ -1,30 +1,15 @@
|
|||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
{
|
||||
"version": "0.1.0",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// the command is a shell script
|
||||
"isShellCommand": true,
|
||||
|
||||
// show the output window only if unrecognized errors occur.
|
||||
"showOutput": "silent",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile", "--loglevel", "silent"],
|
||||
|
||||
// The tsc compiler is started in watching mode
|
||||
"isBackground": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch"
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "gulp",
|
||||
"task": "build",
|
||||
"problemMatcher": [
|
||||
"$gulp-tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -24,10 +24,11 @@
|
|||
* Debug mode detection
|
||||
* Easily manually reconnect to discord
|
||||
|
||||
## The rich presence won't show after sleep / connection issues!
|
||||
## Troubleshooting
|
||||
|
||||
It will only attempt to reconnect 20 times.
|
||||
After it hit that threshold you will have to manually click the `Reconnect to Discord` button in the bottom left of the window.
|
||||
### Can't connect to Discord? Check those:
|
||||
https://github.com/iCrawl/discord-vscode/issues/77#issuecomment-435622205
|
||||
https://github.com/iCrawl/discord-vscode/issues/85#issuecomment-417895483
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
27
gulpfile.js
Normal file
27
gulpfile.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
const gulp = require('gulp');
|
||||
const fsn = require('fs-nextra');
|
||||
const ts = require('gulp-typescript');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const merge = require('merge2');
|
||||
const path = require('path');
|
||||
const project = ts.createProject('tsconfig.json');
|
||||
|
||||
async function build() {
|
||||
await Promise.all([
|
||||
fsn.emptydir('dist'),
|
||||
fsn.emptydir('typings')
|
||||
]);
|
||||
|
||||
const result = project.src()
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(project());
|
||||
|
||||
await fsn.copy(path.join(__dirname, 'src', 'data'), path.join(__dirname, 'dist', 'data'))
|
||||
return merge([
|
||||
result.dts.pipe(gulp.dest('typings')),
|
||||
result.js.pipe(sourcemaps.write('.', { sourceRoot: '../src' })).pipe(gulp.dest('dist'))
|
||||
]);
|
||||
}
|
||||
|
||||
gulp.task('default', build);
|
||||
gulp.task('build', build);
|
20
package.json
20
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "discord-vscode",
|
||||
"displayName": "Discord Presence",
|
||||
"version": "2.12.1",
|
||||
"version": "3.0.0",
|
||||
"description": "Update your discord status with the newly added rich presence.",
|
||||
"private": true,
|
||||
"author": {
|
||||
|
@ -16,12 +16,10 @@
|
|||
],
|
||||
"publisher": "icrawl",
|
||||
"license": "MIT",
|
||||
"main": "./out/src/extension",
|
||||
"main": "./dist/extension",
|
||||
"scripts": {
|
||||
"compile": "tsc -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "node ./node_modules/vscode/bin/test",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'"
|
||||
"build": "gulp",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
|
@ -54,11 +52,6 @@
|
|||
"default": true,
|
||||
"description": "Controls if the Discord Presence should show across all workspaces"
|
||||
},
|
||||
"discord.reconnectThreshold": {
|
||||
"type": "number",
|
||||
"default": 20,
|
||||
"description": "Decides how many reconnect attempts should be made before stopping"
|
||||
},
|
||||
"discord.detailsEditing": {
|
||||
"type": "string",
|
||||
"default": "Editing {filename}",
|
||||
|
@ -152,6 +145,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^10.12.2",
|
||||
"fs-nextra": "^0.3.7",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-typescript": "^5.0.0-alpha.3",
|
||||
"merge2": "^1.2.3",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.1.6",
|
||||
"vscode": "^1.1.21"
|
||||
|
|
42
src/client/RPCClient.ts
Normal file
42
src/client/RPCClient.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
const { Client } = require('discord-rpc');
|
||||
import {
|
||||
Disposable,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import Acivity from '../structures/Activity';
|
||||
import Logger from '../structures/Logger';
|
||||
|
||||
export default class RPCClient implements Disposable {
|
||||
private _rpc: any = new Client({ transport: 'ipc' });
|
||||
|
||||
private _activity = new Acivity();
|
||||
|
||||
private _config = workspace.getConfiguration('discord');
|
||||
|
||||
private _clientId: string;
|
||||
|
||||
public constructor(clientId: string) {
|
||||
this._clientId = clientId;
|
||||
}
|
||||
|
||||
public get client() {
|
||||
return this._rpc;
|
||||
}
|
||||
|
||||
public setActivity(workspaceElapsedTime: boolean = false) {
|
||||
if (!this._rpc) return;
|
||||
const activity = this._activity.generate(workspaceElapsedTime);
|
||||
Logger.log('Sending activity to Discord.');
|
||||
this._rpc.setActivity(activity);
|
||||
}
|
||||
|
||||
public async login() {
|
||||
Logger.log('Logging into RPC.');
|
||||
return this._rpc.login({ clientId: this._clientId });
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
this._activity.dispose();
|
||||
await this._rpc.destroy();
|
||||
}
|
||||
}
|
8
src/constants.ts
Normal file
8
src/constants.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export const LIVE_SHARE_BASE_URL = 'insiders.liveshare.vsengsaas.visualstudio.com';
|
||||
export const VSLS_EXTENSION_ID = 'ms-vsliveshare.vsliveshare';
|
||||
|
||||
export const LIVE_SHARE_COMMANDS = {
|
||||
START: 'liveshare.start',
|
||||
END: 'liveshare.end',
|
||||
JOIN: 'liveshare.join'
|
||||
}
|
426
src/extension.ts
426
src/extension.ts
|
@ -1,401 +1,93 @@
|
|||
// Import the required functions & object types from various packages.
|
||||
import { Client } from 'discord-rpc';
|
||||
import { basename, extname, parse, sep } from 'path';
|
||||
import { setInterval, clearInterval } from 'timers';
|
||||
import RPCClient from './client/RPCClient';
|
||||
import Logger from './structures/Logger';
|
||||
import {
|
||||
commands,
|
||||
debug,
|
||||
env,
|
||||
ExtensionContext,
|
||||
StatusBarItem,
|
||||
StatusBarAlignment,
|
||||
window,
|
||||
workspace,
|
||||
WorkspaceFolder
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import { statSync } from 'fs';
|
||||
const lang = require('./data/languages.json');
|
||||
import { setInterval, clearInterval } from 'timers';
|
||||
|
||||
interface FileDetail {
|
||||
size: string | null;
|
||||
totalLines: string | null;
|
||||
currentLine: string | null;
|
||||
currentColumn: string | null;
|
||||
}
|
||||
|
||||
interface Activity {
|
||||
details: string;
|
||||
state: string;
|
||||
startTimestamp: number | null;
|
||||
largeImageKey: string;
|
||||
largeImageText: string;
|
||||
smallImageKey: string;
|
||||
smallImageText: string;
|
||||
instance: boolean;
|
||||
}
|
||||
|
||||
const knownExtentions: { [x: string]: { image: string } } = lang.knownExtentions;
|
||||
const knownLanguages: string[] = lang.knownLanguages;
|
||||
|
||||
// Define the RPC variable and its type.
|
||||
let rpc: Client;
|
||||
// Define the config variable and its type.
|
||||
let config;
|
||||
// Define the reconnecting var and its type.
|
||||
let reconnecting: boolean;
|
||||
// Define the reconnect counter and its type.
|
||||
let reconnectCounter = 0;
|
||||
// Define the last known file and its type.
|
||||
let lastKnownFile: string;
|
||||
// Define the activity object.
|
||||
let activity: Activity;
|
||||
// Define the activity timer to not spam the API with requests.
|
||||
let activityTimer: NodeJS.Timer;
|
||||
// Define the status bar icon
|
||||
let statusBarIcon: StatusBarItem;
|
||||
|
||||
// `Activate` is fired when the extension is enabled. This SHOULD only fire once.
|
||||
export function activate(context: ExtensionContext) {
|
||||
console.log('[Discord Presence]: Activated!');
|
||||
// Get the workspace's configuration for "discord".
|
||||
config = workspace.getConfiguration('discord');
|
||||
const config = workspace.getConfiguration('discord');
|
||||
const rpc = new RPCClient(config.get<string>('clientID')!);
|
||||
|
||||
// Obtain whether or not the extension is activated.
|
||||
if (config.get('enabled')) initRPC(config.get('clientID'));
|
||||
export async function activate(context: ExtensionContext) {
|
||||
Logger.log('Discord Presence activated!');
|
||||
|
||||
// Register the `discord.enable` command, and set the `enabled` config option to true.
|
||||
const enabler = commands.registerCommand('discord.enable', async () => {
|
||||
if (rpc) await destroyRPC();
|
||||
await config.update('enabled', true);
|
||||
config = workspace.getConfiguration('discord');
|
||||
initRPC(config.get('clientID'));
|
||||
window.showInformationMessage('Enabled Discord Rich Presence for this workspace.');
|
||||
rpc.client.once('ready', () => {
|
||||
Logger.log('Successfully connected to Discord.');
|
||||
if (!config.get<boolean>('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<boolean>('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();
|
||||
}
|
||||
});
|
||||
|
||||
// Register the `discord.disable` command, and set the `enabled` config option to false.
|
||||
const disabler = commands.registerCommand('discord.disable', async () => {
|
||||
if (!rpc) return window.showWarningMessage('Discord Rich Presence is already disabled in this workspace.');
|
||||
await config.update('enabled', false);
|
||||
config = workspace.getConfiguration('discord');
|
||||
await destroyRPC();
|
||||
window.showInformationMessage('Disabled Discord Rich Presence for this workspace.');
|
||||
});
|
||||
|
||||
// Register the `discord.reconnect` command
|
||||
const reconnecter = commands.registerCommand('discord.reconnect', async () => {
|
||||
if (rpc) try { await destroyRPC(); } catch {}
|
||||
initRPC(config.get('clientID'), true);
|
||||
|
||||
if (!config.get('silent')) window.showInformationMessage('Reconnecting to Discord RPC');
|
||||
|
||||
if (statusBarIcon) statusBarIcon.text = '$(pulse) Reconnecting';
|
||||
});
|
||||
|
||||
// Push the new commands into the subscriptions.
|
||||
context.subscriptions.push(enabler, disabler, reconnecter);
|
||||
}
|
||||
|
||||
// `Deactivate` is fired whenever the extension is deactivated.
|
||||
export async function deactivate() {
|
||||
// If there's an RPC Client initalized, destroy it.
|
||||
await destroyRPC();
|
||||
}
|
||||
|
||||
// Initalize the RPC systems.
|
||||
function initRPC(clientID: string, loud?: boolean): void {
|
||||
// Update the RPC variable with a new RPC Client.
|
||||
rpc = new Client({ transport: 'ipc' });
|
||||
|
||||
// Once the RPC Client is ready, set the activity.
|
||||
rpc.once('ready', () => {
|
||||
console.log('[Discord Presence]: Successfully connected to Discord');
|
||||
// Announce the reconnection
|
||||
if (loud && !config.get('silent')) window.showInformationMessage('Successfully reconnected to Discord RPC');
|
||||
|
||||
// Remove icon if connected
|
||||
if (statusBarIcon) {
|
||||
statusBarIcon.dispose();
|
||||
statusBarIcon = null;
|
||||
}
|
||||
|
||||
// Stop from reconnecing.
|
||||
reconnecting = false;
|
||||
// This is purely for safety measures.
|
||||
if (activityTimer) {
|
||||
// Clear the activity interval.
|
||||
clearInterval(activityTimer);
|
||||
// Null activity variable.
|
||||
activityTimer = null;
|
||||
}
|
||||
// Reset the reconnect counter to 0 on a successful reconnect.
|
||||
reconnectCounter = 0;
|
||||
setActivity();
|
||||
// Set the activity once on ready
|
||||
setTimeout(() => rpc.setActivity(activity).catch(err => console.error(`[Discord Presence]: ${err}`)), 500);
|
||||
// Make sure to listen to the close event and dispose and destroy everything accordingly.
|
||||
rpc.transport.once('close', async () => {
|
||||
if (!config.get('enabled')) return;
|
||||
await destroyRPC();
|
||||
|
||||
// Set the client to begin reconnecting
|
||||
reconnecting = true;
|
||||
initRPC(config.get('clientID'));
|
||||
// Create reconnecting button
|
||||
createButton(true);
|
||||
});
|
||||
|
||||
// Update the user's activity to the `activity` variable.
|
||||
activityTimer = setInterval(() => {
|
||||
// Update the config before updating the activity
|
||||
config = workspace.getConfiguration('discord');
|
||||
setActivity(Boolean(config.get('workspaceElapsedTime')));
|
||||
rpc.setActivity(activity).catch(err => console.error(`[Discord Presence]: ${err}`));
|
||||
}, 15000);
|
||||
});
|
||||
rpc.setActivity(config.get<boolean>('workspaceElapsedTime'));
|
||||
}, 10000);
|
||||
})
|
||||
|
||||
// Log in to the RPC Client, and check whether or not it errors.
|
||||
rpc.login({ clientId: clientID }).catch(async error => {
|
||||
// Check if the client is reconnecting
|
||||
console.error(`[Discord Presence]: ${error}`);
|
||||
if (reconnecting) {
|
||||
// Destroy and dispose of everything after the set reconnect attempts
|
||||
if (reconnectCounter >= config.get('reconnectThreshold')) {
|
||||
// Create reconnect button
|
||||
createButton();
|
||||
await destroyRPC();
|
||||
} else {
|
||||
// Increment the counter
|
||||
reconnectCounter++;
|
||||
// Create reconnecting button
|
||||
createButton(true);
|
||||
// Retry connection
|
||||
initRPC(config.get('clientID'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Announce failure
|
||||
if (config.get<boolean>('enabled')) {
|
||||
try {
|
||||
await rpc.login();
|
||||
} catch (error) {
|
||||
Logger.log(`Encountered following error after trying to login:\n${error}`);
|
||||
if (!config.get('silent')) {
|
||||
if (error.message.includes('ENOENT')) window.showErrorMessage('No Discord Client detected!');
|
||||
else window.showErrorMessage(`Couldn't connect to Discord via RPC: ${error.toString()}`);
|
||||
createButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create reconnect button
|
||||
function createButton(isReconnecting?: boolean): void {
|
||||
// Check if the button exists already
|
||||
if (!statusBarIcon) {
|
||||
// Create the icon
|
||||
statusBarIcon = window.createStatusBarItem(StatusBarAlignment.Left);
|
||||
// Check if the client is reconnecting
|
||||
if (isReconnecting) {
|
||||
// Show attempts left
|
||||
const attempts = config.get('reconnectThreshold') - reconnectCounter;
|
||||
statusBarIcon.text = `$(issue-reopened) Reconnecting: ${attempts} attempt${attempts === 1 ? '' : 's'} left`;
|
||||
statusBarIcon.command = '';
|
||||
} else {
|
||||
// Show button to reconnect
|
||||
statusBarIcon.text = '$(plug) Reconnect to Discord';
|
||||
statusBarIcon.command = 'discord.reconnect';
|
||||
}
|
||||
// Show the button
|
||||
statusBarIcon.show();
|
||||
} else {
|
||||
// Check if the client is reconnecting
|
||||
if (isReconnecting) {
|
||||
// Show attempts left
|
||||
const attempts = config.get('reconnectThreshold') - reconnectCounter;
|
||||
statusBarIcon.text = `$(issue-reopened) Reconnecting: ${attempts} attempt${attempts === 1 ? '' : 's'} left`;
|
||||
statusBarIcon.command = '';
|
||||
} else {
|
||||
// Show button to reconnect
|
||||
statusBarIcon.text = '$(plug) Reconnect to Discord';
|
||||
statusBarIcon.command = 'discord.reconnect';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanly destroy the RPC client (if it isn't already) && add icon to reconnect
|
||||
async function destroyRPC(): Promise<void> {
|
||||
// Do not continue if RPC isn't initalized.
|
||||
if (!rpc) return;
|
||||
// Stop reconnecting.
|
||||
reconnecting = false;
|
||||
// Clear the activity interval.
|
||||
if (activityTimer) clearInterval(activityTimer);
|
||||
// Null the activity timer.
|
||||
activityTimer = null;
|
||||
// If there's an RPC Client initalized, destroy it.
|
||||
await rpc.destroy();
|
||||
// Null the RPC variable.
|
||||
rpc = null;
|
||||
// Null the last known file.
|
||||
lastKnownFile = null;
|
||||
const enabler = commands.registerCommand('discord.enable', async () => {
|
||||
await rpc.dispose();
|
||||
await config.update('enabled', true);
|
||||
await rpc.login();
|
||||
window.showInformationMessage('Enabled Discord Rich Presence for this workspace.');
|
||||
});
|
||||
|
||||
const disabler = commands.registerCommand('discord.disable', async () => {
|
||||
await config.update('enabled', false);
|
||||
await rpc.dispose();
|
||||
window.showInformationMessage('Disabled Discord Rich Presence for this workspace.');
|
||||
});
|
||||
|
||||
const reconnecter = commands.registerCommand('discord.reconnect', async () => {
|
||||
await rpc.dispose();
|
||||
await rpc.login();
|
||||
if (!config.get('silent')) window.showInformationMessage('Reconnecting to Discord RPC...');
|
||||
if (statusBarIcon) statusBarIcon.text = '$(pulse) reconnecting...';
|
||||
});
|
||||
|
||||
context.subscriptions.push(enabler, disabler, reconnecter);
|
||||
}
|
||||
|
||||
// This function updates the activity (The Client's Rich Presence status).
|
||||
function setActivity(workspaceElapsedTime: boolean = false): void {
|
||||
// Do not continue if RPC isn't initalized.
|
||||
if (!rpc) return;
|
||||
|
||||
if (window.activeTextEditor && window.activeTextEditor.document.fileName === lastKnownFile) {
|
||||
activity = {
|
||||
...activity,
|
||||
details: generateDetails('detailsDebugging', 'detailsEditing', 'detailsIdle', activity.largeImageKey),
|
||||
state: generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', activity.largeImageKey),
|
||||
smallImageKey: debug.activeDebugSession
|
||||
? 'debug'
|
||||
: env.appName.includes('Insiders')
|
||||
? 'vscode-insiders'
|
||||
: 'vscode',
|
||||
};
|
||||
return;
|
||||
}
|
||||
lastKnownFile = window.activeTextEditor ? window.activeTextEditor.document.fileName : null;
|
||||
|
||||
const fileName: string = window.activeTextEditor ? basename(window.activeTextEditor.document.fileName) : null;
|
||||
const largeImageKey: any = window.activeTextEditor
|
||||
? knownExtentions[Object.keys(knownExtentions).find(key => {
|
||||
if (key.startsWith('.') && fileName.endsWith(key)) return true;
|
||||
const match = key.match(/^\/(.*)\/([mgiy]+)$/);
|
||||
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)
|
||||
: 'vscode-big';
|
||||
|
||||
// Get the previous activity start timestamp (if available) to preserve workspace elapsed time
|
||||
let previousTimestamp = null;
|
||||
if (activity) previousTimestamp = activity['startTimestamp'];
|
||||
// Create a JSON Object with the user's activity information.
|
||||
activity = {
|
||||
details: generateDetails('detailsDebugging', 'detailsEditing', 'detailsIdle', largeImageKey),
|
||||
state: generateDetails('lowerDetailsDebugging', 'lowerDetailsEditing', 'lowerDetailsIdle', largeImageKey),
|
||||
startTimestamp: window.activeTextEditor && previousTimestamp && workspaceElapsedTime ? previousTimestamp : window.activeTextEditor ? new Date().getTime() : null,
|
||||
largeImageKey: largeImageKey
|
||||
? largeImageKey.image
|
||||
|| largeImageKey
|
||||
: 'txt',
|
||||
largeImageText: window.activeTextEditor
|
||||
? config.get('largeImage').replace('{lang}', largeImageKey ? largeImageKey.image || largeImageKey : 'txt').replace('{LANG}', largeImageKey ? (largeImageKey.image || largeImageKey).toUpperCase() : 'TXT')
|
||||
|| window.activeTextEditor.document.languageId.padEnd(2, '\u200b')
|
||||
: config.get('largeImageIdle'),
|
||||
smallImageKey: debug.activeDebugSession
|
||||
? 'debug'
|
||||
: env.appName.includes('Insiders')
|
||||
? 'vscode-insiders'
|
||||
: 'vscode',
|
||||
smallImageText: config.get('smallImage').replace('{appname}', env.appName),
|
||||
instance: false
|
||||
};
|
||||
}
|
||||
|
||||
function generateDetails(debugging, editing, idling, largeImageKey): string {
|
||||
const emptySpaces = '\u200b\u200b';
|
||||
let string: string = config.get(idling).replace('{null}', emptySpaces);
|
||||
|
||||
const fileName: string = window.activeTextEditor ? basename(window.activeTextEditor.document.fileName) : null;
|
||||
let dirName: string = null;
|
||||
if (window.activeTextEditor) {
|
||||
const { dir } = parse(window.activeTextEditor.document.fileName);
|
||||
const split = dir.split(sep);
|
||||
dirName = split[split.length - 1];
|
||||
}
|
||||
const checkState: boolean = window.activeTextEditor
|
||||
? Boolean(workspace.getWorkspaceFolder(window.activeTextEditor.document.uri))
|
||||
: false;
|
||||
|
||||
const workspaceFolder: WorkspaceFolder = checkState ? workspace.getWorkspaceFolder(window.activeTextEditor.document.uri) : null;
|
||||
|
||||
let fullDirName: string = null;
|
||||
if (workspaceFolder) {
|
||||
const { name } = workspaceFolder;
|
||||
const relativePath = workspace.asRelativePath(window.activeTextEditor.document.fileName).split(sep);
|
||||
relativePath.splice(-1, 1);
|
||||
fullDirName = `${name}${sep}${relativePath.join(sep)}`;
|
||||
}
|
||||
|
||||
if (window.activeTextEditor) {
|
||||
if (debug.activeDebugSession) {
|
||||
let rawString = config.get(debugging);
|
||||
const { totalLines, size, currentLine, currentColumn } = getFileDetails(rawString);
|
||||
rawString = rawString
|
||||
.replace('{null}', emptySpaces)
|
||||
.replace('{filename}', fileName)
|
||||
.replace('{dirname}', dirName)
|
||||
.replace('{fulldirname}', fullDirName)
|
||||
.replace('{workspace}',
|
||||
checkState ?
|
||||
workspaceFolder.name :
|
||||
config.get('lowerDetailsNotFound').replace('{null}', emptySpaces)
|
||||
)
|
||||
.replace('{lang}', largeImageKey ? largeImageKey.image || largeImageKey : 'txt')
|
||||
.replace('{LANG}', largeImageKey ? (largeImageKey.image || largeImageKey).toUpperCase() : 'TXT');
|
||||
if (totalLines) rawString = rawString.replace('{totallines}', totalLines);
|
||||
if (size) rawString = rawString.replace('{filesize}', size);
|
||||
if (currentLine) rawString = rawString.replace('{currentline}', currentLine);
|
||||
if (currentColumn) rawString = rawString.replace('{currentcolumn}', currentColumn);
|
||||
string = rawString;
|
||||
} else {
|
||||
let rawString = config.get(editing);
|
||||
const { totalLines, size, currentLine, currentColumn } = getFileDetails(rawString);
|
||||
rawString = rawString
|
||||
.replace('{null}', emptySpaces)
|
||||
.replace('{filename}', fileName)
|
||||
.replace('{dirname}', dirName)
|
||||
.replace('{fulldirname}', fullDirName)
|
||||
.replace('{workspace}',
|
||||
checkState ?
|
||||
workspaceFolder.name :
|
||||
config.get('lowerDetailsNotFound').replace('{null}', emptySpaces)
|
||||
)
|
||||
.replace('{lang}', largeImageKey ? largeImageKey.image || largeImageKey : 'txt')
|
||||
.replace('{LANG}', largeImageKey ? (largeImageKey.image || largeImageKey).toUpperCase() : 'TXT');
|
||||
if (totalLines) rawString = rawString.replace('{totallines}', totalLines);
|
||||
if (size) rawString = rawString.replace('{filesize}', size);
|
||||
if (currentLine) rawString = rawString.replace('{currentline}', currentLine);
|
||||
if (currentColumn) rawString = rawString.replace('{currentcolumn}', currentColumn);
|
||||
string = rawString;
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
function getFileDetails(rawString): FileDetail {
|
||||
const obj = {
|
||||
size: null,
|
||||
totalLines: null,
|
||||
currentLine: null,
|
||||
currentColumn: null,
|
||||
};
|
||||
if (!rawString) return obj;
|
||||
if (rawString.includes('{totallines}')) {
|
||||
obj.totalLines = window.activeTextEditor.document.lineCount.toLocaleString();
|
||||
}
|
||||
if (rawString.includes('{currentline}')) {
|
||||
obj.currentLine = (window.activeTextEditor.selection.active.line + 1).toLocaleString();
|
||||
}
|
||||
if (rawString.includes('{currentcolumn}')) {
|
||||
obj.currentColumn = (window.activeTextEditor.selection.active.character + 1).toLocaleString();
|
||||
}
|
||||
if (rawString.includes('{filesize}')) {
|
||||
const sizes = [' bytes', 'kb', 'mb', 'gb', 'tb'];
|
||||
let currentDivision = 0;
|
||||
let { size } = statSync(window.activeTextEditor.document.fileName);
|
||||
const originalSize = size;
|
||||
if (originalSize > 1000) {
|
||||
size = size / 1000;
|
||||
currentDivision++;
|
||||
while (size > 1000) {
|
||||
currentDivision++;
|
||||
size = size / 1000;
|
||||
}
|
||||
}
|
||||
obj.size = `${originalSize > 1000 ? size.toFixed(2) : size}${sizes[currentDivision]}`;
|
||||
}
|
||||
return obj;
|
||||
export async function deactivate() {
|
||||
clearInterval(activityTimer);
|
||||
await rpc.dispose();
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', console.error);
|
||||
|
|
174
src/structures/Activity.ts
Normal file
174
src/structures/Activity.ts
Normal file
|
@ -0,0 +1,174 @@
|
|||
import {
|
||||
debug,
|
||||
Disposable,
|
||||
env,
|
||||
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 {
|
||||
details?: string;
|
||||
state?: string;
|
||||
startTimestamp?: number | null;
|
||||
largeImageKey?: string;
|
||||
largeImageText?: string;
|
||||
smallImageKey?: string;
|
||||
smallImageText?: string;
|
||||
instance?: boolean;
|
||||
}
|
||||
|
||||
interface FileDetail {
|
||||
size?: string;
|
||||
totalLines?: string;
|
||||
currentLine?: string;
|
||||
currentColumn?: string;
|
||||
}
|
||||
|
||||
export default class Acivity implements Disposable {
|
||||
private _state: Activity | null = null;
|
||||
|
||||
private _config = workspace.getConfiguration('discord');
|
||||
|
||||
private _lastKnownFile: string = '';
|
||||
|
||||
public get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public generate(workspaceElapsedTime: boolean = false) {
|
||||
let largeImageKey: any = 'vscode-big';
|
||||
if (window.activeTextEditor) {
|
||||
if (window.activeTextEditor.document.fileName === this._lastKnownFile) {
|
||||
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'
|
||||
};
|
||||
}
|
||||
this._lastKnownFile = window.activeTextEditor.document.fileName;
|
||||
const filename = basename(window.activeTextEditor.document.fileName);
|
||||
largeImageKey = knownExtentions[Object.keys(knownExtentions).find(key => {
|
||||
if (key.startsWith('.') && filename.endsWith(key)) return true;
|
||||
const match = key.match(/^\/(.*)\/([mgiy]+)$/);
|
||||
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)
|
||||
}
|
||||
|
||||
let previousTimestamp = null;
|
||||
if (this.state && this.state.startTimestamp) previousTimestamp = this.state.startTimestamp;
|
||||
|
||||
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,
|
||||
largeImageKey: largeImageKey ? largeImageKey.image || largeImageKey : 'txt',
|
||||
largeImageText: window.activeTextEditor
|
||||
? this._config.get<string>('largeImage')!.replace('{lang}', largeImageKey ? largeImageKey.image || largeImageKey : 'txt').replace('{LANG}', largeImageKey ? (largeImageKey.image || largeImageKey).toUpperCase() : 'TXT')
|
||||
|| window.activeTextEditor.document.languageId.padEnd(2, '\u200b')
|
||||
: this._config.get<string>('largeImageIdle'),
|
||||
smallImageKey: debug.activeDebugSession ? 'debug' : env.appName.includes('Insiders') ? 'vscode-insiders' : 'vscode',
|
||||
smallImageText: this._config.get<string>('smallImage')!.replace('{appname}', env.appName),
|
||||
instance: false
|
||||
};
|
||||
|
||||
return this._state;
|
||||
}
|
||||
|
||||
private _generateDetails(debugging: string, editing: string, idling: string, largeImageKey: any) {
|
||||
const empty = '\u200b\u200b';
|
||||
let raw = this._config.get<string>(idling);
|
||||
let filename = null;
|
||||
let dirname = null;
|
||||
let checkState = false;
|
||||
let workspaceFolder = null;
|
||||
let fullDirname = null;
|
||||
if (window.activeTextEditor) {
|
||||
filename = basename(window.activeTextEditor.document.fileName);
|
||||
|
||||
const { dir } = parse(window.activeTextEditor.document.fileName);
|
||||
const split = dir.split(sep);
|
||||
dirname = split[split.length - 1];
|
||||
|
||||
checkState = Boolean(workspace.getWorkspaceFolder(window.activeTextEditor.document.uri));
|
||||
|
||||
workspaceFolder = checkState ? workspace.getWorkspaceFolder(window.activeTextEditor.document.uri) : null;
|
||||
|
||||
if (workspaceFolder) {
|
||||
const { name } = workspaceFolder;
|
||||
const relativePath = workspace.asRelativePath(window.activeTextEditor.document.fileName).split(sep);
|
||||
relativePath.splice(-1, 1);
|
||||
fullDirname = `${name}${sep}${relativePath.join(sep)}`;
|
||||
}
|
||||
|
||||
if (debug.activeDebugSession) {
|
||||
raw = this._config.get<string>(debugging);
|
||||
} else {
|
||||
raw = this._config.get<string>(editing);
|
||||
}
|
||||
const { totalLines, size, currentLine, currentColumn } = this._generateFileDetails(raw);
|
||||
raw = raw!
|
||||
.replace('{null}', empty)
|
||||
.replace('{filename}', filename)
|
||||
.replace('{dirname}', dirname)
|
||||
.replace('{fulldirname}', fullDirname!)
|
||||
.replace('{workspace}', checkState && workspaceFolder ? workspaceFolder.name : this._config.get<string>('lowerDetailsNotFound')!.replace('{null}', empty))
|
||||
.replace('{lang}', largeImageKey ? largeImageKey.image || largeImageKey : 'txt')
|
||||
.replace('{Lang}', largeImageKey ? (largeImageKey.image || largeImageKey).toLowerCase().replace(/^\w/, (c: string) => c.toUpperCase()) : 'Txt')
|
||||
.replace('{LANG}', largeImageKey ? (largeImageKey.image || largeImageKey).toUpperCase() : 'TXT');
|
||||
if (totalLines) raw = raw!.replace('{totalline}', totalLines);
|
||||
if (size) raw = raw!.replace('{filsize}', size);
|
||||
if (currentLine) raw = raw!.replace('{currentline}', currentLine);
|
||||
if (currentColumn) raw = raw!.replace('{currentcolumn}', currentColumn);
|
||||
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
private _generateFileDetails(str?: string) {
|
||||
const fileDetail: FileDetail = {};
|
||||
if (!str) return fileDetail;
|
||||
|
||||
if (window.activeTextEditor) {
|
||||
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;
|
||||
if (originalSize > 1000) {
|
||||
size /= 1000;
|
||||
currentDivision++;
|
||||
while (size > 1000) {
|
||||
currentDivision++;
|
||||
size /= 1000;
|
||||
}
|
||||
}
|
||||
fileDetail.size = `${originalSize > 1000 ? size.toFixed(2) : size}${sizes[currentDivision]}`;
|
||||
}
|
||||
}
|
||||
|
||||
return fileDetail;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._state = null;
|
||||
this._lastKnownFile = '';
|
||||
}
|
||||
}
|
14
src/structures/Logger.ts
Normal file
14
src/structures/Logger.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { OutputChannel, window } from 'vscode';
|
||||
|
||||
export default class Logger {
|
||||
static output: OutputChannel;
|
||||
|
||||
static setup() {
|
||||
this.output = this.output || window.createOutputChannel('Discord Presence');
|
||||
}
|
||||
|
||||
static log(message: string) {
|
||||
if (!this.output) this.setup();
|
||||
this.output.appendLine(message);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"esnext",
|
||||
"esnext.asynciterable"
|
||||
"esnext.array",
|
||||
"esnext.asynciterable",
|
||||
"esnext.intl",
|
||||
"esnext.symbol"
|
||||
],
|
||||
"outDir": "out",
|
||||
"rootDir": ".",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test",
|
||||
"out"
|
||||
"include": [
|
||||
"./src"
|
||||
]
|
||||
}
|
||||
|
|
111
tslint.json
111
tslint.json
|
@ -1,111 +0,0 @@
|
|||
{
|
||||
"rules": {
|
||||
"arrow-return-shorthand": true,
|
||||
"callable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": false,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": [
|
||||
true,
|
||||
"tabs"
|
||||
],
|
||||
"interface-over-type-literal": false,
|
||||
"label-position": true,
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-super": true,
|
||||
"no-empty": false,
|
||||
"no-empty-interface": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-misused-new": true,
|
||||
"no-non-null-assertion": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-string-throw": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unnecessary-initializer": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"typeof-compare": true,
|
||||
"unified-signatures": true,
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue