327 lines
9.8 KiB
TypeScript
327 lines
9.8 KiB
TypeScript
import type {
|
||
checkIDEResultDto,
|
||
checkIDEsResultDto,
|
||
checkIDEsVersionDto,
|
||
checkIDEVersionDto
|
||
} from '@my-type/settings'
|
||
import { execSync } from 'node:child_process'
|
||
import { BrowserWindow, screen, shell, Tray } from 'electron'
|
||
import { is } from '@electron-toolkit/utils'
|
||
import path from 'path'
|
||
import { loadJsonFile } from 'load-json-file'
|
||
import os from 'os'
|
||
import {
|
||
JetBrainsIDEDisplayNameEnum as JIN,
|
||
JetBrainsProductCode,
|
||
JetBrainsStateDto
|
||
} from '@my-type/jetbrains-state-tools'
|
||
import { settingsManager } from './settings'
|
||
import { codeLaunchpadIcon } from './resources'
|
||
import { isNodeError } from '@my-type/node-error'
|
||
import { JetBrainsDataProductDto } from '@my-type/jetbrains-data-products'
|
||
|
||
const JETBRAINS_TOOLBOX_STATE_JSON_PATH = path.join(
|
||
os.homedir(),
|
||
'AppData/Local/JetBrains/Toolbox/state.json'
|
||
)
|
||
|
||
/**
|
||
* 通过 where.exe 检查系统中的指定 IDE 是否安装可用。
|
||
* @param display IDE 的正式显示名称,例如 `Visual Studio Code`。
|
||
* @param command IDE 的命令行别名,例如 `code`。
|
||
* @param code (JetBrains IDE)的产品代号;对于其他 IDE 为空字符串
|
||
*/
|
||
function checkIDE(
|
||
display: string,
|
||
command: string,
|
||
code: JetBrainsProductCode | '' = ''
|
||
): checkIDEResultDto | null {
|
||
try {
|
||
const paths = execSync(`where.exe ${command}`).toString().split('\n').slice(0, -1)
|
||
return { code, display: display, command: command, paths: paths }
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查 JerBrains IDEs 是否安装可用。
|
||
*/
|
||
async function checkJetBrainsIDEs(): Promise<checkIDEsResultDto> {
|
||
// 构建数据结构的辅助函数
|
||
const _ = (command: string, code: JetBrainsProductCode): checkIDEResultDto => {
|
||
return {
|
||
code,
|
||
command,
|
||
display: JIN[command],
|
||
paths: []
|
||
}
|
||
}
|
||
const result: checkIDEsResultDto = {
|
||
pycharm: _('pycharm', 'PY'),
|
||
clion: _('clion', 'CL'),
|
||
webstorm: _('webstorm', 'WS'),
|
||
phpstorm: _('phpstorm', 'PS'),
|
||
idea: _('idea', 'IU')
|
||
}
|
||
// 优先从 JBTState.json 查找
|
||
if (settingsManager._settings?.codeLaunchpadIDESearchPolicy.includes('JBTState.json')) {
|
||
// 处理文件不存在,即 JetBrains Toolbox 相关问题
|
||
try {
|
||
const data: JetBrainsStateDto = await loadJsonFile(JETBRAINS_TOOLBOX_STATE_JSON_PATH)
|
||
for (const ide of data.tools) {
|
||
for (const resultElement of Object.values(result)) {
|
||
if (resultElement?.code === ide.productCode) {
|
||
resultElement.paths = [ide.launchCommand]
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
if (isNodeError(error)) {
|
||
if (error.code === 'ENOENT') {
|
||
console.log('由于 JBTState.json 不存在,回退到 where.exe。')
|
||
}
|
||
} else {
|
||
console.error(error)
|
||
}
|
||
}
|
||
}
|
||
// 从 where.exe 查找
|
||
// 此分支确保在设置中启用 where.exe 方式,以及 JBTState.json 方式没有结果(未找到任何,或出现错误)时进入
|
||
if (
|
||
settingsManager._settings?.codeLaunchpadIDESearchPolicy.includes('where.exe') &&
|
||
Object.entries(result).length === 0
|
||
) {
|
||
result['clion'] = checkIDE(JIN.clion, 'clion', 'CL')
|
||
result['pycharm'] = checkIDE(JIN.pycharm, 'pycharm', 'PY')
|
||
result['webstorm'] = checkIDE(JIN.webstorm, 'webstorm', 'WS')
|
||
result['phpstorm'] = checkIDE(JIN.phpstorm, 'phpstorm', 'PS')
|
||
result['idea'] = checkIDE(JIN.idea, 'idea', 'IU')
|
||
}
|
||
return result
|
||
}
|
||
|
||
/**
|
||
* 获取系统中的各 IDE 是否安装可用。
|
||
*/
|
||
export async function getIDEs(): Promise<checkIDEsResultDto> {
|
||
return global.installedIDEs === null ? await checkIDEs() : global.installedIDEs
|
||
}
|
||
|
||
/**
|
||
* 检查系统中的各 IDE 是否安装可用。
|
||
* 原理是运行 `where.exe ${command}` 并检查返回值。
|
||
*/
|
||
export async function checkIDEs(): Promise<checkIDEsResultDto> {
|
||
console.log('在系统中查找已安装的 IDE ...')
|
||
const vscodeIDEs = {
|
||
vscode: checkIDE('Visual Studio Code', 'code')
|
||
}
|
||
global.installedIDEs = { ...vscodeIDEs, ...(await checkJetBrainsIDEs()) }
|
||
return global.installedIDEs
|
||
}
|
||
|
||
/**
|
||
* 检查 VSCode 的版本信息,通过 code --version。
|
||
*/
|
||
async function checkVSCodeVersion(): Promise<checkIDEVersionDto> {
|
||
const install = execSync('code --version').toString().split('\n')[0]
|
||
let latest = 'unknown'
|
||
try {
|
||
latest = await fetch('https://update.code.visualstudio.com/api/releases/stable')
|
||
.then((res) => res.json())
|
||
.then((data) => data[0])
|
||
} catch (error) {
|
||
console.error('获取 VSCode 版本列表时出现错误。错误提供如下。')
|
||
console.error(error)
|
||
}
|
||
return { code: '', install, latest, display: 'Visual Studio Code' }
|
||
}
|
||
|
||
/**
|
||
* 检查 JetBrains IDE 的版本信息
|
||
*/
|
||
export async function checkJetBrainsIDEsVersion(): Promise<checkIDEsVersionDto> {
|
||
// 构建数据结构的辅助函数
|
||
const _ = (display: string, code: JetBrainsProductCode): checkIDEVersionDto => {
|
||
return {
|
||
code,
|
||
display,
|
||
install: 'unknown',
|
||
latest: 'unknown'
|
||
}
|
||
}
|
||
const result: checkIDEsVersionDto = {
|
||
pycharm: _('pycharm', 'PY'),
|
||
clion: _('clion', 'CL'),
|
||
webstorm: _('webstorm', 'WS'),
|
||
phpstorm: _('phpstorm', 'PS'),
|
||
idea: _('idea', 'IU')
|
||
}
|
||
|
||
// 尝试从 JBTState.json 获取已安装的 JetBrains IDEs 的版本
|
||
// TODO:添加更多获取 JetBrains IDEs 已安装版本的方案
|
||
try {
|
||
const data: JetBrainsStateDto = await loadJsonFile(JETBRAINS_TOOLBOX_STATE_JSON_PATH)
|
||
for (const tool of data.tools) {
|
||
for (const resultElement of Object.values(result)) {
|
||
if (resultElement?.code === tool.productCode) {
|
||
resultElement.install = tool.displayVersion
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
if (isNodeError(error)) {
|
||
if (error.code === 'ENOENT') {
|
||
console.log('由于 JBTState.json 不存在,检查 JetBrains IDEs 已安装版本的任务失败。')
|
||
}
|
||
} else {
|
||
console.error(error)
|
||
}
|
||
}
|
||
|
||
// 从命令行获取 JetBrains IDE 的已安装版本的方案仍然在科研中
|
||
// 所以这个方法就暂时先结束了~
|
||
|
||
// 从网络接口获取 JetBrains IDEs 的版本列表
|
||
const codes: JetBrainsProductCode[] = []
|
||
for (const resultElement of Object.values(result)) {
|
||
codes.push(<JetBrainsProductCode>resultElement?.code)
|
||
}
|
||
try {
|
||
const params = new URLSearchParams({
|
||
type: 'release',
|
||
code: codes.join(',')
|
||
})
|
||
const data: JetBrainsDataProductDto[] = await fetch(
|
||
`https://data.services.jetbrains.com/products?${params}`
|
||
).then((res) => res.json())
|
||
for (const datum of data) {
|
||
for (const resultElement of Object.values(result)) {
|
||
if (resultElement?.code === datum.intellijProductCode) {
|
||
resultElement.latest = datum.releases[0].version
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('从 JetBrains IDEs 版本列表接口获取数据失败。错误提供如下。')
|
||
console.error(error)
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
/**
|
||
* 获取各 IDE 的版本信息。
|
||
*/
|
||
export async function getIDEsVersion(): Promise<checkIDEsVersionDto> {
|
||
return global.installedIDEsVersion === null
|
||
? await checkIDEsVersion()
|
||
: global.installedIDEsVersion
|
||
}
|
||
|
||
/**
|
||
* 检查各 IDE 的版本信息。
|
||
*/
|
||
export async function checkIDEsVersion(): Promise<checkIDEsVersionDto> {
|
||
let result: checkIDEsVersionDto = {}
|
||
for (const ide in await getIDEs()) {
|
||
switch (ide) {
|
||
case 'vscode':
|
||
result['vscode'] = await checkVSCodeVersion()
|
||
break
|
||
}
|
||
}
|
||
result = { ...result, ...(await checkJetBrainsIDEsVersion()) }
|
||
global.installedIDEsVersion = result
|
||
return result
|
||
}
|
||
|
||
/**
|
||
* 创建代码启动台窗口。
|
||
* @return 布尔值,表明创建是否成功
|
||
*/
|
||
export function createCodeLaunchpadWindow(closeOnBlur: boolean): boolean {
|
||
// 不允许重复创建
|
||
if (global.codeLaunchpadWindow !== null) {
|
||
return false
|
||
}
|
||
|
||
const windowWidth = settingsManager._settings?.codeLaunchpadWidth
|
||
? settingsManager._settings?.codeLaunchpadWidth
|
||
: 460
|
||
const windowHeight = settingsManager._settings?.codeLaunchpadHeight
|
||
? settingsManager._settings?.codeLaunchpadHeight
|
||
: 760
|
||
const position = settingsManager._settings?.codeLaunchpadPosition
|
||
? settingsManager._settings.codeLaunchpadPosition
|
||
: 'left top'
|
||
|
||
const codeLaunchpadWindow = new BrowserWindow({
|
||
width: windowWidth,
|
||
height: windowHeight,
|
||
x:
|
||
position === 'left top' || position === 'left bottom'
|
||
? 0
|
||
: screen.getPrimaryDisplay().workArea.width - windowWidth,
|
||
y:
|
||
position === 'left top' || position === 'right top'
|
||
? 0
|
||
: screen.getPrimaryDisplay().workArea.height - windowHeight,
|
||
frame: false,
|
||
show: false,
|
||
autoHideMenuBar: true,
|
||
// 代码启动台需要置顶
|
||
alwaysOnTop: true,
|
||
resizable: false,
|
||
backgroundColor: '#1f1f1f',
|
||
icon: codeLaunchpadIcon,
|
||
webPreferences: {
|
||
preload: path.join(__dirname, '../preload/index.mjs'),
|
||
sandbox: false
|
||
}
|
||
})
|
||
|
||
codeLaunchpadWindow.on('ready-to-show', () => {
|
||
codeLaunchpadWindow.show()
|
||
})
|
||
|
||
// 如有必要,失去焦点时自动关闭
|
||
if (closeOnBlur) {
|
||
codeLaunchpadWindow.on('blur', () => {
|
||
codeLaunchpadWindow.close()
|
||
})
|
||
}
|
||
|
||
// 关闭代码启动台时
|
||
codeLaunchpadWindow.on('close', () => {
|
||
global.codeLaunchpadWindow = null
|
||
})
|
||
|
||
codeLaunchpadWindow.webContents.setWindowOpenHandler((details) => {
|
||
shell.openExternal(details.url)
|
||
return { action: 'deny' }
|
||
})
|
||
|
||
// 开发和生产环境的各自设置
|
||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||
codeLaunchpadWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/codeLaunchpad/IDEs')
|
||
} else {
|
||
codeLaunchpadWindow.loadFile(path.join(__dirname, '../renderer/index.html'), {
|
||
hash: '/codeLaunchpad/IDEs'
|
||
})
|
||
}
|
||
|
||
global.codeLaunchpadWindow = codeLaunchpadWindow
|
||
|
||
return true
|
||
}
|
||
|
||
export function createCodeLaunchpadTray(): Tray {
|
||
const tray = new Tray(codeLaunchpadIcon)
|
||
|
||
tray.on('click', () => createCodeLaunchpadWindow(true))
|
||
|
||
return tray
|
||
}
|