refactor: big rewrite

This commit is contained in:
iCrawl 2021-02-10 04:24:42 +01:00
parent 3b3f41af23
commit aa5e0c97da
No known key found for this signature in database
GPG key ID: 1AB888B16355FBB2
21 changed files with 4232 additions and 1403 deletions

View file

@ -1,23 +0,0 @@
name: Lint
on: [push, pull_request]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install pnpm
run: curl -L https://unpkg.com/@pnpm/self-installer | node
- name: Install dependencies
run: pnpm i
- name: Run Lint
uses: icrawl/action-eslint@v1

23
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: Testing
on: [push, pull_request]
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Node v14
uses: actions/setup-node@v2
with:
node-version: 14
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run TSC
run: npm run build

View file

@ -1,23 +0,0 @@
name: TSC
on: [push, pull_request]
jobs:
tsc:
name: TSC
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install pnpm
run: curl -L https://unpkg.com/@pnpm/self-installer | node
- name: Install dependencies
run: pnpm i
- name: Run TSC
uses: icrawl/action-tsc@v1

8
.prettierrc.json Normal file
View file

@ -0,0 +1,8 @@
{
"printWidth": 120,
"useTabs": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf"
}

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2020 iCrawl Copyright (c) 2017-2021 iCrawl
Copyright (c) 2017-2019 Khinenw Copyright (c) 2017-2019 Khinenw
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

View file

@ -1,4 +1,5 @@
# Discord Presence # Discord Presence
> Update your discord status with the newly added rich presence. > Update your discord status with the newly added rich presence.
<div align="center"> <div align="center">
@ -14,19 +15,20 @@
## Features ## Features
* Shows what you are editing in VSCode with no bullsh*t involved - Shows what you are editing in VSCode with no bullsh\*t involved
* Support for over 130 of the most popular languages - Support for over 130 of the most popular languages
* Enable/Disable Rich Presence for individual workspaces (enabled by default) - Enable/Disable Rich Presence for individual workspaces (enabled by default)
* Custom string support - Custom string support
* Respects Discords 15sec limit when it comes to updating your status - Respects Discords 15sec limit when it comes to updating your status
* Stable or Insiders build detection - Stable or Insiders build detection
* Debug mode detection - Debug mode detection
* Easily manually reconnect to Discord - Easily manually reconnect to Discord
* VSCode Live Share support - VSCode Live Share support
## Troubleshooting ## Troubleshooting
### Can't connect to Discord? Check those: ### Can't connect to Discord? Check those:
https://github.com/iCrawl/discord-vscode/issues/77#issuecomment-435622205 https://github.com/iCrawl/discord-vscode/issues/77#issuecomment-435622205
https://github.com/iCrawl/discord-vscode/issues/85#issuecomment-417895483 https://github.com/iCrawl/discord-vscode/issues/85#issuecomment-417895483

3291
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{ {
"name": "discord-vscode", "name": "discord-vscode",
"displayName": "Discord Presence", "displayName": "Discord Presence",
"version": "4.1.0", "version": "5.0.0",
"description": "Update your discord status with the newly added rich presence.", "description": "Update your discord status with the newly added rich presence.",
"private": true, "private": true,
"author": { "author": {
@ -20,7 +20,6 @@
"scripts": { "scripts": {
"prebuild": "npm run lint", "prebuild": "npm run lint",
"build": "webpack --mode production", "build": "webpack --mode production",
"tsc": "tsc",
"lint": "eslint src --ext .ts", "lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix" "lint:fix": "eslint src --ext .ts --fix"
}, },
@ -34,42 +33,22 @@
"commands": [ "commands": [
{ {
"command": "discord.enable", "command": "discord.enable",
"title": "Enable Discord Presence in the Current Workspace", "title": "Enable Discord Presence in the current workspace",
"category": "Discord Presence" "category": "Discord Presence"
}, },
{ {
"command": "discord.disable", "command": "discord.disable",
"title": "Disable Discord Presence in the Current Workspace", "title": "Disable Discord Presence in the current workspace",
"category": "Discord Presence" "category": "Discord Presence"
}, },
{ {
"command": "discord.reconnect", "command": "discord.reconnect",
"title": "Reconnect Discord Presence to Discord RPC", "title": "Reconnect Discord Presence to Discord",
"category": "Discord Presence" "category": "Discord Presence"
}, },
{ {
"command": "discord.disconnect", "command": "discord.disconnect",
"title": "Disconnect Discord Presence from Discord RPC", "title": "Disconnect Discord Presence from Discord",
"category": "Discord Presence"
},
{
"command": "discord.allowSpectate",
"title": "Allow Spectating",
"category": "Discord Presence"
},
{
"command": "discord.disableSpectate",
"title": "Disable Spectating",
"category": "Discord Presence"
},
{
"command": "discord.allowJoinRequests",
"title": "Allow Join Requests",
"category": "Discord Presence"
},
{
"command": "discord.disableJoinRequests",
"title": "Disable Join Requests",
"category": "Discord Presence" "category": "Discord Presence"
} }
], ],
@ -83,67 +62,67 @@
"default": true, "default": true,
"description": "Controls if the Discord Presence should show across all workspaces" "description": "Controls if the Discord Presence should show across all workspaces"
}, },
"discord.detailsEditing": { "discord.details_idling": {
"type": "string",
"default": "Editing {filename}",
"description": "Custom string for the details section of the rich presence\n\t- '{null}' will be replaced with an empty space.\n\t- '{filename}' will be replaced with the current file name.\n\t- '{dirname}' will get replaced with the folder name that has the current file.\n\t- '{fulldirname}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspaceFolder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspaceAndFolder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{currentcolumn}' will get replaced with the current column of the current line.\n\t- '{currentline}' will get replaced with the current line number.\n\t- '{totallines}' will get replaced with the total line number.\n\t- '{filesize}' will get replaced with the current file's size.\n\t- '{gitreponame}' will be replaced with the active Git repository name (from the git URL)\n\t- '{gitbranch}' will be replaced with the current active branch name."
},
"discord.detailsDebugging": {
"type": "string",
"default": "Debugging {filename}",
"description": "Custom string for the details section of the rich presence when debugging\n\t- '{null}' will be replaced with an empty space.\n\t- '{filename}' will be replaced with the current file name.\n\t- '{dirname}' will get replaced with the folder name that has the current file.\n\t- '{fulldirname}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspaceFolder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspaceAndFolder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{currentcolumn}' will get replaced with the current column of the current line.\n\t- '{currentline}' will get replaced with the current line number.\n\t- '{totallines}' will get replaced with the total line number.\n\t- '{filesize}' will get replaced with the current file's size.\n\t- '{gitreponame}' will be replaced with the active Git repository name (from the git URL)\n\t- '{gitbranch}' will be replaced with the current active branch name."
},
"discord.detailsIdle": {
"type": "string", "type": "string",
"default": "Idling", "default": "Idling",
"description": "Custom string for the details section of the rich presence when idling\n\t- '{null}' will be replaced with an empty space." "description": "Custom string for the details section of the rich presence when idling\n\t- '{empty}' will be replaced with an empty space."
}, },
"discord.lowerDetailsEditing": { "discord.details_editing": {
"type": "string",
"default": "Editing {file_name}",
"description": "Custom string for the details section of the rich presence\n\t- '{empty}' will be replaced with an empty space.\n\t- '{file_name}' will be replaced with the current file name.\n\t- '{dir_name}' will get replaced with the folder name that has the current file.\n\t- '{full_dir_name}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspace_folder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspace_and_folder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{current_column}' will get replaced with the current column of the current line.\n\t- '{current_line}' will get replaced with the current line number.\n\t- '{total_lines}' will get replaced with the total line number.\n\t- '{file_size}' will get replaced with the current file's size.\n\t- '{git_repo_name}' will be replaced with the active Git repository name (from the git URL)\n\t- '{git_branch}' will be replaced with the current active branch name."
},
"discord.details_debugging": {
"type": "string",
"default": "Debugging {file_name}",
"description": "Custom string for the details section of the rich presence when debugging\n\t- '{empty}' will be replaced with an empty space.\n\t- '{file_name}' will be replaced with the current file name.\n\t- '{dir_name}' will get replaced with the folder name that has the current file.\n\t- '{full_dir_name}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspace_folder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspace_and_folder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{current_column}' will get replaced with the current column of the current line.\n\t- '{current_line}' will get replaced with the current line number.\n\t- '{total_lines}' will get replaced with the total line number.\n\t- '{file_size}' will get replaced with the current file's size.\n\t- '{git_repo_name}' will be replaced with the active Git repository name (from the git URL)\n\t- '{git_branch}' will be replaced with the current active branch name."
},
"discord.lower_details_idling": {
"type": "string",
"default": "Idling",
"description": "Custom string for the state section of the rich presence when idling\n\t- '{empty}' will be replaced with an empty space."
},
"discord.lower_details_editing": {
"type": "string", "type": "string",
"default": "Workspace: {workspace}", "default": "Workspace: {workspace}",
"description": "Custom string for the state section of the rich presence\n\t- '{null}' will be replaced with an empty space.\n\t- '{filename}' will be replaced with the current file name.\n\t- '{dirname}' will get replaced with the folder name that has the current file.\n\t- '{fulldirname}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspaceFolder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspaceAndFolder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{currentcolumn}' will get replaced with the current column of the current line.\n\t- '{currentline}' will get replaced with the current line number.\n\t- '{totallines}' will get replaced with the total line number.\n\t- '{filesize}' will get replaced with the current file's size.\n\t- '{gitreponame}' will be replaced with the active Git repository name (from the git URL)\n\t- '{gitbranch}' will be replaced with the current active branch name." "description": "Custom string for the state section of the rich presence\n\t- '{empty}' will be replaced with an empty space.\n\t- '{file_name}' will be replaced with the current file name.\n\t- '{dir_name}' will get replaced with the folder name that has the current file.\n\t- '{full_dir_name}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspace_folder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspace_and_folder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{current_column}' will get replaced with the current column of the current line.\n\t- '{current_line}' will get replaced with the current line number.\n\t- '{total_lines}' will get replaced with the total line number.\n\t- '{file_size}' will get replaced with the current file's size.\n\t- '{git_repo_name}' will be replaced with the active Git repository name (from the git URL)\n\t- '{git_branch}' will be replaced with the current active branch name."
}, },
"discord.lowerDetailsDebugging": { "discord.lower_details_debugging": {
"type": "string", "type": "string",
"default": "Debugging: {workspace}", "default": "Debugging: {workspace}",
"description": "Custom string for the state section of the rich presence when debugging\n\t- '{null}' will be replaced with an empty space.\n\t- '{filename}' will be replaced with the current file name.\n\t- '{dirname}' will get replaced with the folder name that has the current file.\n\t- '{fulldirname}' will get replaced with the full directory name without the current file name..\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspaceFolder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspaceAndFolder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{currentcolumn}' will get replaced with the current column of the current line.\n\t- '{currentline}' will get replaced with the current line number.\n\t- '{totallines}' will get replaced with the total line number.\n\t- '{filesize}' will get replaced with the current file's size.\n\t- '{gitreponame}' will be replaced with the active Git repository name (from the git URL)\n\t- '{gitbranch}' will be replaced with the current active branch name." "description": "Custom string for the state section of the rich presence when debugging\n\t- '{empty}' will be replaced with an empty space.\n\t- '{file_name}' will be replaced with the current file name.\n\t- '{dir_name}' will get replaced with the folder name that has the current file.\n\t- '{full_dir_name}' will get replaced with the full directory name without the current file name.\n\t- '{workspace}' will be replaced with the current workspace name, if any.\n\t- '{workspace_folder}' will be replaced with the currently accessed workspace folder, if any.\n\t- '{workspace_and_folder} will be replaced with the currently accessed workspace and workspace folder like this: 'Workspace - WorkspaceFolder'\n\t- '{current_column}' will get replaced with the current column of the current line.\n\t- '{current_line}' will get replaced with the current line number.\n\t- '{total_lines}' will get replaced with the total line number.\n\t- '{file_size}' will get replaced with the current file's size.\n\t- '{git_repo_name}' will be replaced with the active Git repository name (from the git URL)\n\t- '{git_branch}' will be replaced with the current active branch name."
}, },
"discord.lowerDetailsIdle": { "discord.lower_details_no_workspace_found": {
"type": "string",
"default": "Idling",
"description": "Custom string for the state section of the rich presence when idling\n\t- '{null}' will be replaced with an empty space."
},
"discord.lowerDetailsNotFound": {
"type": "string", "type": "string",
"default": "No workspace.", "default": "No workspace.",
"description": "Custom string for the state section of the rich presence when no workspace is found.\nIf set to '{null}', this will be an empty space.\n\t- '{currentline}' will get replaced with the current line number.\n\t- '{totallines}' will get replaced with the total line number.\n\t- '{filesize}' will get replaced with the current file's size." "description": "Custom string for the state section of the rich presence when no workspace is found.\nIf set to '{empty}', this will be an empty space.\n\t- '{current_line}' will get replaced with the current line number.\n\t- '{total_lines}' will get replaced with the total line number.\n\t- '{file_size}' will get replaced with the current file's size."
}, },
"discord.largeImage": { "discord.large_image_idling": {
"type": "string",
"default": "Editing a {LANG} file",
"description": "Custom string for the largeImageText section of the rich presence.\n\t- '{lang}' will be replaced with the lowercased language ID\n\t- '{LANG}' will be replaced with the uppercased language ID"
},
"discord.largeImageIdle": {
"type": "string", "type": "string",
"default": "Idling", "default": "Idling",
"description": "Custom string for the largeImageText section of the rich presence when idling" "description": "Custom string for the largeImageText section of the rich presence when idling"
}, },
"discord.smallImage": { "discord.large_image": {
"type": "string", "type": "string",
"default": "{appname}", "default": "Editing a {LANG} file",
"description": "Custom string for the smallImageText section of the rich presence\n\t- '{appname}' will get replaced with the current Visual Studio Code version." "description": "Custom string for the largeImageText section of the rich presence.\n\t- '{lang}' will be replaced with the lowercased language ID\n\t- '{LANG}' will be replaced with the uppercased language ID"
}, },
"discord.silent": { "discord.small_image": {
"type": "string",
"default": "{app_name}",
"description": "Custom string for the smallImageText section of the rich presence\n\t- '{app_name}' will get replaced with the current Visual Studio Code version."
},
"discord.suppress_notifications": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "Decides if error messages are shown to the user" "description": "Decides if error messages are shown to the user"
}, },
"discord.workspaceElapsedTime": { "discord.workspace_elapsed_time": {
"type": "boolean", "type": "boolean",
"default": false, "default": true,
"description": "Decides whether to display elapsed time for a workspace or a single file" "description": "Decides whether to display elapsed time for a workspace or a single file"
}, },
"discord.workspaceExcludePatterns": { "discord.workspace_exclude_patterns": {
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -181,10 +160,10 @@
}, },
"dependencies": { "dependencies": {
"bufferutil": "^4.0.3", "bufferutil": "^4.0.3",
"dayjs": "^1.10.4",
"discord-rpc": "github:discordjs/RPC", "discord-rpc": "github:discordjs/RPC",
"tslib": "^2.1.0", "tslib": "^2.1.0",
"utf-8-validate": "^5.0.4", "utf-8-validate": "^5.0.4"
"vsls": "^1.0.3015"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^14.14.25", "@types/node": "^14.14.25",
@ -193,7 +172,7 @@
"@typescript-eslint/parser": "^4.15.0", "@typescript-eslint/parser": "^4.15.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"eslint": "^7.19.0", "eslint": "^7.19.0",
"eslint-config-marine": "^7.2.0", "eslint-config-marine": "^8.1.0",
"eslint-config-prettier": "^7.2.0", "eslint-config-prettier": "^7.2.0",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.3.1",
"prettier": "^2.2.1", "prettier": "^2.2.1",
@ -204,6 +183,6 @@
"webpack-cli": "^4.5.0" "webpack-cli": "^4.5.0"
}, },
"engines": { "engines": {
"vscode": "^1.51.0" "vscode": "^1.53.0"
} }
} }

193
src/activity.ts Normal file
View file

@ -0,0 +1,193 @@
import { basename, parse, sep } from 'path';
import { debug, env, extensions, Selection, TextDocument, window, workspace } from 'vscode';
import {
CONFIG_KEYS,
DEBUG_IMAGE_KEY,
EMPTY,
FILE_SIZES,
IDLE_IMAGE_KEY,
REPLACE_KEYS,
VSCODE_IMAGE_KEY,
VSCODE_INSIDERS_IMAGE_KEY,
} from './constants';
import { GitExtension } from './git';
import { log, LogLevel } from './logger';
import { getConfig, resolveFileIcon, toLower, toTitle, toUpper } from './util';
interface ActivityPayload {
details: string;
state?: string;
startTimestamp?: number | null;
largeImageKey?: string;
largeImageText?: string;
smallImageKey?: string;
smallImageText?: string;
partyId?: string;
partySize?: number;
partyMax?: number;
matchSecret?: string;
joinSecret?: string;
spectateSecret?: string;
instance?: boolean;
}
export async function activity() {
const config = getConfig();
const appName = env.appName;
const defaultSmallImageKey = debug.activeDebugSession
? DEBUG_IMAGE_KEY
: appName.includes('Insiders')
? VSCODE_INSIDERS_IMAGE_KEY
: VSCODE_IMAGE_KEY;
let state: ActivityPayload = {
details: await details(CONFIG_KEYS.DetailsIdling, CONFIG_KEYS.DetailsEditing, CONFIG_KEYS.DetailsDebugging),
state: await details(
CONFIG_KEYS.LowerDetailsIdling,
CONFIG_KEYS.LowerDetailsEditing,
CONFIG_KEYS.LowerDetailsDebugging,
),
startTimestamp: null,
largeImageKey: IDLE_IMAGE_KEY,
largeImageText: config[CONFIG_KEYS.LargeImageIdling],
smallImageKey: defaultSmallImageKey,
smallImageText: config[CONFIG_KEYS.SmallImage].replace(REPLACE_KEYS.AppName, appName),
};
if (window.activeTextEditor) {
const largeImageKey = resolveFileIcon(window.activeTextEditor.document);
const largeImageText = config[CONFIG_KEYS.LargeImage]
.replace(REPLACE_KEYS.LanguageLowerCase, toLower(largeImageKey))
.replace(REPLACE_KEYS.LanguageTitleCase, toTitle(largeImageKey))
.replace(REPLACE_KEYS.LanguageUpperCase, toUpper(largeImageKey))
.padEnd(2, EMPTY);
state = {
...state,
details: await details(CONFIG_KEYS.DetailsIdling, CONFIG_KEYS.DetailsEditing, CONFIG_KEYS.DetailsDebugging),
state: await details(
CONFIG_KEYS.LowerDetailsIdling,
CONFIG_KEYS.LowerDetailsEditing,
CONFIG_KEYS.LowerDetailsDebugging,
),
largeImageKey,
largeImageText,
};
}
log(LogLevel.Debug, JSON.stringify(state, null, 2));
return state;
}
async function details(idling: CONFIG_KEYS, editing: CONFIG_KEYS, debugging: CONFIG_KEYS) {
const config = getConfig();
let raw = (config[idling] as string).replace(REPLACE_KEYS.Empty, EMPTY);
if (window.activeTextEditor) {
const fileName = basename(window.activeTextEditor.document.fileName);
const { dir } = parse(window.activeTextEditor.document.fileName);
const split = dir.split(sep);
const dirName = split[split.length - 1];
const noWorkspaceFound = config[CONFIG_KEYS.LowerDetailsNoWorkspaceFound].replace(REPLACE_KEYS.Empty, EMPTY);
const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri);
const workspaceFolderName = workspaceFolder?.name ?? noWorkspaceFound;
const workspaceName = workspace.name ?? workspaceFolderName;
const workspaceAndFolder = `${workspaceName}${workspaceFolderName === EMPTY ? '' : ` - ${workspaceFolderName}`}`;
const fileIcon = resolveFileIcon(window.activeTextEditor.document);
if (debug.activeDebugSession) {
raw = config[debugging] as string;
} else {
raw = config[editing] as string;
}
if (workspaceFolder) {
const { name } = workspaceFolder;
const relativePath = workspace.asRelativePath(window.activeTextEditor.document.fileName).split(sep);
relativePath.splice(-1, 1);
raw = raw.replace(REPLACE_KEYS.FullDirName, `${name}${sep}${relativePath.join(sep)}`);
}
raw = await fileDetails(raw, window.activeTextEditor.document, window.activeTextEditor.selection);
raw = raw
.replace(REPLACE_KEYS.FileName, fileName)
.replace(REPLACE_KEYS.DirName, dirName)
.replace(REPLACE_KEYS.Workspace, workspaceName)
.replace(REPLACE_KEYS.WorkspaceFolder, workspaceFolderName)
.replace(REPLACE_KEYS.WorkspaceAndFolder, workspaceAndFolder)
.replace(REPLACE_KEYS.LanguageLowerCase, toLower(fileIcon))
.replace(REPLACE_KEYS.LanguageTitleCase, toTitle(fileIcon))
.replace(REPLACE_KEYS.LanguageUpperCase, toUpper(fileIcon));
}
return raw;
}
async function fileDetails(_raw: string, document: TextDocument, selection: Selection) {
let raw = _raw.slice();
const gitExtension = extensions.getExtension<GitExtension>('vscode.git');
const git = gitExtension?.exports.getAPI(1);
if (raw.includes(REPLACE_KEYS.TotalLines)) {
raw = raw.replace(REPLACE_KEYS.TotalLines, document.toLocaleString());
}
if (raw.includes(REPLACE_KEYS.CurrentLine)) {
raw = raw.replace(REPLACE_KEYS.CurrentLine, (selection.active.line + 1).toLocaleString());
}
if (raw.includes(REPLACE_KEYS.CurrentColumn)) {
raw = raw.replace(REPLACE_KEYS.CurrentColumn, (selection.active.character + 1).toLocaleString());
}
if (raw.includes(REPLACE_KEYS.FileSize)) {
let currentDivision = 0;
let { size } = await workspace.fs.stat(document.uri);
const originalSize = size;
if (originalSize > 1000) {
size /= 1000;
currentDivision++;
while (size > 1000) {
currentDivision++;
size /= 1000;
}
}
raw = raw.replace(
REPLACE_KEYS.FileSize,
`${originalSize > 1000 ? size.toFixed(2) : size}${FILE_SIZES[currentDivision]}`,
);
}
if (raw.includes(REPLACE_KEYS.GitBranch)) {
if (git?.repositories.length) {
raw = raw.replace(
REPLACE_KEYS.GitBranch,
git.repositories.find((repo) => repo.ui.selected)?.state.HEAD?.name ?? EMPTY,
);
} else {
raw = raw.replace(REPLACE_KEYS.GitBranch, 'Unknown');
}
}
if (raw.includes(REPLACE_KEYS.GitRepoName)) {
if (git?.repositories.length) {
raw = raw.replace(
REPLACE_KEYS.GitRepoName,
git.repositories
.find((repo) => repo.ui.selected)
?.state.remotes[0].fetchUrl?.split('/')[1]
.replace('.git', '') ?? EMPTY,
);
} else {
raw = raw.replace(REPLACE_KEYS.GitRepoName, 'Unknown');
}
}
return raw;
}

View file

@ -1,172 +0,0 @@
const { Client } = require('discord-rpc'); // eslint-disable-line
import { Disposable, StatusBarItem, Uri, window, workspace, env } from 'vscode';
import * as vsls from 'vsls';
import Activity from '../structures/Activity';
import Logger from '../structures/Logger';
import { API } from '../git';
let activityTimer: NodeJS.Timer | undefined;
export default class RPCClient implements Disposable {
public config = workspace.getConfiguration('discord');
public git?: API;
private rpc: any;
private readonly activity = new Activity(this);
public constructor(private readonly clientId: string, public statusBarIcon: StatusBarItem) {}
public get client() {
return this.rpc;
}
public async setActivity(workspaceElapsedTime = false) {
if (!this.rpc) return;
const activity = await this.activity.generate(workspaceElapsedTime);
if (!activity) return;
Logger.log('Sending activity to Discord.');
this.rpc.setActivity(activity);
}
public allowSpectate() {
if (!this.rpc) return;
Logger.log('Allowed spectating.');
Logger.log('Sending spectate activity to Discord.');
void this.activity.allowSpectate();
}
public disableSpectate() {
if (!this.rpc) return;
Logger.log('Disabled spectating.');
void this.activity.disableSpectate();
}
public allowJoinRequests() {
if (!this.rpc) return;
Logger.log('Allowed join requests.');
Logger.log('Sending join activity to Discord.');
void this.activity.allowJoinRequests();
}
public disableJoinRequests() {
if (!this.rpc) return;
Logger.log('Disabled join requests.');
void this.activity.disableJoinRequests();
}
public async login() {
if (this.rpc) {
this.dispose();
}
this.rpc = new Client({ transport: 'ipc' });
Logger.log('Logging into RPC...');
this.rpc.transport.once('close', () => {
if (!this.config.get<boolean>('enabled')) return;
void this.dispose();
this.statusBarIcon.text = '$(plug) Reconnect to Discord';
this.statusBarIcon.command = 'discord.reconnect';
this.statusBarIcon.tooltip = '';
});
this.rpc.once('ready', async () => {
Logger.log('Successfully connected to Discord.');
this.statusBarIcon.text = '$(globe) Connected to Discord';
this.statusBarIcon.tooltip = 'Connected to Discord';
setTimeout(() => (this.statusBarIcon.text = '$(globe)'), 5000);
if (activityTimer) clearInterval(activityTimer);
void this.setActivity(this.config.get<boolean>('workspaceElapsedTime'));
activityTimer = setInterval(() => {
this.config = workspace.getConfiguration('discord');
void this.setActivity(this.config.get<boolean>('workspaceElapsedTime'));
}, 1000);
this.rpc.subscribe('ACTIVITY_SPECTATE', async ({ secret }: { secret: string }) => {
const liveshare = await vsls.getApi();
if (!liveshare) return;
try {
const s = Buffer.from(secret, 'base64').toString();
// You might be asking yourself: "but why?"
// VS Liveshare has this annoying bug where you convert a URL string to a URI object to autofill
// But the autofill will be empty, so to circumvent this I need to add copying the link to the clipboard
// And immediately pasting it after the window pops up empty
await env.clipboard.writeText(s);
const uriString = await env.clipboard.readText();
const uri = Uri.parse(uriString);
await liveshare.join(uri);
} catch (error) {
Logger.log(error);
}
});
// You might be asking yourself again: "but why?"
// Same here, this is a real nasty race condition that happens inside the discord-rpc module currently
// To circumvent this we need to timeout sending the subscribe events to the discord client
setTimeout(() => {
this.rpc.subscribe(
'ACTIVITY_JOIN_REQUEST',
async ({ user }: { user: { username: string; discriminator: string } }) => {
const val = await window.showInformationMessage(
`${user.username}#${user.discriminator} wants to join your session`,
{ title: 'Accept' },
{ title: 'Decline' },
);
if (val && val.title === 'Accept') await this.rpc.sendJoinInvite(user);
else await this.rpc.closeJoinRequest(user);
},
);
}, 1000);
setTimeout(() => {
this.rpc.subscribe('ACTIVITY_JOIN', async ({ secret }: { secret: string }) => {
const liveshare = await vsls.getApi();
if (!liveshare) return;
try {
const s = Buffer.from(secret, 'base64').toString();
// You might be asking yourself again again: "but why?"
// See first comment above
await env.clipboard.writeText(s);
const uriString = await env.clipboard.readText();
const uri = Uri.parse(uriString);
await liveshare.join(uri);
} catch (error) {
Logger.log(error);
}
});
}, 2000);
const liveshare = await vsls.getApi();
if (!liveshare) return;
liveshare.onDidChangeSession(({ session }) => {
if (session.id) return this.activity.changePartyId(session.id);
return this.activity.changePartyId();
});
liveshare.onDidChangePeers(({ added, removed }) => {
if (added.length) return this.activity.increasePartySize(added.length);
else if (removed.length) return this.activity.decreasePartySize(removed.length);
});
});
try {
await this.rpc.login({ clientId: this.clientId });
} catch (error) {
throw error;
}
}
public dispose() {
this.activity.dispose();
if (this.rpc) this.rpc.destroy();
this.rpc = null;
this.statusBarIcon.tooltip = '';
if (activityTimer) clearInterval(activityTimer);
}
}

View file

@ -1,8 +1,51 @@
export const LIVE_SHARE_BASE_URL = 'insiders.liveshare.vsengsaas.visualstudio.com'; import LANG from './data/languages.json';
export const VSLS_EXTENSION_ID = 'ms-vsliveshare.vsliveshare';
export const LIVE_SHARE_COMMANDS = { export const CLIENT_ID = '383226320970055681' as const;
Start: 'liveshare.start',
End: 'liveshare.end', export const KNOWN_EXTENSIONS: { [key: string]: { image: string } } = LANG.KNOWN_EXTENSIONS;
Join: 'liveshare.join', export const KNOWN_LANGUAGES: { language: string; image: string }[] = LANG.KNOWN_LANGUAGES;
};
export const EMPTY = '\u200b\u200b';
export const FILE_SIZES = [' bytes', 'kb', 'mb', 'gb', 'tb'];
export const IDLE_IMAGE_KEY = 'vscode-big';
export const DEBUG_IMAGE_KEY = 'debug';
export const VSCODE_IMAGE_KEY = 'vscode';
export const VSCODE_INSIDERS_IMAGE_KEY = 'vscode-insiders';
export const enum REPLACE_KEYS {
Empty = '{empty}',
FileName = '{file_name}',
DirName = '{dir_name}',
FullDirName = '{full_dir_name}',
Workspace = '{workspace}',
WorkspaceFolder = '{workspace_folder}',
WorkspaceAndFolder = '{workspace_and_folder}',
LanguageLowerCase = '{lang}',
LanguageTitleCase = '{Lang}',
LanguageUpperCase = '{LANG}',
TotalLines = '{total_lines}',
CurrentLine = '{current_line}',
CurrentColumn = '{current_column}',
FileSize = '{file_size}',
AppName = '{app_name}',
GitRepoName = '{git_repo_name}',
GitBranch = '{git_branch}',
}
export const enum CONFIG_KEYS {
Enabled = 'enabled',
DetailsIdling = 'details_idling',
DetailsEditing = 'details_editing',
DetailsDebugging = 'details_debugging',
LowerDetailsIdling = 'lower_details_idling',
LowerDetailsEditing = 'lower_details_editing',
LowerDetailsDebugging = 'lower_details_debugging',
LowerDetailsNoWorkspaceFound = 'lower_details_no_workspace_found',
LargeImageIdling = 'large_image_idling',
LargeImage = 'large_image',
SmallImage = 'small_image',
SuppressNotifications = 'suppress_notifications',
WorkspaceElapsedTime = 'workspace_elapsed_time',
WorkspaceExcludePatterns = 'workspace_exclude_patterns',
}

View file

@ -1,520 +1,445 @@
{ {
"knownLanguages": [ "KNOWN_LANGUAGES": [
"ahk", { "language": "abap", "image": "text" },
"android", { "language": "bat", "image": "bat" },
"angular", { "language": "bibtex", "image": "text" },
"applescript", { "language": "clojure", "image": "clojure" },
"appveyor", { "language": "coffeescript", "image": "coffeescript" },
"arduino", { "language": "c", "image": "c" },
"as", { "language": "cpp", "image": "cpp" },
"asp", { "language": "csharp", "image": "csharp" },
"assembly", { "language": "css", "image": "css" },
"autoit", { "language": "diff", "image": "manifest" },
"babel", { "language": "dockerfile", "image": "docker" },
"bat", { "language": "fsharp", "image": "fsharp" },
"bower", { "language": "git-commit", "image": "manifest" },
"brainfuck", { "language": "git-rebase", "image": "manifest" },
"c", { "language": "go", "image": "go" },
"cargo", { "language": "groovy", "image": "groovy" },
"cfml", { "language": "handlebars", "image": "handlebars" },
"circleci", { "language": "haml", "image": "text" },
"clojure", { "language": "html", "image": "html" },
"cmake", { "language": "ini", "image": "manifest" },
"codeclimate", { "language": "java", "image": "java" },
"coffee", { "language": "javascript", "image": "javascript" },
"cpp", { "language": "javascriptreact", "image": "jsx" },
"crystal", { "language": "jsx", "image": "jsx" },
"csharp", { "language": "json", "image": "json" },
"cshtml", { "language": "jsonc", "image": "json" },
"css", { "language": "latex", "image": "text" },
"cuda", { "language": "less", "image": "less" },
"d", { "language": "lua", "image": "lua" },
"dart", { "language": "makefile", "image": "makefile" },
"delphi", { "language": "markdown", "image": "markdown" },
"denizen", { "language": "objective-c", "image": "objective-c" },
"dm", { "language": "objective-cpp", "image": "objective-c" },
"docker", { "language": "perl", "image": "perl" },
"editorconfig", { "language": "perl6", "image": "perl" },
"ejs", { "language": "php", "image": "php" },
"elixir", { "language": "plaintext", "image": "text" },
"elm", { "language": "powershell", "image": "powershell" },
"env", { "language": "jade", "image": "pug" },
"erlang", { "language": "pug", "image": "pug" },
"eslint", { "language": "python", "image": "python" },
"flowconfig", { "language": "r", "image": "r" },
"fsharp", { "language": "razor", "image": "cshtml" },
"gemfile", { "language": "ruby", "image": "ruby" },
"git", { "language": "rust", "image": "rust" },
"go", { "language": "scss", "image": "scss" },
"godot", { "language": "sass", "image": "scss" },
"gradle", { "language": "shaderlab", "image": "manifest" },
"graphql", { "language": "slim", "image": "text" },
"groovy", { "language": "sql", "image": "sql" },
"gruntfile", { "language": "stylus", "image": "stylus" },
"gulp", { "language": "swift", "image": "swift" },
"handlebars", { "language": "typescript", "image": "typescript" },
"harbour", { "language": "typescriptreact", "image": "tsx" },
"haskell", { "language": "tex", "image": "tex" },
"haxe", { "language": "vb", "image": "vb" },
"heroku", { "language": "vue", "image": "vue" },
"hjson", { "language": "vue-html", "image": "vue" },
"html", { "language": "xml", "image": "xml" },
"http", { "language": "xsl", "image": "xml" },
"ini", { "language": "yaml", "image": "yaml" }
"jar", ],
"java", "KNOWN_EXTENSIONS": {
"jest", ".ahk": { "image": "ahk" },
"jinja", ".ahkl": { "image": "ahk" },
"js", "androidmanifest.xml": { "image": "android" },
"jsmap", "/^angular[^.]*\\.js$/i": { "image": "angular" },
"json", ".applescript": { "image": "applescript" },
"jsx", "/(\\.)?appveyor\\.yml/i": { "image": "appveyor" },
"julia", ".ino": { "image": "arduino" },
"jupyter", ".swf": { "image": "as" },
"kotlin", ".as": { "image": "as" },
"less", ".jsfl": { "image": "as" },
"lisp", ".swc": { "image": "as" },
"livescript", ".asp": { "image": "asp" },
"log", ".asax": { "image": "asp" },
"lua", ".ascx": { "image": "asp" },
"makefile", ".ashx": { "image": "asp" },
"manifest", ".asmx": { "image": "asp" },
"markdown", ".aspx": { "image": "asp" },
"markdownx", ".axd": { "image": "asp" },
"marko", "/\\.(l?a|[ls]?o|out|s|a51|asm|axf|elf|prx|puff|z80)$/i": { "image": "assembly" },
"nim", ".agc": { "image": "assembly" },
"nix", ".ko": { "image": "assembly" },
"nodemon", ".lst": { "image": "assembly" },
"npm", "/\\.((c([+px]{2}?)?-?)?objdump|bsdiff|bin|dat|pak|pdb)$/i": { "image": "assembly" },
"objc", ".d-objdump": { "image": "assembly" },
"ocaml", "/\\.gcode|\\.gco/i": { "image": "assembly" },
"pascal", "/\\.rpy[bc]$/i": { "image": "assembly" },
"pawn", "/\\.py[co]$/i": { "image": "assembly" },
"perl", ".swp": { "image": "assembly" },
"php", ".DS_Store": { "image": "assembly" },
"ponylang", ".au3": { "image": "autoit" },
"postcss", "babel.config.js": { "image": "babel" },
"powershell", "/\\.babelrc/i": { "image": "babel" },
"prettier", ".bat": { "image": "bat" },
"prisma", ".batch": { "image": "bat" },
"processing", ".cmd": { "image": "bat" },
"pug", "/\\.(exe|com|msi)$/i": { "image": "bat" },
"purescript", ".reg": { "image": "bat" },
"python", "/^(\\.bowerrc|bower\\.json|Bowerfile)$/i": { "image": "bower" },
"r", "/\\.bf?$/i": { "image": "brainfuck" },
"reasonml", "/\\.c$/i": { "image": "c" },
"ruby", "/(cargo.toml|cargo.lock)/i": { "image": "cargo" },
"rust", ".cfc": { "image": "cfml" },
"scala", ".cfm": { "image": "cfml" },
"scss", "circle.yml": { "image": "circleci" },
"shell", ".clj": { "image": "clojure" },
"sqf", ".cl2": { "image": "clojure" },
"sql", ".cljc": { "image": "clojure" },
"stylus", ".cljx": { "image": "clojure" },
"svelte", ".hic": { "image": "clojure" },
"svg", "/\\.cljs(cm)?$/i": { "image": "clojure" },
"swift", ".cmake": { "image": "cmake" },
"tex", "/^CMakeLists\\.txt$/": { "image": "cmake" },
"text", "/\\.codeclimate\\.(yml|json)/i": { "image": "codeclimate" },
"toml", ".coffee": { "image": "coffeescript" },
"travis", ".cjsx": { "image": "coffeescript" },
"ts", ".coffee.ecr": { "image": "coffeescript" },
"tsx", ".coffee.erb": { "image": "coffeescript" },
"twig", ".litcoffee": { "image": "coffeescript" },
"typescript-def", ".iced": { "image": "coffeescript" },
"v", "/\\.c[+px]{2}$|\\.cc$/i": { "image": "cpp" },
"v", "/\\.h[+px]{2}$/i": { "image": "cpp" },
"vala", "/\\.[it]pp$/i": { "image": "cpp" },
"vb", "/\\.(tcc|inl)$/i": { "image": "cpp" },
"vba", ".cats": { "image": "cpp" },
"vbhtml", ".idc": { "image": "cpp" },
"vbproj", ".w": { "image": "cpp" },
"vcxproj", ".nc": { "image": "cpp" },
"vscodeignore", ".upc": { "image": "cpp" },
"vue", ".xpm": { "image": "cpp" },
"wasm", "/\\.e?cr$/i": { "image": "crystal" },
"webpack", ".cs": { "image": "csharp" },
"xml", ".csx": { "image": "csharp" },
"yaml", ".cshtml": { "image": "cshtml" },
"yarn", ".css": { "image": "css" },
"zig" ".css.map": { "image": "cssmap" },
], ".cu": { "image": "cuda" },
"knownExtensions": { "/\\.di?$/i": { "image": "d" },
".ahk": { "image": "ahk" }, ".dart": { "image": "dart" },
".ahkl": { "image": "ahk" }, ".dfm": { "image": "delphi" },
"androidmanifest.xml": { "image": "android" }, ".dpr": { "image": "delphi" },
"/^angular[^.]*\\.js$/i": { "image": "angular" }, ".dsc": { "image": "denizen" },
".applescript": { "image": "applescript" }, ".dm": { "image": "dm" },
"/(\\.)?appveyor\\.yml/i": { "image": "appveyor" }, ".dme": { "image": "dm" },
".ino": { "image": "arduino" }, ".dmm": { "image": "dm" },
".swf": { "image": "as" }, "/^(Dockerfile|docker-compose)|\\.docker(file|ignore)$/i": { "image": "docker" },
".as": { "image": "as" }, "/^docker-sync\\.yml$/i": { "image": "docker" },
".jsfl": { "image": "as" }, ".editorconfig": { "image": "editorconfig" },
".swc": { "image": "as" }, ".ejs": { "image": "ejs" },
".asp": { "image": "asp" }, ".ex": { "image": "elixir" },
".asax": { "image": "asp" }, "/\\.(exs|l?eex)$/i": { "image": "elixir" },
".ascx": { "image": "asp" }, "/^mix\\.(exs?|lock)$/i": { "image": "elixir" },
".ashx": { "image": "asp" }, ".elm": { "image": "elm" },
".asmx": { "image": "asp" }, ".env": { "image": "env" },
".aspx": { "image": "asp" }, ".erl": { "image": "erlang" },
".axd": { "image": "asp" }, ".beam": { "image": "erlang" },
"/\\.(l?a|[ls]?o|out|s|a51|asm|axf|elf|prx|puff|z80)$/i": { "image": "assembly" }, ".hrl": { "image": "erlang" },
".agc": { "image": "assembly" }, ".xrl": { "image": "erlang" },
".ko": { "image": "assembly" }, ".yrl": { "image": "erlang" },
".lst": { "image": "assembly" }, ".app.src": { "image": "erlang" },
"/\\.((c([+px]{2}?)?-?)?objdump|bsdiff|bin|dat|pak|pdb)$/i": { "image": "assembly" }, "/^Emakefile$/": { "image": "erlang" },
".d-objdump": { "image": "assembly" }, "/^rebar(\\.config)?\\.lock$/i": { "image": "erlang" },
"/\\.gcode|\\.gco/i": { "image": "assembly" }, "/\\.(eslintrc|eslintignore)/i": { "image": "eslint" },
"/\\.rpy[bc]$/i": { "image": "assembly" }, "/(\\.firebaserc|firebase\\.json)/i": { "image": "firebase" },
"/\\.py[co]$/i": { "image": "assembly" }, ".flowconfig": { "image": "flowconfig" },
".swp": { "image": "assembly" }, ".fs": { "image": "fsharp" },
".DS_Store": { "image": "assembly" }, ".fsi": { "image": "fsharp" },
".au3": { "image": "autoit" }, ".fsscript": { "image": "fsharp" },
"babel.config.js": { "image": "babel" }, ".fsx": { "image": "fsharp" },
"/\\.babelrc/i": { "image": "babel" }, "/gatsby-(browser|node|ssr|config)\\.js/i": { "image": "gatsbyjs" },
".bat": { "image": "bat" }, "/^Gemfile(\\.lock)?$/i": { "image": "gemfile" },
".batch": { "image": "bat" }, "/^\\.git|^\\.keep$|\\.mailmap$/i": { "image": "git" },
".cmd": { "image": "bat" }, ".go": { "image": "go" },
"/\\.(exe|com|msi)$/i": { "image": "bat" }, ".gd": { "image": "godot" },
".reg": { "image": "bat" }, ".gradle": { "image": "gradle" },
"/^(\\.bowerrc|bower\\.json|Bowerfile)$/i": { "image": "bower" }, "gradlew": { "image": "gradle" },
"/\\.bf?$/i": { "image": "brainfuck" }, ".gql": { "image": "graphql" },
"/\\.c$/i": { "image": "c" }, ".graphql": { "image": "graphql" },
"/(cargo.toml|cargo.lock)/i": { "image": "cargo" }, ".groovy": { "image": "groovy" },
".cfc": { "image": "cfml" }, ".gvy": { "image": "groovy" },
".cfm": { "image": "cfml" }, ".gy": { "image": "groovy" },
"circle.yml": { "image": "circleci" }, ".gsh": { "image": "groovy" },
".clj": { "image": "clojure" }, "/gruntfile\\.(js|coffee)/i": { "image": "gruntfile" },
".cl2": { "image": "clojure" }, "gulpfile.js": { "image": "gulp" },
".cljc": { "image": "clojure" }, "/\\.(hbs|handlebars|(mu)?stache)$/i": { "image": "handlebars" },
".cljx": { "image": "clojure" }, ".prg": { "image": "harbour" },
".hic": { "image": "clojure" }, ".hbp": { "image": "harbour" },
"/\\.cljs(cm)?$/i": { "image": "clojure" }, ".hbc": { "image": "harbour" },
".cmake": { "image": "cmake" }, ".rc": { "image": "harbour" },
"/^CMakeLists\\.txt$/": { "image": "cmake" }, ".fmg": { "image": "harbour" },
"/\\.codeclimate\\.(yml|json)/i": { "image": "codeclimate" }, ".hs": { "image": "haskell" },
".coffee": { "image": "coffee" }, ".hsc": { "image": "haskell" },
".cjsx": { "image": "coffee" }, ".c2hs": { "image": "haskell" },
".coffee.ecr": { "image": "coffee" }, ".lhs": { "image": "haskell" },
".coffee.erb": { "image": "coffee" }, ".hx": { "image": "haxe" },
".litcoffee": { "image": "coffee" }, ".hxml": { "image": "haxe" },
".iced": { "image": "coffee" }, "/^procfile/i": { "image": "heroku" },
"/\\.c[+px]{2}$|\\.cc$/i": { "image": "cpp" }, "heroku.yml": { "image": "heroku" },
"/\\.h[+px]{2}$/i": { "image": "cpp" }, ".hjson": { "image": "hjson" },
"/\\.[it]pp$/i": { "image": "cpp" }, "/\\.x?html?$/i": { "image": "html" },
"/\\.(tcc|inl)$/i": { "image": "cpp" }, ".http": { "image": "http" },
".cats": { "image": "cpp" }, ".rest": { "image": "http" },
".idc": { "image": "cpp" }, ".jar": { "image": "jar" },
".w": { "image": "cpp" }, ".java": { "image": "java" },
".nc": { "image": "cpp" }, "jest.config.js": { "image": "jest" },
".upc": { "image": "cpp" }, ".jinja": { "image": "jinja" },
".xpm": { "image": "cpp" }, ".js": { "image": "javascript" },
"/\\.e?cr$/i": { "image": "crystal" }, ".es6": { "image": "javascript" },
".cs": { "image": "csharp" }, ".es": { "image": "javascript" },
".csx": { "image": "csharp" }, ".mjs": { "image": "javascript" },
".cshtml": { "image": "cshtml" }, ".js.map": { "image": "jsmap" },
".css": { "image": "css" }, ".json": { "image": "json" },
".css.map": { "image": "cssmap" }, ".jsonc": { "image": "json" },
".cu": { "image": "cuda" }, ".jsx": { "image": "jsx" },
"/\\.di?$/i": { "image": "d" }, "/\\.(jil|jl)/i": { "image": "julia" },
".dart": { "image": "dart" }, ".ipynb": { "image": "jupyter" },
".dfm": { "image": "delphi" }, ".kt": { "image": "kotlin" },
".dpr": { "image": "delphi" }, ".ktm": { "image": "kotlin" },
".dsc": { "image": "denizen" }, ".kts": { "image": "kotlin" },
".dm": { "image": "dm" }, ".less": { "image": "less" },
".dme": { "image": "dm" }, ".lsp": { "image": "lisp" },
".dmm": { "image": "dm" }, ".lisp": { "image": "lisp" },
"/^(Dockerfile|docker-compose)|\\.docker(file|ignore)$/i": { "image": "docker" }, ".l": { "image": "lisp" },
"/^docker-sync\\.yml$/i": { "image": "docker" }, ".nl": { "image": "lisp" },
".editorconfig": { "image": "editorconfig" }, ".ny": { "image": "lisp" },
".ejs": { "image": "ejs" }, ".podsl": { "image": "lisp" },
".ex": { "image": "elixir" }, ".sexp": { "image": "lisp" },
"/\\.(exs|l?eex)$/i": { "image": "elixir" }, ".ls": { "image": "livescript" },
"/^mix\\.(exs?|lock)$/i": { "image": "elixir" }, ".log": { "image": "log" },
".elm": { "image": "elm" }, ".lua": { "image": "lua" },
".env": { "image": "env" }, ".pd_lua": { "image": "lua" },
".erl": { "image": "erlang" }, ".rbxs": { "image": "lua" },
".beam": { "image": "erlang" }, ".wlua": { "image": "lua" },
".hrl": { "image": "erlang" }, "/^Makefile/": { "image": "makefile" },
".xrl": { "image": "erlang" }, "/^mk\\.config$/": { "image": "makefile" },
".yrl": { "image": "erlang" }, "/\\.(mk|mak|make)$/i": { "image": "makefile" },
".app.src": { "image": "erlang" }, "/^BSDmakefile$/i": { "image": "makefile" },
"/^Emakefile$/": { "image": "erlang" }, "/^GNUmakefile$/i": { "image": "makefile" },
"/^rebar(\\.config)?\\.lock$/i": { "image": "erlang" }, "/^makefile\\.sco$/i": { "image": "makefile" },
"/\\.(eslintrc|eslintignore)/i": { "image": "eslint" }, "/^Kbuild$/": { "image": "makefile" },
"/(\\.firebaserc|firebase\\.json)/i": { "image": "firebase" }, "/^makefile$/": { "image": "makefile" },
".flowconfig": { "image": "flowconfig" }, "/^mkfile$/i": { "image": "makefile" },
".fs": { "image": "fsharp" }, "/^\\.?qmake$/i": { "image": "makefile" },
".fsi": { "image": "fsharp" }, "/\\.(h|geo|topo)$/i": { "image": "manifest" },
".fsscript": { "image": "fsharp" }, ".cson": { "image": "manifest" },
".fsx": { "image": "fsharp" }, ".json5": { "image": "manifest" },
"/gatsby-(browser|node|ssr|config)\\.js/i": { "image": "gatsbyjs" }, ".ndjson": { "image": "manifest" },
"/^Gemfile(\\.lock)?$/i": { "image": "gemfile" }, ".fea": { "image": "manifest" },
"/^\\.git|^\\.keep$|\\.mailmap$/i": { "image": "git" }, ".json.eex": { "image": "manifest" },
".go": { "image": "go" }, ".proto": { "image": "manifest" },
".gd": { "image": "godot" }, ".pytb": { "image": "manifest" },
".gradle": { "image": "gradle" }, ".pydeps": { "image": "manifest" },
"gradlew": { "image": "gradle" }, "/\\.pot?$/i": { "image": "manifest" },
".gql": { "image": "graphql" }, ".ejson": { "image": "manifest" },
".graphql": { "image": "graphql" }, ".edn": { "image": "manifest" },
".groovy": { "image": "groovy" }, ".eam.fs": { "image": "manifest" },
".gvy": { "image": "groovy" }, ".qml": { "image": "manifest" },
".gy": { "image": "groovy" }, ".qbs": { "image": "manifest" },
".gsh": { "image": "groovy" }, ".ston": { "image": "manifest" },
"/gruntfile\\.(js|coffee)/i": { "image": "gruntfile" }, ".ttl": { "image": "manifest" },
"gulpfile.js": { "image": "gulp" }, ".rviz": { "image": "manifest" },
"/\\.(hbs|handlebars|(mu)?stache)$/i": { "image": "handlebars" }, ".syntax": { "image": "manifest" },
".prg": { "image": "harbour" }, ".webmanifest": { "image": "manifest" },
".hbp": { "image": "harbour" }, "/^pkginfo$/": { "image": "manifest" },
".hbc": { "image": "harbour" }, "/^mime\\.types$/i": { "image": "manifest" },
".rc": { "image": "harbour" }, "/^METADATA\\.pb$/": { "image": "manifest" },
".fmg": { "image": "harbour" }, "/[\\/\\\\](?:magic[\\/\\\\]Magdir|file[\\/\\\\]magic)[\\/\\\\][-.\\w]+$/i": { "image": "manifest" },
".hs": { "image": "haskell" }, "/(\\\\|\\/)dev[-\\w]+\\1(?:[^\\\\\\/]+\\1)*(?!DESC|NOTES)(?:[A-Z][-A-Z]*)(?:\\.in)?$/": { "image": "manifest" },
".hsc": { "image": "haskell" }, "lib/icons/.icondb.js": { "image": "manifest" },
".c2hs": { "image": "haskell" }, "/\\.git[\\/\\\\](.*[\\/\\\\])?(HEAD|ORIG_HEAD|packed-refs|logs[\\/\\\\](.+[\\/\\\\])?[^\\/\\\\]+)$/": {
".lhs": { "image": "haskell" }, "image": "manifest"
".hx": { "image": "haxe" }, },
".hxml": { "image": "haxe" }, "/\\.(md|mdown|markdown|mkd|mkdown|mdwn|mkdn|rmd|ron|pmd)$/i": { "image": "markdown" },
"/^procfile/i": { "image": "heroku" }, ".mdx": { "image": "markdownx" },
"heroku.yml": { "image": "heroku" }, ".marko": { "image": "marko" },
".hjson": { "image": "hjson" }, ".nim": { "image": "nim" },
"/\\.x?html?$/i": { "image": "html" }, ".nims": { "image": "nim" },
".http": { "image": "http" }, ".nimble": { "image": "nim" },
".rest": { "image": "http" }, ".nix": { "image": "nix" },
".jar": { "image": "jar" }, "nodemon.json": { "image": "nodemon" },
".java": { "image": "java" }, ".npmrc": { "image": "npm" },
"jest.config.js": { "image": "jest" }, "/\\.mm?$/i": { "image": "objective-c" },
".jinja": { "image": "jinja" }, ".pch": { "image": "objective-c" },
".js": { "image": "js" }, ".x": { "image": "objective-c" },
".es6": { "image": "js" }, ".ml": { "image": "ocaml" },
".es": { "image": "js" }, ".mli": { "image": "ocaml" },
".mjs": { "image": "js" }, ".eliom": { "image": "ocaml" },
".js.map": { "image": "jsmap" }, ".eliomi": { "image": "ocaml" },
".json": { "image": "json" }, ".ml4": { "image": "ocaml" },
".jsonc": { "image": "json" }, ".mll": { "image": "ocaml" },
".jsx": { "image": "jsx" }, ".mly": { "image": "ocaml" },
"/\\.(jil|jl)/i": { "image": "julia" }, "/\\.pas(cal)?$/i": { "image": "pascal" },
".ipynb": { "image": "jupyter" }, ".lpr": { "image": "pascal" },
".kt": { "image": "kotlin" }, ".p": { "image": "pawn" },
".ktm": { "image": "kotlin" }, ".inc": { "image": "pawn" },
".kts": { "image": "kotlin" }, ".sma": { "image": "pawn" },
".less": { "image": "less" }, ".pwn": { "image": "pawn" },
".lsp": { "image": "lisp" }, ".sp": { "image": "pawn" },
".lisp": { "image": "lisp" }, "/\\.p(er)?l$/i": { "image": "perl" },
".l": { "image": "lisp" }, ".al": { "image": "perl" },
".nl": { "image": "lisp" }, ".ph": { "image": "perl" },
".ny": { "image": "lisp" }, ".plx": { "image": "perl" },
".podsl": { "image": "lisp" }, ".pm": { "image": "perl" },
".sexp": { "image": "lisp" }, "/\\.(psgi|xs)$/i": { "image": "perl" },
".ls": { "image": "livescript" }, ".pl6": { "image": "perl" },
".log": { "image": "log" }, "/\\.[tp]6$|\\.6pl$/i": { "image": "perl" },
".lua": { "image": "lua" }, "/\\.(pm6|p6m)$/i": { "image": "perl" },
".pd_lua": { "image": "lua" }, ".6pm": { "image": "perl" },
".rbxs": { "image": "lua" }, ".nqp": { "image": "perl" },
".wlua": { "image": "lua" }, ".p6l": { "image": "perl" },
"/^Makefile/": { "image": "makefile" }, ".pod6": { "image": "perl" },
"/^mk\\.config$/": { "image": "makefile" }, "/^Rexfile$/": { "image": "perl" },
"/\\.(mk|mak|make)$/i": { "image": "makefile" }, "/\\.php([st\\d]|_cs)?$/i": { "image": "php" },
"/^BSDmakefile$/i": { "image": "makefile" }, "/^Phakefile/": { "image": "php" },
"/^GNUmakefile$/i": { "image": "makefile" }, ".pony": { "image": "ponylang" },
"/^makefile\\.sco$/i": { "image": "makefile" }, ".pcss": { "image": "postcss" },
"/^Kbuild$/": { "image": "makefile" }, ".ps1": { "image": "powershell" },
"/^makefile$/": { "image": "makefile" }, ".psd1": { "image": "powershell" },
"/^mkfile$/i": { "image": "makefile" }, ".psm1": { "image": "powershell" },
"/^\\.?qmake$/i": { "image": "makefile" }, ".ps1xml": { "image": "powershell" },
"/\\.(h|geo|topo)$/i": { "image": "manifest" }, ".prettierignore": { "image": "prettier" },
".cson": { "image": "manifest" }, "/\\.prettier((rc)|(\\.(toml|yml|yaml|json|js))?$){2}/i": { "image": "prettier" },
".json5": { "image": "manifest" }, "prettier.config.js": { "image": "prettier" },
".ndjson": { "image": "manifest" }, "prisma.yml": { "image": "prisma" },
".fea": { "image": "manifest" }, ".pde": { "image": "processing" },
".json.eex": { "image": "manifest" }, ".jade": { "image": "pug" },
".proto": { "image": "manifest" }, ".pug": { "image": "pug" },
".pytb": { "image": "manifest" }, ".purs": { "image": "purescript" },
".pydeps": { "image": "manifest" }, ".py": { "image": "python" },
"/\\.pot?$/i": { "image": "manifest" }, ".ipy": { "image": "python" },
".ejson": { "image": "manifest" }, ".isolate": { "image": "python" },
".edn": { "image": "manifest" }, ".pep": { "image": "python" },
".eam.fs": { "image": "manifest" }, ".gyp": { "image": "python" },
".qml": { "image": "manifest" }, ".gypi": { "image": "python" },
".qbs": { "image": "manifest" }, ".pyde": { "image": "python" },
".ston": { "image": "manifest" }, ".pyp": { "image": "python" },
".ttl": { "image": "manifest" }, ".pyt": { "image": "python" },
".rviz": { "image": "manifest" }, ".py3": { "image": "python" },
".syntax": { "image": "manifest" }, ".pyi": { "image": "python" },
".webmanifest": { "image": "manifest" }, ".pyw": { "image": "python" },
"/^pkginfo$/": { "image": "manifest" }, ".tac": { "image": "python" },
"/^mime\\.types$/i": { "image": "manifest" }, ".wsgi": { "image": "python" },
"/^METADATA\\.pb$/": { "image": "manifest" }, ".xpy": { "image": "python" },
"/[\\/\\\\](?:magic[\\/\\\\]Magdir|file[\\/\\\\]magic)[\\/\\\\][-.\\w]+$/i": { "image": "manifest" }, ".rpy": { "image": "python" },
"/(\\\\|\\/)dev[-\\w]+\\1(?:[^\\\\\\/]+\\1)*(?!DESC|NOTES)(?:[A-Z][-A-Z]*)(?:\\.in)?$/": { "image": "manifest" }, "/\\.?(pypirc|pythonrc|python-venv)$/i": { "image": "python" },
"lib/icons/.icondb.js": { "image": "manifest" }, "/^(SConstruct|SConscript)$/": { "image": "python" },
"/\\.git[\\/\\\\](.*[\\/\\\\])?(HEAD|ORIG_HEAD|packed-refs|logs[\\/\\\\](.+[\\/\\\\])?[^\\/\\\\]+)$/": { "/^(Snakefile|WATCHLISTS)$/": { "image": "python" },
"image": "manifest" "/^wscript$/": { "image": "python" },
}, "/\\.(r|Rprofile|rsx|rd)$/i": { "image": "r" },
"/\\.(md|mdown|markdown|mkd|mkdown|mdwn|mkdn|rmd|ron|pmd)$/i": { "image": "markdown" }, ".re": { "image": "reasonml" },
".mdx": { "image": "markdownx" }, "/\\.(rb|ru|ruby|erb|gemspec|god|mspec|pluginspec|podspec|rabl|rake|opal)$/i": { "image": "ruby" },
".marko": { "image": "marko" }, "/^\\.?(irbrc|gemrc|pryrc|ruby-(gemset|version))$/i": { "image": "ruby" },
".nim": { "image": "nim" }, "/^(Appraisals|(Rake|[bB]uild|Cap|Danger|Deliver|Fast|Guard|Jar|Maven|Pod|Puppet|Snap)file(\\.lock)?)$/": {
".nims": { "image": "nim" }, "image": "ruby"
".nimble": { "image": "nim" }, },
".nix": { "image": "nix" }, "/\\.(jbuilder|rbuild|rb[wx]|builder)$/i": { "image": "ruby" },
"nodemon.json": { "image": "nodemon" }, "/^rails$/": { "image": "ruby" },
".npmrc": { "image": "npm" }, ".watchr": { "image": "ruby" },
"/\\.mm?$/i": { "image": "objc" }, ".rs": { "image": "rust" },
".pch": { "image": "objc" }, "/\\.(sc|scala)$/i": { "image": "scala" },
".x": { "image": "objc" }, ".scss": { "image": "scss" },
".ml": { "image": "ocaml" }, ".sass": { "image": "scss" },
".mli": { "image": "ocaml" }, "/\\.(sh|rc|bats|bash|tool|install|command)$/i": { "image": "shell" },
".eliom": { "image": "ocaml" }, "/^(\\.?bash(rc|[-_]?(profile|login|logout|history|prompt))|_osc|config|install-sh|PKGBUILD)$/i": {
".eliomi": { "image": "ocaml" }, "image": "shell"
".ml4": { "image": "ocaml" }, },
".mll": { "image": "ocaml" }, "/\\.(ksh|mksh|pdksh)$/i": { "image": "shell" },
".mly": { "image": "ocaml" }, ".sh-session": { "image": "shell" },
"/\\.pas(cal)?$/i": { "image": "pascal" }, "/\\.zsh(-theme|_history)?$|^\\.?(antigen|zpreztorc|zlogin|zlogout|zprofile|zshenv|zshrc)$/i": { "image": "shell" },
".lpr": { "image": "pascal" }, "/\\.fish$|^\\.fishrc$/i": { "image": "shell" },
".p": { "image": "pawn" }, "/^\\.?(login|profile)$/": { "image": "shell" },
".inc": { "image": "pawn" }, ".inputrc": { "image": "shell" },
".sma": { "image": "pawn" }, ".tmux": { "image": "shell" },
".pwn": { "image": "pawn" }, "/^(configure|config\\.(guess|rpath|status|sub)|depcomp|libtool|compile)$/": { "image": "shell" },
".sp": { "image": "pawn" }, "/^\\/(private\\/)?etc\\/([^\\/]+\\/)*(profile$|nanorc$|rc\\.|csh\\.)/i": { "image": "shell" },
"/\\.p(er)?l$/i": { "image": "perl" }, "/^\\.?cshrc$/i": { "image": "shell" },
".al": { "image": "perl" }, ".profile": { "image": "shell" },
".ph": { "image": "perl" }, ".tcsh": { "image": "shell" },
".plx": { "image": "perl" }, ".csh": { "image": "shell" },
".pm": { "image": "perl" }, ".sqf": { "image": "sqf" },
"/\\.(psgi|xs)$/i": { "image": "perl" }, "/\\.(my)?sql$/i": { "image": "sql" },
".pl6": { "image": "perl" }, ".ddl": { "image": "sql" },
"/\\.[tp]6$|\\.6pl$/i": { "image": "perl" }, ".udf": { "image": "sql" },
"/\\.(pm6|p6m)$/i": { "image": "perl" }, ".hql": { "image": "sql" },
".6pm": { "image": "perl" }, ".viw": { "image": "sql" },
".nqp": { "image": "perl" }, ".prc": { "image": "sql" },
".p6l": { "image": "perl" }, ".cql": { "image": "sql" },
".pod6": { "image": "perl" }, ".db2": { "image": "sql" },
"/^Rexfile$/": { "image": "perl" }, "/\\.(styl|stylus)$/i": { "image": "stylus" },
"/\\.php([st\\d]|_cs)?$/i": { "image": "php" }, ".svelte": { "image": "svelte" },
"/^Phakefile/": { "image": "php" }, ".svg": { "image": "svg" },
".pony": { "image": "ponylang" }, ".swift": { "image": "swift" },
".pcss": { "image": "postcss" }, ".tex": { "image": "tex" },
".ps1": { "image": "powershell" }, ".ltx": { "image": "tex" },
".psd1": { "image": "powershell" }, ".aux": { "image": "tex" },
".psm1": { "image": "powershell" }, ".sty": { "image": "tex" },
".ps1xml": { "image": "powershell" }, ".dtx": { "image": "tex" },
".prettierignore": { "image": "prettier" }, ".cls": { "image": "tex" },
"/\\.prettier((rc)|(\\.(toml|yml|yaml|json|js))?$){2}/i": { "image": "prettier" }, ".ins": { "image": "tex" },
"prettier.config.js": { "image": "prettier" }, ".lbx": { "image": "tex" },
"prisma.yml": { "image": "prisma" }, ".mkiv": { "image": "tex" },
".pde": { "image": "processing" }, ".mkvi": { "image": "tex" },
".jade": { "image": "pug" }, ".mkii": { "image": "tex" },
".pug": { "image": "pug" }, ".texi": { "image": "tex" },
".purs": { "image": "purescript" }, "/^hyphen(ex)?\\.(cs|den|det|fr|sv|us)$/": { "image": "tex" },
".py": { "image": "python" }, "/\\.te?xt$/i": { "image": "text" },
".ipy": { "image": "python" }, ".rtf": { "image": "text" },
".isolate": { "image": "python" }, "/\\.i?nfo$/i": { "image": "text" },
".pep": { "image": "python" }, ".msg": { "image": "text" },
".gyp": { "image": "python" }, "/\\.(utxt|utf8)$/i": { "image": "text" },
".gypi": { "image": "python" }, ".toml": { "image": "toml" },
".pyde": { "image": "python" }, ".travis.yml": { "image": "travis" },
".pyp": { "image": "python" }, ".ts": { "image": "typescript" },
".pyt": { "image": "python" }, ".tsx": { "image": "tsx" },
".py3": { "image": "python" }, ".twig": { "image": "twig" },
".pyi": { "image": "python" }, "/.*\\.d\\.ts/i": { "image": "typescript-def" },
".pyw": { "image": "python" }, ".v": { "image": "v" },
".tac": { "image": "python" }, ".vh": { "image": "v" },
".wsgi": { "image": "python" }, ".vala": { "image": "vala" },
".xpy": { "image": "python" }, ".vapi": { "image": "vala" },
".rpy": { "image": "python" }, ".vb": { "image": "vb" },
"/\\.?(pypirc|pythonrc|python-venv)$/i": { "image": "python" }, ".vbs": { "image": "vb" },
"/^(SConstruct|SConscript)$/": { "image": "python" }, ".vbhtml": { "image": "vb" },
"/^(Snakefile|WATCHLISTS)$/": { "image": "python" }, ".vbproj": { "image": "vb" },
"/^wscript$/": { "image": "python" }, ".vba": { "image": "vba" },
"/\\.(r|Rprofile|rsx|rd)$/i": { "image": "r" }, ".vcxproj": { "image": "vcxproj" },
".re": { "image": "reasonml" }, ".vscodeignore": { "image": "vscodeignore" },
"/\\.(rb|ru|ruby|erb|gemspec|god|mspec|pluginspec|podspec|rabl|rake|opal)$/i": { "image": "ruby" }, ".vue": { "image": "vue" },
"/^\\.?(irbrc|gemrc|pryrc|ruby-(gemset|version))$/i": { "image": "ruby" }, ".wat": { "image": "wasm" },
"/^(Appraisals|(Rake|[bB]uild|Cap|Danger|Deliver|Fast|Guard|Jar|Maven|Pod|Puppet|Snap)file(\\.lock)?)$/": { ".wast": { "image": "wasm" },
"image": "ruby" ".wasm": { "image": "wasm" },
}, "/webpack(\\.dev|\\.development|\\.prod|\\.production)?\\.config(\\.babel)?\\.(js|jsx|coffee|ts|json|json5|yaml|yml)/i": {
"/\\.(jbuilder|rbuild|rb[wx]|builder)$/i": { "image": "ruby" }, "image": "webpack"
"/^rails$/": { "image": "ruby" }, },
".watchr": { "image": "ruby" }, ".xml": { "image": "xml" },
".rs": { "image": "rust" }, "/\\.ya?ml$/i": { "image": "yaml" },
"/\\.(sc|scala)$/i": { "image": "scala" }, "/^yarn(\\.lock)?$/i": { "image": "yarn" },
".scss": { "image": "scss" }, ".yarnrc": { "image": "yarn" },
".sass": { "image": "scss" }, ".zig": { "image": "zig" }
"/\\.(sh|rc|bats|bash|tool|install|command)$/i": { "image": "shell" }, }
"/^(\\.?bash(rc|[-_]?(profile|login|logout|history|prompt))|_osc|config|install-sh|PKGBUILD)$/i": {
"image": "shell"
},
"/\\.(ksh|mksh|pdksh)$/i": { "image": "shell" },
".sh-session": { "image": "shell" },
"/\\.zsh(-theme|_history)?$|^\\.?(antigen|zpreztorc|zlogin|zlogout|zprofile|zshenv|zshrc)$/i": { "image": "shell" },
"/\\.fish$|^\\.fishrc$/i": { "image": "shell" },
"/^\\.?(login|profile)$/": { "image": "shell" },
".inputrc": { "image": "shell" },
".tmux": { "image": "shell" },
"/^(configure|config\\.(guess|rpath|status|sub)|depcomp|libtool|compile)$/": { "image": "shell" },
"/^\\/(private\\/)?etc\\/([^\\/]+\\/)*(profile$|nanorc$|rc\\.|csh\\.)/i": { "image": "shell" },
"/^\\.?cshrc$/i": { "image": "shell" },
".profile": { "image": "shell" },
".tcsh": { "image": "shell" },
".csh": { "image": "shell" },
".sqf": { "image": "sqf" },
"/\\.(my)?sql$/i": { "image": "sql" },
".ddl": { "image": "sql" },
".udf": { "image": "sql" },
".hql": { "image": "sql" },
".viw": { "image": "sql" },
".prc": { "image": "sql" },
".cql": { "image": "sql" },
".db2": { "image": "sql" },
"/\\.(styl|stylus)$/i": { "image": "stylus" },
".svelte": { "image": "svelte" },
".svg": { "image": "svg" },
".swift": { "image": "swift" },
".tex": { "image": "tex" },
".ltx": { "image": "tex" },
".aux": { "image": "tex" },
".sty": { "image": "tex" },
".dtx": { "image": "tex" },
".cls": { "image": "tex" },
".ins": { "image": "tex" },
".lbx": { "image": "tex" },
".mkiv": { "image": "tex" },
".mkvi": { "image": "tex" },
".mkii": { "image": "tex" },
".texi": { "image": "tex" },
"/^hyphen(ex)?\\.(cs|den|det|fr|sv|us)$/": { "image": "tex" },
"/\\.te?xt$/i": { "image": "text" },
".rtf": { "image": "text" },
"/\\.i?nfo$/i": { "image": "text" },
".msg": { "image": "text" },
"/\\.(utxt|utf8)$/i": { "image": "text" },
".toml": { "image": "toml" },
".travis.yml": { "image": "travis" },
".ts": { "image": "ts" },
".tsx": { "image": "tsx" },
".twig": { "image": "twig" },
"/.*\\.d\\.ts/i": { "image": "typescript-def" },
".v": { "image": "v" },
".vh": { "image": "v" },
".vala": { "image": "vala" },
".vapi": { "image": "vala" },
".vb": { "image": "vb" },
".vbs": { "image": "vb" },
".vbhtml": { "image": "vb" },
".vbproj": { "image": "vb" },
".vba": { "image": "vba" },
".vcxproj": { "image": "vcxproj" },
".vscodeignore": { "image": "vscodeignore" },
".vue": { "image": "vue" },
".wat": { "image": "wasm" },
".wast": { "image": "wasm" },
".wasm": { "image": "wasm" },
"/webpack(\\.dev|\\.development|\\.prod|\\.production)?\\.config(\\.babel)?\\.(js|jsx|coffee|ts|json|json5|yaml|yml)/i": { "image": "webpack" },
".xml": { "image": "xml" },
"/\\.ya?ml$/i": { "image": "yaml" },
"/^yarn(\\.lock)?$/i": { "image": "yarn" },
".yarnrc": { "image": "yarn" },
".zig": { "image": "zig" }
}
} }

View file

@ -1,112 +1,118 @@
import { commands, ExtensionContext, StatusBarAlignment, StatusBarItem, window, workspace, extensions } from 'vscode'; const { Client } = require('discord-rpc'); // eslint-disable-line
import RPCClient from './client/RPCClient'; import {
import Logger from './structures/Logger'; commands,
import { GitExtension } from './git'; ExtensionContext,
const { register } = require('discord-rpc'); // eslint-disable-line StatusBarAlignment,
StatusBarItem,
window,
workspace,
extensions,
debug,
} from 'vscode';
import { activity } from './activity';
let loginTimeout: NodeJS.Timer | undefined; import { CLIENT_ID, CONFIG_KEYS } from './constants';
import { GitExtension } from './git';
import { log, LogLevel } from './logger';
import { getConfig } from './util';
const statusBarIcon: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); const statusBarIcon: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
statusBarIcon.text = '$(pulse) Connecting to Discord...'; statusBarIcon.text = '$(pulse) Connecting to Discord...';
const clientId = '383226320970055681'; const rpc = new Client({ transport: 'ipc' });
const config = workspace.getConfiguration('discord'); const config = getConfig();
register(clientId);
const rpc = new RPCClient(clientId, statusBarIcon);
export async function activate(context: ExtensionContext) { async function sendActivity() {
Logger.log('Discord Presence activated!'); rpc.setActivity(await activity());
}
async function login(context: ExtensionContext) {
rpc.once('ready', () => {
log(LogLevel.Info, 'Successfully connected to Discord');
statusBarIcon.text = '$(globe) Connected to Discord';
statusBarIcon.tooltip = 'Connected to Discord';
void sendActivity();
const onChangeActiveTextEditor = window.onDidChangeActiveTextEditor(() => sendActivity());
const onChangeTextDocument = workspace.onDidChangeTextDocument(() => sendActivity());
const onStartDebugSession = debug.onDidStartDebugSession(() => sendActivity());
const onTerminateDebugSession = debug.onDidTerminateDebugSession(() => sendActivity());
context.subscriptions.push(
onChangeActiveTextEditor,
onChangeTextDocument,
onStartDebugSession,
onTerminateDebugSession,
);
});
try {
await rpc.login({ clientId: CLIENT_ID });
} catch (error) {
log(LogLevel.Error, `Encountered following error while trying to login:\n${error as string}`);
rpc.dispose();
if (!config[CONFIG_KEYS.SuppressNotifications]) {
if (error?.message?.includes('ENOENT')) void window.showErrorMessage('No Discord client detected');
else void window.showErrorMessage(`Couldn't connect to Discord via RPC: ${error as string}`);
}
rpc.statusBarIcon.text = '$(pulse) Reconnect to Discord';
rpc.statusBarIcon.command = 'discord.reconnect';
}
}
export function activate(context: ExtensionContext) {
log(LogLevel.Info, 'Discord Presence activated');
let isWorkspaceExcluded = false; let isWorkspaceExcluded = false;
const excludePatterns = config.get<string[]>('workspaceExcludePatterns'); for (const pattern of config[CONFIG_KEYS.WorkspaceExcludePatterns]) {
if (excludePatterns?.length) { const regex = new RegExp(pattern);
for (const pattern of excludePatterns) { const folders = workspace.workspaceFolders;
const regex = new RegExp(pattern); if (!folders) break;
const folders = workspace.workspaceFolders; if (folders.some((folder) => regex.test(folder.uri.fsPath))) {
if (!folders) break; isWorkspaceExcluded = true;
if (folders.some((folder) => regex.test(folder.uri.fsPath))) { break;
isWorkspaceExcluded = true;
break;
}
} }
} }
const enabler = commands.registerCommand('discord.enable', () => { const enabler = commands.registerCommand('discord.enable', () => {
rpc.dispose(); rpc.destroy();
void config.update('enabled', true); void config.update('enabled', true);
rpc.config = workspace.getConfiguration('discord'); statusBarIcon.text = '$(pulse) Connecting to Discord...';
rpc.statusBarIcon.text = '$(pulse) Connecting to Discord...'; statusBarIcon.show();
rpc.statusBarIcon.show(); void login(context);
void rpc.login(); void window.showInformationMessage('Enabled Discord Presence for this workspace');
void window.showInformationMessage('Enabled Discord Rich Presence for this workspace.');
}); });
const disabler = commands.registerCommand('discord.disable', () => { const disabler = commands.registerCommand('discord.disable', () => {
void config.update('enabled', false); void config.update('enabled', false);
rpc.config = workspace.getConfiguration('discord'); rpc.destroy();
rpc.dispose();
rpc.statusBarIcon.hide(); rpc.statusBarIcon.hide();
void window.showInformationMessage('Disabled Discord Rich Presence for this workspace.'); void window.showInformationMessage('Disabled Discord Presence for this workspace');
}); });
const reconnecter = commands.registerCommand('discord.reconnect', () => { const reconnecter = commands.registerCommand('discord.reconnect', () => {
if (loginTimeout) clearTimeout(loginTimeout); deactivate();
rpc.dispose(); void activate(context);
loginTimeout = setTimeout(() => {
void rpc.login();
if (!config.get('silent')) void window.showInformationMessage('Reconnecting to Discord RPC...');
rpc.statusBarIcon.text = '$(pulse) Reconnecting to Discord...';
rpc.statusBarIcon.command = 'discord.reconnect';
}, 1000);
}); });
const disconnect = commands.registerCommand('discord.disconnect', () => { const disconnect = commands.registerCommand('discord.disconnect', () => {
rpc.dispose(); rpc.destroy();
rpc.statusBarIcon.text = '$(pulse) Reconnect to Discord'; rpc.statusBarIcon.text = '$(pulse) Reconnect to Discord';
rpc.statusBarIcon.command = 'discord.reconnect'; rpc.statusBarIcon.command = 'discord.reconnect';
}); });
const allowSpectate = commands.registerCommand('discord.allowSpectate', () => rpc.allowSpectate());
const disableSpectate = commands.registerCommand('discord.disableSpectate', () => rpc.disableSpectate());
const allowJoinRequests = commands.registerCommand('discord.allowJoinRequests', () => rpc.allowJoinRequests());
const disableJoinRequests = commands.registerCommand('discord.disableJoinRequests', () => rpc.disableJoinRequests());
context.subscriptions.push( context.subscriptions.push(enabler, disabler, reconnecter, disconnect);
enabler,
disabler,
reconnecter,
disconnect,
allowSpectate,
disableSpectate,
allowJoinRequests,
disableJoinRequests,
);
if (!isWorkspaceExcluded && config.get<boolean>('enabled')) { if (!isWorkspaceExcluded && config[CONFIG_KEYS.Enabled]) {
statusBarIcon.show(); statusBarIcon.show();
try { void login(context);
await rpc.login();
} catch (error) {
Logger.log(`Encountered following error after trying to login:\n${error as string}`);
rpc.dispose();
if (!config.get('silent')) {
if (error?.message?.includes('ENOENT')) void window.showErrorMessage('No Discord Client detected!');
else void window.showErrorMessage(`Couldn't connect to Discord via RPC: ${error as string}`);
}
rpc.statusBarIcon.text = '$(pulse) Reconnect to Discord';
rpc.statusBarIcon.command = 'discord.reconnect';
}
} }
const gitExtension = extensions.getExtension<GitExtension>('vscode.git'); const gitExtension = extensions.getExtension<GitExtension>('vscode.git');
if (gitExtension) { void gitExtension?.activate();
if (gitExtension.isActive) {
rpc.git = gitExtension.exports.getAPI(1);
} else {
const extension = await gitExtension.activate();
rpc.git = extension.getAPI(1);
}
}
} }
export function deactivate() { export function deactivate() {
rpc.dispose(); rpc.destroy();
} }
process.on('unhandledRejection', (err) => Logger.log(err as string));

95
src/git.d.ts vendored
View file

@ -1,10 +1,10 @@
/* /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*/ *--------------------------------------------------------------------------------------------*/
// eslint-disable-next-line import { Uri, Event, Disposable, ProviderResult } from 'vscode';
import { Uri, SourceControlInputBox, Event, CancellationToken } from 'vscode'; export { ProviderResult } from 'vscode';
export interface Git { export interface Git {
readonly path: string; readonly path: string;
@ -14,6 +14,11 @@ export interface InputBox {
value: string; value: string;
} }
export const enum ForcePushMode {
Force,
ForceWithLease,
}
export const enum RefType { export const enum RefType {
Head, Head,
RemoteHead, RemoteHead,
@ -42,7 +47,10 @@ export interface Commit {
readonly hash: string; readonly hash: string;
readonly message: string; readonly message: string;
readonly parents: string[]; readonly parents: string[];
readonly authorEmail?: string | undefined; readonly authorDate?: Date;
readonly authorName?: string;
readonly authorEmail?: string;
readonly commitDate?: Date;
} }
export interface Submodule { export interface Submodule {
@ -117,6 +125,24 @@ export interface RepositoryUIState {
export interface LogOptions { export interface LogOptions {
/** Max number of log entries to retrieve. If not specified, the default is 32. */ /** Max number of log entries to retrieve. If not specified, the default is 32. */
readonly maxEntries?: number; readonly maxEntries?: number;
readonly path?: string;
}
export interface CommitOptions {
all?: boolean | 'tracked';
amend?: boolean;
signoff?: boolean;
signCommit?: boolean;
empty?: boolean;
noVerify?: boolean;
requireUserConfig?: boolean;
}
export interface BranchQuery {
readonly remote?: boolean;
readonly pattern?: string;
readonly count?: number;
readonly contains?: string;
} }
export interface Repository { export interface Repository {
@ -157,6 +183,7 @@ export interface Repository {
createBranch(name: string, checkout: boolean, ref?: string): Promise<void>; createBranch(name: string, checkout: boolean, ref?: string): Promise<void>;
deleteBranch(name: string, force?: boolean): Promise<void>; deleteBranch(name: string, force?: boolean): Promise<void>;
getBranch(name: string): Promise<Branch>; getBranch(name: string): Promise<Branch>;
getBranches(query: BranchQuery): Promise<Ref[]>;
setBranchUpstream(name: string, upstream: string): Promise<void>; setBranchUpstream(name: string, upstream: string): Promise<void>;
getMergeBase(ref1: string, ref2: string): Promise<string>; getMergeBase(ref1: string, ref2: string): Promise<string>;
@ -166,24 +193,75 @@ export interface Repository {
addRemote(name: string, url: string): Promise<void>; addRemote(name: string, url: string): Promise<void>;
removeRemote(name: string): Promise<void>; removeRemote(name: string): Promise<void>;
renameRemote(name: string, newName: string): Promise<void>;
fetch(remote?: string, ref?: string, depth?: number): Promise<void>; fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
pull(unshallow?: boolean): Promise<void>; pull(unshallow?: boolean): Promise<void>;
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>; push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise<void>;
blame(path: string): Promise<string>; blame(path: string): Promise<string>;
log(options?: LogOptions): Promise<Commit[]>; log(options?: LogOptions): Promise<Commit[]>;
commit(message: string, opts?: CommitOptions): Promise<void>;
}
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly url: string | string[];
}
export interface RemoteSourceProvider {
readonly name: string;
readonly icon?: string; // codicon name
readonly supportsQuery?: boolean;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
getBranches?(url: string): ProviderResult<string[]>;
publishRepository?(repository: Repository): Promise<void>;
}
export interface Credentials {
readonly username: string;
readonly password: string;
}
export interface CredentialsProvider {
getCredentials(host: Uri): ProviderResult<Credentials>;
}
export interface PushErrorHandler {
handlePushError(
repository: Repository,
remote: Remote,
refspec: string,
error: Error & { gitErrorCode: GitErrorCodes },
): Promise<boolean>;
} }
export type APIState = 'uninitialized' | 'initialized'; export type APIState = 'uninitialized' | 'initialized';
export interface PublishEvent {
repository: Repository;
branch?: string;
}
export interface API { export interface API {
readonly state: APIState; readonly state: APIState;
readonly onDidChangeState: Event<APIState>; readonly onDidChangeState: Event<APIState>;
readonly onDidPublish: Event<PublishEvent>;
readonly git: Git; readonly git: Git;
readonly repositories: Repository[]; readonly repositories: Repository[];
readonly onDidOpenRepository: Event<Repository>; readonly onDidOpenRepository: Event<Repository>;
readonly onDidCloseRepository: Event<Repository>; readonly onDidCloseRepository: Event<Repository>;
toGitUri(uri: Uri, ref: string): Uri;
getRepository(uri: Uri): Repository | null;
init(root: Uri): Promise<Repository | null>;
openRepository(root: Uri): Promise<Repository | null>;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
registerPushErrorHandler(handler: PushErrorHandler): Disposable;
} }
export interface GitExtension { export interface GitExtension {
@ -220,6 +298,7 @@ export const enum GitErrorCodes {
CantOpenResource = 'CantOpenResource', CantOpenResource = 'CantOpenResource',
GitNotFound = 'GitNotFound', GitNotFound = 'GitNotFound',
CantCreatePipe = 'CantCreatePipe', CantCreatePipe = 'CantCreatePipe',
PermissionDenied = 'PermissionDenied',
CantAccessRemote = 'CantAccessRemote', CantAccessRemote = 'CantAccessRemote',
RepositoryNotFound = 'RepositoryNotFound', RepositoryNotFound = 'RepositoryNotFound',
RepositoryIsLocked = 'RepositoryIsLocked', RepositoryIsLocked = 'RepositoryIsLocked',

33
src/logger.ts Normal file
View file

@ -0,0 +1,33 @@
import { window } from 'vscode';
import dayjs from 'dayjs';
const outputChannel = window.createOutputChannel('Discord Presence');
export const enum LogLevel {
Trace = 'TRACE',
Debug = 'DEBUG',
Info = 'INFO',
Warn = 'WARN',
Error = 'ERROR',
}
function send(level: string, message: string) {
outputChannel.appendLine(`[${dayjs().format('DD/MM/YYYY HH:mm:ss')} - ${level}] ${message}`);
}
export function log(level: LogLevel, message: string | Error) {
if (typeof message === 'string') {
send(level, message);
} else if (message instanceof Error) {
send(level, message.message);
if (message.stack) {
send(level, message.stack);
}
} else if (typeof message === 'object') {
try {
const json = JSON.stringify(message, null, 2);
send(level, json);
} catch {}
}
}

View file

@ -1,376 +0,0 @@
import { basename, parse, sep } from 'path';
import { debug, Disposable, env, window, workspace } from 'vscode';
import * as vsls from 'vsls';
import RPCClient from '../client/RPCClient';
import lang from '../data/languages.json';
const knownExtensions: { [key: string]: { image: string } } = lang.knownExtensions;
const knownLanguages: string[] = lang.knownLanguages;
const empty = '\u200b\u200b';
const sizes = [' bytes', 'kb', 'mb', 'gb', 'tb'];
export interface State {
details?: string;
state?: string;
startTimestamp?: number | null;
largeImageKey?: string;
largeImageText?: string;
smallImageKey?: string;
smallImageText?: string;
partyId?: string;
partySize?: number;
partyMax?: number;
matchSecret?: string;
joinSecret?: string;
spectateSecret?: string;
instance?: boolean;
}
interface FileDetail {
size?: string;
totalLines?: string;
currentLine?: string;
currentColumn?: string;
gitbranch?: string;
gitreponame?: string;
}
export default class Activity implements Disposable {
private _state: State | null = null;
private lastKnownFile = '';
public constructor(private readonly client: RPCClient) {}
public get state() {
return this._state;
}
public async generate(workspaceElapsedTime = false) {
let largeImageKey: any = 'vscode-big';
if (window.activeTextEditor) {
if (window.activeTextEditor.document.languageId === 'Log') return this._state;
if (this._state && window.activeTextEditor.document.fileName === this.lastKnownFile) {
return (this._state = {
...this._state,
details: await this._generateDetails(
'detailsDebugging',
'detailsEditing',
'detailsIdle',
this._state.largeImageKey,
),
smallImageKey: debug.activeDebugSession
? 'debug'
: env.appName.includes('Insiders')
? 'vscode-insiders'
: 'vscode',
state: await this._generateDetails(
'lowerDetailsDebugging',
'lowerDetailsEditing',
'lowerDetailsIdle',
this._state.largeImageKey,
),
});
}
this.lastKnownFile = window.activeTextEditor.document.fileName;
const filename = basename(window.activeTextEditor.document.fileName);
largeImageKey =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
knownExtensions[
Object.keys(knownExtensions).find((key) => {
if (filename.endsWith(key)) return true;
const match = /^\/(.*)\/([mgiy]+)$/.exec(key);
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?.startTimestamp) previousTimestamp = this._state.startTimestamp;
this._state = {
...this._state,
details: await this._generateDetails('detailsDebugging', 'detailsEditing', 'detailsIdle', largeImageKey),
startTimestamp:
window.activeTextEditor && previousTimestamp && workspaceElapsedTime
? previousTimestamp
: window.activeTextEditor
? new Date().getTime()
: null,
state: await this._generateDetails(
'lowerDetailsDebugging',
'lowerDetailsEditing',
'lowerDetailsIdle',
largeImageKey,
),
largeImageKey: largeImageKey ? largeImageKey.image || largeImageKey : 'txt',
largeImageText: window.activeTextEditor
? this.client.config
.get<string>('largeImage')!
.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') ||
window.activeTextEditor.document.languageId.padEnd(2, '\u200b')
: this.client.config.get<string>('largeImageIdle'),
smallImageKey: debug.activeDebugSession
? 'debug'
: env.appName.includes('Insiders')
? 'vscode-insiders'
: 'vscode',
smallImageText: this.client.config.get<string>('smallImage')!.replace('{appname}', env.appName),
};
return this._state;
}
public async allowSpectate() {
if (!this._state) return;
const liveshare = await vsls.getApi();
if (!liveshare) return;
const join = await liveshare.share({ suppressNotification: true, access: vsls.Access.ReadOnly });
this._state = {
...this._state,
spectateSecret: join ? Buffer.from(join.toString()).toString('base64') : undefined,
instance: true,
};
return this._state;
}
public async disableSpectate() {
if (!this._state) return;
const liveshare = await vsls.getApi();
if (!liveshare) return;
await liveshare.end();
delete this._state.spectateSecret;
this._state.instance = false;
return this._state;
}
public async allowJoinRequests() {
if (!this._state) return;
const liveshare = await vsls.getApi();
if (!liveshare) return;
const join = await liveshare.share({ suppressNotification: true });
this._state = {
...this._state,
partyId: join ? join.query : undefined,
partySize: 1,
partyMax: 5,
joinSecret: join ? Buffer.from(join.toString()).toString('base64') : undefined,
instance: true,
};
return this._state;
}
public async disableJoinRequests() {
if (!this._state) return;
const liveshare = await vsls.getApi();
if (!liveshare) return;
await liveshare.end();
delete this._state.partyId;
delete this._state.partySize;
delete this._state.partyMax;
delete this._state.joinSecret;
this._state.instance = false;
return this._state;
}
public changePartyId(id?: string) {
if (!this._state) return;
if (!id) {
delete this._state.partyId;
delete this._state.partySize;
delete this._state.partyMax;
this._state.instance = false;
return this._state;
}
this._state = {
...this._state,
partyId: id,
partySize: this._state.partySize ? this._state.partySize + 1 : 1,
partyMax: 5,
instance: true,
};
return this._state;
}
public increasePartySize(size?: number) {
if (!this._state) return;
if (this._state.partySize === 5) return;
this._state = {
...this._state,
partySize: this._state.partySize ? this._state.partySize + 1 : size,
};
return this._state;
}
public decreasePartySize(size?: number) {
if (!this._state) return;
if (this._state.partySize === 1) return;
this._state = {
...this._state,
partySize: this._state.partySize ? this._state.partySize - 1 : size,
};
return this._state;
}
public dispose() {
this._state = null;
this.lastKnownFile = '';
}
private async _generateDetails(debugging: string, editing: string, idling: string, largeImageKey: any) {
let raw = this.client.config.get<string>(idling)!.replace('{null}', empty);
let filename = null;
let dirname = null;
let checkState = false;
let workspaceName = null;
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));
workspaceName = workspace.name;
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.client.config.get<string>(debugging)!;
} else {
raw = this.client.config.get<string>(editing)!;
}
const { totalLines, size, currentLine, currentColumn, gitbranch, gitreponame } = await this._generateFileDetails(
raw,
);
raw = raw
.replace('{null}', empty)
.replace('{filename}', filename)
.replace('{dirname}', dirname)
.replace('{fulldirname}', fullDirname!)
.replace(
'{workspace}',
workspaceName
? workspaceName
: checkState && workspaceFolder
? workspaceFolder.name
: this.client.config.get<string>('lowerDetailsNotFound')!.replace('{null}', empty),
)
.replace(
'{workspaceFolder}',
checkState && workspaceFolder
? workspaceFolder.name
: this.client.config.get<string>('lowerDetailsNotFound')!.replace('{null}', empty),
)
.replace(
'{workspaceAndFolder}',
checkState && workspaceName && workspaceFolder
? `${workspaceName} - ${workspaceFolder.name}`
: this.client.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('{totallines}', totalLines);
if (size) raw = raw.replace('{filesize}', size);
if (currentLine) raw = raw.replace('{currentline}', currentLine);
if (currentColumn) raw = raw.replace('{currentcolumn}', currentColumn);
if (gitbranch) raw = raw.replace('{gitbranch}', gitbranch);
if (gitreponame) raw = raw.replace('{gitreponame}', gitreponame);
}
return raw;
}
private async _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}')) {
let currentDivision = 0;
let { size } = await workspace.fs.stat(window.activeTextEditor.document.uri);
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]}`;
}
if (str.includes('{gitbranch}')) {
if (this.client.git?.repositories.length) {
fileDetail.gitbranch = this.client.git.repositories.find((repo) => repo.ui.selected)!.state.HEAD!.name;
} else {
fileDetail.gitbranch = 'Unknown';
}
}
if (str.includes('{gitreponame}')) {
if (this.client.git?.repositories.length) {
fileDetail.gitreponame = this.client.git.repositories
.find((repo) => repo.ui.selected)!
.state.remotes[0].fetchUrl!.split('/')[1]
.replace('.git', '');
} else {
fileDetail.gitreponame = 'Unknown';
}
}
}
return fileDetail;
}
}

View file

@ -1,13 +0,0 @@
import { OutputChannel, window } from 'vscode';
// eslint-disable-next-line
export default class Logger {
private static output?: OutputChannel;
public static log(message: string) {
if (!this.output) {
this.output = window.createOutputChannel('Discord Presence');
}
this.output.appendLine(message);
}
}

56
src/util.ts Normal file
View file

@ -0,0 +1,56 @@
import { basename } from 'path';
import { TextDocument, workspace, WorkspaceConfiguration } from 'vscode';
import { KNOWN_EXTENSIONS, KNOWN_LANGUAGES } from './constants';
type WorkspaceExtensionConfigurationuration = WorkspaceConfiguration & {
enabled: boolean;
details_editing: string;
details_debugging: string;
details_idling: string;
lower_details_editing: string;
lower_details_debugging: string;
lower_details_idling: string;
lower_details_no_workspace_found: string;
large_image: string;
large_image_idling: string;
small_image: string;
suppress_notifications: boolean;
workspace_elapsed_time: boolean;
workspace_exclude_patterns: string[];
};
export function getConfig() {
return workspace.getConfiguration('discord') as WorkspaceExtensionConfigurationuration;
}
export const toLower = (str: string) => str.toLocaleLowerCase();
export const toUpper = (str: string) => str.toLocaleUpperCase();
export const toTitle = (str: string) => toLower(str).replace(/^\w/, (c) => toUpper(c));
export function resolveFileIcon(document: TextDocument) {
const filename = basename(document.fileName);
const findKnownExtension = Object.keys(KNOWN_EXTENSIONS).find((key) => {
if (filename.endsWith(key)) {
return true;
}
const match = /^\/(.*)\/([mgiy]+)$/.exec(key);
if (!match) {
return false;
}
const regex = new RegExp(match[1], match[2]);
return regex.test(filename);
});
const findKnownLanguage = KNOWN_LANGUAGES.find((key) => key.language === document.languageId);
const fileIcon = findKnownExtension
? KNOWN_EXTENSIONS[findKnownExtension]
: findKnownLanguage
? findKnownLanguage.image
: null;
return typeof fileIcon === 'string' ? fileIcon : fileIcon?.image ?? 'text';
}

View file

@ -1,7 +1,7 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true
}, },
"include": [ "include": [
"**/*.ts", "**/*.ts",
@ -11,7 +11,7 @@
"**/*.test.ts", "**/*.test.ts",
"**/*.test.js", "**/*.test.js",
"**/*.spec.ts", "**/*.spec.ts",
"**/*.spec.js", "**/*.spec.js"
], ],
"exclude": [] "exclude": []
} }

View file

@ -6,11 +6,9 @@
"removeComments": false, "removeComments": false,
"alwaysStrict": true, "alwaysStrict": true,
"pretty": true, "pretty": true,
"target": "es2017", "target": "ES2019",
"module": "commonjs", "module": "commonjs",
"lib": [ "lib": ["ESNext"],
"ESNext"
],
"outDir": "dist", "outDir": "dist",
"sourceMap": true, "sourceMap": true,
"inlineSources": true, "inlineSources": true,

View file

@ -1,7 +1,11 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-require-imports */
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const path = require('path'); const path = require('path');
/** @type {import('webpack').Configuration} */
module.exports = { module.exports = {
target: 'node', target: 'node',
entry: './src/extension.ts', entry: './src/extension.ts',