fix(theme): Define the chart series colour in the high-contrast dark theme

Цей коміт міститься в:
l0ng-ai committed 2026-10-27 17:43:37 +08:00
1 джерело 575749f2ac
коміт 0ab50e0dd4
51 files changed
+4398 -537

No files matched your search

+232 -9
Переглянути файл
@@ -755,6 +755,51 @@ export const commands = {
* "Unknown SSH host key" (records it in `~/.ssh/known_hosts`).
*/
dbTrustSshHost: (id: string) => typedError<null, IdeError>(__TAURI_INVOKE("db_trust_ssh_host", { id })),
/** Test Connection: connects with `config` as edited (not necessarily saved). */
sshTest: (config: SshConfiguration, secret: string | null) => typedError<string, IdeError>(__TAURI_INVOKE("ssh_test", { config, secret })),
/** Remembers the password / passphrase of configuration `id` (`None` forgets it). */
sshSetSecret: (id: string, secret: string | null, save: boolean) => __TAURI_INVOKE<void>("ssh_set_secret", { id, secret, save }),
/** Whether connecting with configuration `id` needs a password first. */
sshNeedsSecret: (id: string) => typedError<boolean, IdeError>(__TAURI_INVOKE("ssh_needs_secret", { id })),
/** Trusts the host key that failed the last connection of configuration `id`. */
sshTrustHost: (id: string) => typedError<null, IdeError>(__TAURI_INVOKE("ssh_trust_host", { id })),
/** Ids of the configurations with an open connection. */
sshConnected: () => __TAURI_INVOKE<string[]>("ssh_connected"),
sshDisconnect: (id: string) => __TAURI_INVOKE<void>("ssh_disconnect", { id }),
/** Answers an `IdeEvent::Askpass` prompt (`None`: cancelled); every window drops it. */
sshAskpassAnswer: (id: number, answer: string | null) => __TAURI_INVOKE<void>("ssh_askpass_answer", { id, answer }),
/**
* Tools › Start SSH Session: the system `ssh` command line for configuration `id`
* (run in a terminal tab, so prompts and host-key questions are answered there).
*/
sshTerminalCommand: (id: string) => typedError<SshTerminalCommand, IdeError>(__TAURI_INVOKE("ssh_terminal_command", { id })),
/** Tools › Deployment › Upload to…: files and folders to `server` (default server when `None`). */
deployUpload: (paths: IpcPath[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_upload", { paths, server, request })),
/** Download from…: the remote counterparts of files and folders. */
deployDownload: (paths: IpcPath[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_download", { paths, server, request })),
/** Deletes the remote counterparts of files and folders. */
deployDeleteRemote: (paths: IpcPath[], server: string | null) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_delete_remote", { paths, server })),
/** Sync with Deployed: local and remote files under `path`, side by side. */
deployCompare: (path: IpcPath, server: string | null, request: RequestId) => typedError<SyncEntry[], IdeError>(__TAURI_INVOKE("deploy_compare", { path, server, request })),
/** Applies the Sync with Deployed choices. */
deploySync: (steps: SyncStep[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_sync", { steps, server, request })),
/** Where `path` is deployed (remote path, web URL); `None` when not mapped. */
deployInfo: (path: IpcPath, server: string | null) => typedError<{
server: string,
remotePath: string,
webUrl: string | null,
} | null, IdeError>(__TAURI_INVOKE("deploy_info", { path, server })),
/** Remote Host tool window: the server root of `server`, resolved on the host. */
deployServerRoot: (server: string) => typedError<string, IdeError>(__TAURI_INVOKE("deploy_server_root", { server })),
/** Entries of a remote folder of SSH configuration `ssh` (`~`: the login directory). */
remoteList: (ssh: string, path: string) => typedError<RemoteEntry[], IdeError>(__TAURI_INVOKE("remote_list", { ssh, path })),
/** Opens a remote file for editing: a local copy, uploaded back on save. */
remoteOpen: (ssh: string, path: string) => typedError<IpcPath, IdeError>(__TAURI_INVOKE("remote_open", { ssh, path })),
/** The remote file a local copy stands for: (SSH configuration id, remote path). */
remoteOf: (path: IpcPath) => __TAURI_INVOKE<[string, string] | null>("remote_of", { path }),
remoteDelete: (ssh: string, path: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_delete", { ssh, path })),
remoteMkdir: (ssh: string, path: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_mkdir", { ssh, path })),
remoteRename: (ssh: string, from: string, to: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_rename", { ssh, from, to })),
/** Test Connection: connects with unsaved settings and returns the server version. */
dbTest: (source: DataSource, password: string | null, sshSecret: string | null) => typedError<string, IdeError>(__TAURI_INVOKE("db_test", { source, password, sshSecret })),
dbConnected: () => typedError<string[], IdeError>(__TAURI_INVOKE("db_connected")),
@@ -897,6 +942,15 @@ export type ApplyOutcome = {
conflicts: string[],
};
/** What an askpass prompt asks for. */
export type AskpassKind =
/** Password or passphrase: hidden input. */
"secret" |
/** User name: visible input. */
"text" |
/** Yes / no (an unknown host key); answered with `yes` or `no`. */
"confirm";
export type AttachKind = "java" | "node" | "python" | "go" | "native";
export type AttachableProcess = {
@@ -918,6 +972,13 @@ export type AutoImportSettings = {
optimizeOnTheFly?: boolean,
};
/** When local changes are uploaded to the default deployment server. */
export type AutoUpload = "never" |
/** Files saved with Save / Save All (⌘S). */
"onExplicitSave" |
/** Every save, including autosave and external changes. */
"always";
/** A step run before a configuration launches; a failing step aborts the launch. */
export type BeforeLaunchTask = {
/**
@@ -1682,24 +1743,19 @@ export type DbSortKey = {
/** Connect through an SSH tunnel: the database host and port are reached from `host`. */
export type DbSsh = {
enabled?: boolean,
/** A Settings › SSH Configurations id to connect with; empty = the fields below. */
config?: string,
/** Host name, or a `Host` alias of `~/.ssh/config` with [`DbSshAuth::OpenSsh`]. */
host?: string,
port?: number | null,
user?: string,
auth?: DbSshAuth,
auth?: SshAuth,
/** Private key for [`DbSshAuth::KeyPair`]; `~/` is the home directory. */
keyFile?: string,
/** Keep the SSH password / key passphrase in the saved passwords. */
saveSecret?: boolean,
};
/** How the SSH tunnel authenticates (IDEA's SSH configuration "Authentication type"). */
export type DbSshAuth = "password" |
/** A private key file, optionally protected by a passphrase. */
"keyPair" |
/** `~/.ssh/config` (HostName, User, Port, IdentityFile, ProxyJump) and the SSH agent. */
"openSsh";
export type DbSslMode = "disable" | "prefer" | "require" |
/** Require, and check the server certificate against the CA file. */
"verifyCa" |
@@ -1798,6 +1854,51 @@ export type DeletedBranch = {
unmerged: boolean,
};
/** Where a local file is deployed. */
export type DeployedPath = {
server: string,
remotePath: string,
webUrl: string | null,
};
export type DeploymentMapping = {
/** Project-relative folder; empty = the project root. */
local?: string,
/** Remote folder, relative to the server root (a leading `/` is the root too). */
deployment?: string,
/** Web path, relative to the server's URL. */
web?: string,
};
/** An SFTP server and how project folders map onto it. */
export type DeploymentServer = {
id?: string,
name?: string,
/** [`SshConfiguration::id`]. */
sshConfig?: string,
/** Absolute remote folder the mappings are relative to; empty = the login directory. */
rootPath?: string,
/** Web server URL of the root (Open in Browser on the deployed file). */
webUrl?: string,
mappings?: DeploymentMapping[],
/** Remote paths (relative to the root) never touched. */
excludedPaths?: string[],
};
/** Build, Execution, Deployment › Deployment (and its Options page). */
export type DeploymentSettings = {
servers?: DeploymentServer[],
/** Server used by Upload / Download / Sync and automatic upload; empty = none. */
defaultServer?: string,
autoUpload?: AutoUpload,
/** Delete remote files when their local file is deleted (automatic upload). */
deleteRemote?: boolean,
/** Keep local modification times on uploaded files. */
preserveTimestamps?: boolean,
/** File names never uploaded or downloaded (`*` and `?` wildcards; `;`-separated in the UI). */
exclude?: string[],
};
export type Diagnostic = {
range: Range,
severity: Severity,
@@ -2386,6 +2487,11 @@ export type FileSettings = {
fileTypeOverrides?: { [key in string]: string },
};
export type FileStamp = {
size: number,
mtime: number | null,
};
export type FileStatus = {
path: IpcPath,
relPath: string,
@@ -2731,7 +2837,11 @@ export type IdeEvent =
* The project's source / test / resource / excluded roots changed (build files
* changed or folders were marked); the UI re-reads them with `project_roots`.
*/
{ type: "projectRootsChanged"; root: IpcPath };
{ type: "projectRootsChanged"; root: IpcPath } |
/** A child process (`ssh`, `git`) asks for a credential; answered with `ssh_askpass_answer`. */
{ type: "askpass"; id: number; prompt: string; kind: AskpassKind } |
/** Prompt `id` was answered (in any window) or gave up. */
{ type: "askpassDone"; id: number };
/** An identifier-like token whose text equals the searched name. */
export type IdentifierOccurrence = {
@@ -3975,6 +4085,20 @@ export type RemoteEdit = { op: "add"; name: string; url: string } | { op: "remov
/** Renames `name` to `new_name` (when different) and sets its URL. */
{ op: "edit"; name: string; new_name: string; url: string };
/** A remote directory entry. */
export type RemoteEntry = {
name: string,
/** Absolute remote path. */
path: string,
isDir: boolean,
isSymlink: boolean,
size: number,
/** Seconds since the Unix epoch. */
mtime: number | null,
/** `rwxr-xr-x`. */
permissions: string,
};
export type RemoteOp = { op: "fetch" } | { op: "pull"; rebase: boolean } | { op: "push"; force_with_lease: boolean; set_upstream: boolean;
/** Push dialog › Push tags. */
tags?: PushTags | null } |
@@ -4121,6 +4245,8 @@ export type RunExtras_Deserialize = {
* with the run's working directory and environment.
*/
stopCommand?: string | null,
/** Settings › Run Targets id the process runs on (over SSH); `None` = this machine. */
target?: string | null,
};
export type RunExtras_Serialize = {
@@ -4156,6 +4282,8 @@ export type RunExtras_Serialize = {
* with the run's working directory and environment.
*/
stopCommand?: string | null,
/** Settings › Run Targets id the process runs on (over SSH); `None` = this machine. */
target?: string | null,
};
/** A process started from a run configuration. */
@@ -4181,6 +4309,22 @@ export type RunSettings = {
terminationTimeoutMs?: number,
};
/** Build, Execution, Deployment › Run Targets: run configurations on another machine. */
export type RunTarget = {
id?: string,
name?: string,
/** [`SshConfiguration::id`]. */
sshConfig?: string,
/** Remote folder the project is synchronised to and run from. */
projectRoot?: string,
/** Upload changed project files before each run. */
syncBeforeRun?: boolean,
/** Interpreters on the target; empty = the one on the remote `PATH`. */
python?: string,
node?: string,
java?: string,
};
export type RustSettings = {
/** Directory holding `cargo` / `rustc`; empty = `PATH` / rustup default. */
toolchain?: string,
@@ -4380,6 +4524,12 @@ export type Settings_Deserialize = {
inspections?: InspectionSettings,
/** Editor › Natural Languages › Spelling. */
spelling?: SpellingSettings,
/** Tools › SSH Configurations. */
ssh?: SshSettings,
/** Build, Execution, Deployment › Deployment. */
deployment?: DeploymentSettings,
/** Build, Execution, Deployment › Run Targets. */
runTargets?: RunTarget[],
};
export type Settings_Serialize = {
@@ -4422,6 +4572,12 @@ export type Settings_Serialize = {
inspections: InspectionSettings,
/** Editor › Natural Languages › Spelling. */
spelling: SpellingSettings,
/** Tools › SSH Configurations. */
ssh: SshSettings,
/** Build, Execution, Deployment › Deployment. */
deployment: DeploymentSettings,
/** Build, Execution, Deployment › Run Targets. */
runTargets: RunTarget[],
};
export type Severity = "error" | "warning" | "info" | "hint";
@@ -4506,6 +4662,39 @@ export type SqlStatement = {
text: string,
};
/** How an SSH connection authenticates. */
export type SshAuth = "password" |
/** A private key file, optionally protected by a passphrase. */
"keyPair" |
/** `~/.ssh/config` (HostName, User, Port, IdentityFile, ProxyJump) and the SSH agent. */
"openSsh";
/** A named SSH host, shared by terminals, deployment, run targets and data sources. */
export type SshConfiguration = {
id?: string,
name?: string,
/** Host name, or a `Host` alias of `~/.ssh/config` with [`SshAuth::OpenSsh`]. */
host?: string,
port?: number | null,
user?: string,
auth?: SshAuth,
/** Private key for [`SshAuth::KeyPair`]; `~/` is the home directory. */
keyFile?: string,
/** Keep the password / key passphrase in the saved passwords. */
saveSecret?: boolean,
};
/** Tools › SSH Configurations. */
export type SshSettings = {
configs?: SshConfiguration[],
};
export type SshTerminalCommand = {
shell: string,
args: string[],
title: string,
};
export type StackFrame = {
id: number,
name: string,
@@ -4632,12 +4821,34 @@ export type SymbolMatch = {
indices: number[],
};
export type SyncAction = "upload" | "download" | "deleteRemote" | "deleteLocal";
/** One file of Sync with Deployed. */
export type SyncEntry = {
/** Project-relative, `/`-separated. */
path: string,
remotePath: string,
local: FileStamp | null,
remote: FileStamp | null,
status: SyncStatus,
};
export type SyncProgress = {
message: string,
/** 0..1, or `None` when indeterminate. */
fraction: number | null,
};
export type SyncStatus = "localOnly" | "remoteOnly" | "localNewer" | "remoteNewer" |
/** Same time, different size. */
"different" | "same";
export type SyncStep = {
/** Project-relative, `/`-separated. */
path: string,
action: SyncAction,
};
/** Counters for diagnostics (`app_metrics`). */
export type SyntaxCacheStats = {
/** Documents tracked (open and observed). */
@@ -4875,6 +5086,18 @@ export type ToolStatus =
/** The executable chosen in settings. */
"custom" | "missing";
export type TransferError = {
path: string,
message: string,
};
export type TransferReport = {
files: number,
/** Files left alone: excluded, or outside every mapping. */
skipped: number,
errors: TransferError[],
};
export type TypeScriptSettings = {
/**
* `bundled` (the server's own TypeScript), `workspace` (`node_modules/typescript`),
+2 -2
Переглянути файл
@@ -13,7 +13,7 @@ import {
type DbOptions,
type DbProperty,
type DbSsh,
type DbSshAuth,
type SshAuth,
type DbSslMode,
type DbTxMode,
} from "@/platform/ipc";
@@ -51,7 +51,7 @@ const TX: Array<SelectOption<DbTxMode>> = [
{ value: "manual", label: "Manual" },
];
const SSH_AUTH: Array<SelectOption<DbSshAuth>> = [
const SSH_AUTH: Array<SelectOption<SshAuth>> = [
{ value: "openSsh", label: "OpenSSH config and authentication agent" },
{ value: "password", label: "Password" },
{ value: "keyPair", label: "Key pair" },
+210
Переглянути файл
@@ -0,0 +1,210 @@
/**
* SSH configurations, deployment servers and run targets as features consume
* them (mirrors `ide-core::remote_settings` defaults). Normalisation never throws.
*/
export type SshAuthValid = "password" | "keyPair" | "openSsh";
export type AutoUploadValid = "never" | "onExplicitSave" | "always";
export interface SshConfigValid {
id: string;
name: string;
host: string;
port: number | null;
user: string;
auth: SshAuthValid;
keyFile: string;
saveSecret: boolean;
}
export interface DeploymentMappingValid {
local: string;
deployment: string;
web: string;
}
export interface DeploymentServerValid {
id: string;
name: string;
sshConfig: string;
rootPath: string;
webUrl: string;
mappings: DeploymentMappingValid[];
excludedPaths: string[];
}
export interface DeploymentSettingsValid {
servers: DeploymentServerValid[];
defaultServer: string;
autoUpload: AutoUploadValid;
deleteRemote: boolean;
preserveTimestamps: boolean;
exclude: string[];
}
export interface RunTargetValid {
id: string;
name: string;
sshConfig: string;
projectRoot: string;
syncBeforeRun: boolean;
python: string;
node: string;
java: string;
}
export interface RemoteSettings {
ssh: { configs: SshConfigValid[] };
deployment: DeploymentSettingsValid;
runTargets: RunTargetValid[];
}
export const DEFAULT_SSH_CONFIG: SshConfigValid = {
id: "",
name: "",
host: "",
port: null,
user: "",
auth: "openSsh",
keyFile: "",
saveSecret: false,
};
export const DEFAULT_DEPLOYMENT_SERVER: DeploymentServerValid = {
id: "",
name: "",
sshConfig: "",
rootPath: "",
webUrl: "",
mappings: [{ local: "", deployment: "", web: "" }],
excludedPaths: [],
};
export const DEFAULT_DEPLOYMENT: DeploymentSettingsValid = {
servers: [],
defaultServer: "",
autoUpload: "never",
deleteRemote: false,
preserveTimestamps: true,
exclude: [".git", ".svn", ".hg", ".idea", ".openidea", ".DS_Store", "*.pyc", "__pycache__"],
};
export const DEFAULT_RUN_TARGET: RunTargetValid = {
id: "",
name: "",
sshConfig: "",
projectRoot: "",
syncBeforeRun: true,
python: "",
node: "",
java: "",
};
export const DEFAULT_REMOTE: RemoteSettings = {
ssh: { configs: [] },
deployment: DEFAULT_DEPLOYMENT,
runTargets: [],
};
type Obj = Record<string, unknown>;
const obj = (v: unknown): Obj => (v && typeof v === "object" && !Array.isArray(v) ? (v as Obj) : {});
const text = (v: unknown, d: string) => (typeof v === "string" ? v : d);
const bool = (v: unknown, d: boolean) => (typeof v === "boolean" ? v : d);
const strings = (v: unknown, d: string[]) => (Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : d);
const oneOf = <T extends string>(v: unknown, values: readonly T[], d: T): T =>
values.includes(v as T) ? (v as T) : d;
const port = (v: unknown): number | null =>
typeof v === "number" && Number.isInteger(v) && v > 0 && v <= 65535 ? v : null;
/** Items with a unique, non-empty id (duplicates get `_` appended, missing ones `<prefix><n>`). */
function withIds<T extends { id: string }>(raw: unknown, prefix: string, item: (o: Obj) => T | null): T[] {
if (!Array.isArray(raw)) return [];
const seen = new Set<string>();
const out: T[] = [];
raw.forEach((x, i) => {
const v = item(obj(x));
if (!v) return;
let id = v.id.trim() || `${prefix}${i + 1}`;
while (seen.has(id)) id += "_";
seen.add(id);
out.push({ ...v, id });
});
return out;
}
export function validateSshConfigs(raw: unknown): SshConfigValid[] {
const d = DEFAULT_SSH_CONFIG;
return withIds(raw, "ssh", (o) => ({
id: text(o.id, ""),
name: text(o.name, ""),
host: text(o.host, ""),
port: port(o.port),
user: text(o.user, ""),
auth: oneOf(o.auth, ["password", "keyPair", "openSsh"] as const, d.auth),
keyFile: text(o.keyFile, ""),
saveSecret: bool(o.saveSecret, d.saveSecret),
}));
}
function validateMappings(raw: unknown): DeploymentMappingValid[] {
if (!Array.isArray(raw)) return [];
return raw.map((m) => {
const o = obj(m);
return { local: text(o.local, ""), deployment: text(o.deployment, ""), web: text(o.web, "") };
});
}
export function validateDeployment(raw: unknown): DeploymentSettingsValid {
const o = obj(raw);
const d = DEFAULT_DEPLOYMENT;
const servers = withIds(o.servers, "server", (s) => ({
id: text(s.id, ""),
name: text(s.name, ""),
sshConfig: text(s.sshConfig, ""),
rootPath: text(s.rootPath, ""),
webUrl: text(s.webUrl, ""),
mappings: validateMappings(s.mappings),
excludedPaths: strings(s.excludedPaths, []),
}));
return {
servers,
defaultServer: text(o.defaultServer, d.defaultServer),
autoUpload: oneOf(o.autoUpload, ["never", "onExplicitSave", "always"] as const, d.autoUpload),
deleteRemote: bool(o.deleteRemote, d.deleteRemote),
preserveTimestamps: bool(o.preserveTimestamps, d.preserveTimestamps),
exclude: strings(o.exclude, d.exclude),
};
}
export function validateRunTargets(raw: unknown): RunTargetValid[] {
const d = DEFAULT_RUN_TARGET;
return withIds(raw, "target", (o) => ({
id: text(o.id, ""),
name: text(o.name, ""),
sshConfig: text(o.sshConfig, ""),
projectRoot: text(o.projectRoot, ""),
syncBeforeRun: bool(o.syncBeforeRun, d.syncBeforeRun),
python: text(o.python, ""),
node: text(o.node, ""),
java: text(o.java, ""),
}));
}
export function validateRemote(root: unknown): RemoteSettings {
const o = obj(root);
return {
ssh: { configs: validateSshConfigs(obj(o.ssh).configs) },
deployment: validateDeployment(o.deployment),
runTargets: validateRunTargets(o.runTargets),
};
}
/** `user@host:port` with what is known (the name when the host is empty). */
export function sshLabel(c: Pick<SshConfigValid, "name" | "host" | "port" | "user">): string {
const host = c.host.trim();
if (!host) return c.name;
return `${c.user.trim() ? `${c.user.trim()}@` : ""}${host}${c.port ? `:${c.port}` : ""}`;
}
let seq = 0;
/** A fresh id for a new SSH configuration, server or target. */
export const newRemoteId = (prefix: string) => `${prefix}-${Date.now().toString(36)}-${++seq}`;
+4 -1
Переглянути файл
@@ -4,6 +4,7 @@
* `ide-core::workflow_settings` defaults). Normalisation never throws.
*/
import { useProject } from "./project";
import { DEFAULT_REMOTE, validateRemote, type RemoteSettings } from "./remoteSettings";
export type UpdateMethod = "merge" | "rebase";
export type IncomingCheck = "auto" | "always" | "never";
@@ -121,7 +122,7 @@ export interface ExternalToolValid {
openConsole: boolean;
}
export interface WorkflowSettings {
export interface WorkflowSettings extends RemoteSettings {
vcs: VcsSettingsValid;
console: ConsoleSettingsValid;
debugger: DebuggerSettingsValid;
@@ -239,6 +240,7 @@ export const DEFAULT_WORKFLOW: WorkflowSettings = {
},
actionsOnSave: { optimizeImports: false, cleanup: false },
externalTools: [],
...DEFAULT_REMOTE,
};
type Obj = Record<string, unknown>;
@@ -423,6 +425,7 @@ export function validateWorkflow(raw: unknown): WorkflowSettings {
terminal: validateTerminal(root.terminal),
actionsOnSave: validateActionsOnSave(root.actionsOnSave),
externalTools: validateExternalTools(root.externalTools),
...validateRemote(root),
};
}
+2
Переглянути файл
@@ -645,6 +645,8 @@ html {
--link: #a7c5ff;
--link-hover: #d0dffe;
--chart-series: #f29598;
--danger: #ff9b9f;
--warning: #f2c55c;
--success: #8ec39d;