mirror of
https://github.com/eugeny/tabby
synced 2026-07-08 08:01:17 +00:00
cleanup
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
let wnr: any = null
|
||||
|
||||
try {
|
||||
wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
} catch { }
|
||||
|
||||
let cachedEnvironment: Record<string, string>|null = null
|
||||
|
||||
/** Strips `undefined` values from a process-style environment object. */
|
||||
function normalizeEnv (env: Record<string, string|undefined>): Record<string, string> {
|
||||
const result: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (value !== undefined) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a variable name to its actual key in `env`. Lookups are
|
||||
* case-insensitive on Windows, where environment variable names are.
|
||||
*/
|
||||
function findKey (env: Record<string, string>, name: string): string|undefined {
|
||||
if (name in env) {
|
||||
return name
|
||||
}
|
||||
if (process.platform !== 'win32') {
|
||||
return undefined
|
||||
}
|
||||
const lower = name.toLowerCase()
|
||||
return Object.keys(env).find(k => k.toLowerCase() === lower)
|
||||
}
|
||||
|
||||
/** Iteratively expands `%VAR%` references within an environment block in place. */
|
||||
function expandWindowsEnv (env: Record<string, string>): void {
|
||||
const pattern = /%([^%]+)%/g
|
||||
let changed = true
|
||||
let iterations = 0
|
||||
while (changed && iterations < 10) {
|
||||
changed = false
|
||||
iterations++
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
const expanded = value.replace(pattern, (match, varName) => {
|
||||
const found = findKey(env, varName)
|
||||
return found !== undefined ? env[found] : match
|
||||
})
|
||||
if (expanded !== env[key]) {
|
||||
env[key] = expanded
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readRegistryEnv (hive: any, path: string): Record<string, string> {
|
||||
const env: Record<string, string> = {}
|
||||
if (!wnr) {
|
||||
return env
|
||||
}
|
||||
try {
|
||||
const key = wnr.getRegistryKey(hive, path)
|
||||
if (!key) {
|
||||
return env
|
||||
}
|
||||
for (const [name, entry] of Object.entries(key)) {
|
||||
if (!name || typeof name !== 'string') {
|
||||
continue
|
||||
}
|
||||
const e = entry as any
|
||||
if (e && typeof e.value === 'string') {
|
||||
env[name] = e.value
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore registry read errors
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
function isPathLikeVar (name: string): boolean {
|
||||
const lower = name.toLowerCase()
|
||||
return lower === 'path' || lower === 'pathext'
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a registry environment source into `target`. Most variables override,
|
||||
* but `Path`/`PATHEXT` are appended to the existing value (resolved
|
||||
* case-insensitively so e.g. system `Path` and user `PATH` don't split).
|
||||
*/
|
||||
function mergeRegistryEnv (target: Record<string, string>, source: Record<string, string>): void {
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
const existingKey = isPathLikeVar(key) ? findKey(target, key) : undefined
|
||||
if (existingKey !== undefined) {
|
||||
target[existingKey] = target[existingKey] && value
|
||||
? target[existingKey] + ';' + value
|
||||
: target[existingKey] || value
|
||||
} else {
|
||||
target[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the environment block from Windows registry sources,
|
||||
* mimicking the behavior of Windows Terminal's environment refresh.
|
||||
*/
|
||||
function buildWindowsEnvironment (): Record<string, string> {
|
||||
const merged: Record<string, string> = {}
|
||||
|
||||
// System env vars as base, then user and volatile overrides (Path/PATHEXT appended)
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.LM, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment'))
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.CU, 'Environment'))
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.CU, 'Volatile Environment'))
|
||||
|
||||
// Preserve process-specific vars (e.g. Electron, Node, Angular)
|
||||
// that aren't defined in the registry
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined && findKey(merged, key) === undefined) {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
expandWindowsEnv(merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
export function getEnvironment (refreshFromRegistry = false): Record<string, string> {
|
||||
if (process.platform === 'win32' && refreshFromRegistry) {
|
||||
cachedEnvironment = buildWindowsEnvironment()
|
||||
} else {
|
||||
cachedEnvironment ??= normalizeEnv(process.env)
|
||||
}
|
||||
return cachedEnvironment
|
||||
}
|
||||
|
||||
/** Expands `%VAR%` (Windows) / `$VAR` (POSIX) references using the cached environment. */
|
||||
export function substituteEnv (env: Record<string, string>): Record<string, string> {
|
||||
const base = getEnvironment()
|
||||
env = { ...env }
|
||||
const pattern = process.platform === 'win32' ? /%(\w+)%/g : /\$(\w+)\b/g
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
env[key] = value.toString().replace(pattern, (substring, p1) => {
|
||||
const found = findKey(base, p1)
|
||||
return found !== undefined ? base[found] : ''
|
||||
})
|
||||
}
|
||||
return env
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Injector } from '@angular/core'
|
||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
||||
import { BaseSession } from 'tabby-terminal'
|
||||
import { SessionOptions, ChildProcess, PTYInterface, PTYProxy } from './api'
|
||||
import { getWindowsEnvironment } from './windowsEnvironment'
|
||||
import { getEnvironment, substituteEnv } from './environment'
|
||||
|
||||
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi
|
||||
|
||||
@@ -22,21 +22,6 @@ function mergeEnv (...envs) {
|
||||
return result
|
||||
}
|
||||
|
||||
function substituteEnv (env: Record<string, string>) {
|
||||
env = { ...env }
|
||||
const pattern = process.platform === 'win32' ? /%(\w+)%/g : /\$(\w+)\b/g
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
env[key] = value.toString().replace(pattern, function (substring, p1) {
|
||||
if (process.platform === 'win32') {
|
||||
return Object.entries(process.env).find(x => x[0].toLowerCase() === p1.toLowerCase())?.[1] ?? ''
|
||||
} else {
|
||||
return process.env[p1] ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
export class Session extends BaseSession {
|
||||
private pty: PTYProxy|null = null
|
||||
@@ -68,9 +53,9 @@ export class Session extends BaseSession {
|
||||
}
|
||||
|
||||
if (!pty) {
|
||||
const baseEnv = this.hostApp.platform === Platform.Windows && this.config.store.terminal.windowsRefreshEnvironment
|
||||
? getWindowsEnvironment()
|
||||
: process.env
|
||||
const baseEnv = getEnvironment(
|
||||
this.hostApp.platform === Platform.Windows && this.config.store.terminal.windowsRefreshEnvironment,
|
||||
)
|
||||
|
||||
let env = mergeEnv(
|
||||
baseEnv,
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
let wnr: any = null
|
||||
|
||||
try {
|
||||
wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
} catch { }
|
||||
|
||||
function expandWindowsEnv (env: Record<string, string>): Record<string, string> {
|
||||
const result = { ...env }
|
||||
const pattern = /%([^%]+)%/g
|
||||
let changed = true
|
||||
let iterations = 0
|
||||
while (changed && iterations < 10) {
|
||||
changed = false
|
||||
iterations++
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
const expanded = value.replace(pattern, (match, varName) => {
|
||||
const lookup = varName.toLowerCase()
|
||||
const foundKey = Object.keys(result).find(k => k.toLowerCase() === lookup)
|
||||
if (foundKey !== undefined) {
|
||||
return result[foundKey]
|
||||
}
|
||||
const processKey = Object.keys(process.env).find(k => k.toLowerCase() === lookup)
|
||||
if (processKey !== undefined) {
|
||||
return process.env[processKey]!
|
||||
}
|
||||
return match
|
||||
})
|
||||
if (expanded !== result[key]) {
|
||||
result[key] = expanded
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function readRegistryEnv (hive: any, path: string): Record<string, string> {
|
||||
const env: Record<string, string> = {}
|
||||
if (!wnr) {
|
||||
return env
|
||||
}
|
||||
try {
|
||||
const key = wnr.getRegistryKey(hive, path)
|
||||
if (!key) {
|
||||
return env
|
||||
}
|
||||
for (const [name, entry] of Object.entries(key)) {
|
||||
if (!name || typeof name !== 'string') {
|
||||
continue
|
||||
}
|
||||
const e = entry as any
|
||||
if (e && typeof e.value === 'string') {
|
||||
env[name] = e.value
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore registry read errors
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
function isPathLikeVar (name: string): boolean {
|
||||
return name.toLowerCase() === 'path' || name.toLowerCase() === 'pathext'
|
||||
}
|
||||
|
||||
function mergePathValue (existing: string, additional: string): string {
|
||||
if (!existing) {
|
||||
return additional
|
||||
}
|
||||
if (!additional) {
|
||||
return existing
|
||||
}
|
||||
return existing + ';' + additional
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the environment block from Windows registry sources,
|
||||
* mimicking the behavior of Windows Terminal's environment refresh.
|
||||
* Falls back to process.env if windows-native-registry is unavailable.
|
||||
*/
|
||||
export function getWindowsEnvironment (): Record<string, string> {
|
||||
if (!wnr) {
|
||||
const result: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const systemEnv = readRegistryEnv(wnr.HK.LM, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
|
||||
const userEnv = readRegistryEnv(wnr.HK.CU, 'Environment')
|
||||
const volatileEnv = readRegistryEnv(wnr.HK.CU, 'Volatile Environment')
|
||||
|
||||
const merged: Record<string, string> = {}
|
||||
|
||||
// System env vars as base
|
||||
for (const [key, value] of Object.entries(systemEnv)) {
|
||||
merged[key] = value
|
||||
}
|
||||
|
||||
// User env vars override system, but Path/PATHEXT are merged
|
||||
for (const [key, value] of Object.entries(userEnv)) {
|
||||
if (isPathLikeVar(key) && merged[key]) {
|
||||
merged[key] = mergePathValue(merged[key], value)
|
||||
} else {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Volatile env vars override, but Path/PATHEXT are merged
|
||||
for (const [key, value] of Object.entries(volatileEnv)) {
|
||||
if (isPathLikeVar(key) && merged[key]) {
|
||||
merged[key] = mergePathValue(merged[key], value)
|
||||
} else {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve process-specific vars (e.g. Electron, Node, Angular)
|
||||
// that aren't defined in the registry
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined && !(key in merged)) {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return expandWindowsEnv(merged)
|
||||
}
|
||||
@@ -245,3 +245,12 @@ div.mt-4(*ngIf='hostApp.platform === Platform.Windows')
|
||||
[(ngModel)]='config.store.terminal.setComSpec',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title(translate) Refresh environment variables from registry
|
||||
.description(translate) Reload PATH and other variables from the Windows registry for each new session
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.windowsRefreshEnvironment',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user