feat: Third pass aligning core experience with IntelliJ IDEA

Project & files
- Project view modes (Project Files / Open Files / All Changed Files /
  Scratches); External Libraries for Python, JDK (src.zip), Node, Cargo, Go,
  Maven/Gradle jars; read-only jar/zip browsing
- ⌘N New popup with type search and more file kinds; New Project, Rename
  Project, Reload All from Disk, Export/Import Settings, Power Save Mode
- File type associations, extra content roots and exclude patterns (indexed
  and watched without reopening the project)

Editor & navigation
- Find bar: search scope (comments/strings), Preserve Case, Exclude, multiline,
  history; Find in Files: Module scope, custom pattern scopes, multiline
- Search Everywhere filters, Assign Shortcut, settings results; named bookmark
  lists, file/folder bookmarks; Structure filters and Show Inherited; Recent
  Locations timestamps and breadcrumbs; Code Vision author
- Completion options, editor tab placement/layout, inline completion (LSP 3.18)
- Spell checking (Typo inspection, ide-spell) and Inspections settings with
  custom profiles; Code Style schemes, formatter markers, Do not format

Run, VCS, services, database
- Typed run configurations (Shell, Program, npm, Gradle, Maven, Docker,
  Compound), Modify options, Before launch, env dialog, Current File,
  Emulate terminal (PTY)
- Git log graph/view options, Go to Hash/Branch/Tag, date filter keeps the
  graph; Pull/Merge/Rebase dialogs, Manage Remotes, Worktrees
- Generic Services host (Docker connections/registries, run sessions); TODO
  scope/changes tabs with changelists; Coverage report import
- Data sources: wire-compatible vendors, URL import, SSL certs, Options,
  Schemas and Advanced tabs, color and comment

Workbench
- Run Anything, Customize Main Toolbar, named tool window layouts, menu
  completeness, Code Cleanup, macros, "Don't Show Again", editor banners
- Chinese translations for the new UI

Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
Цей коміт міститься в:
l0ng-ai committed 2026-10-25 21:24:00 +08:00
1 джерело 294198a727
коміт 1e00ea5ee2
426 files changed
+85241 -2886

No files matched your search

+103 -1
Переглянути файл
@@ -1,6 +1,23 @@
import { describe, expect, it } from "vitest";
import { exportData, type ExportData } from "./export";
import { buildRowEdits, connectRemedy, emptyEdits, hasEdits, parseSourceUrl, sourceLabel, splitTarget, sshLabel, targetOf, toCsv, toTsv } from "./model";
import {
buildRowEdits,
colorVar,
connectRemedy,
emptyEdits,
hasEdits,
idleTargets,
matchesPattern,
parseSourceUrl,
shownNames,
sourceLabel,
splitTarget,
sshLabel,
targetOf,
toCsv,
toTsv,
} from "./model";
import { VENDORS, sourceUrl, vendorById, vendorOf, vendorPreset } from "./vendors";
describe("clipboard formats", () => {
it("copies TSV with NULL as empty and whitespace flattened", () => {
@@ -131,3 +148,88 @@ describe("parseSourceUrl", () => {
expect(parseSourceUrl("oracle://x")).toBeNull();
});
});
describe("data source types", () => {
it("presets wire-compatible products on the existing drivers", () => {
expect(vendorPreset(vendorById("cockroachdb")!)).toEqual({ driver: "postgres", vendor: "cockroachdb", port: 26257 });
expect(vendorPreset(vendorById("postgres")!)).toEqual({ driver: "postgres", vendor: "", port: 5432 });
expect(vendorPreset(vendorById("supabase")!).ssl).toBe("require");
expect(VENDORS.every((v) => ["postgres", "mysql", "sqlite"].includes(v.driver))).toBe(true);
const labels = VENDORS.map((v) => v.label);
expect(labels).toEqual([...labels].sort((a, b) => a.localeCompare(b)));
});
it("falls back to the driver for unknown or mismatched presets", () => {
expect(vendorOf({ driver: "mysql", vendor: "tidb" }).label).toBe("TiDB");
expect(vendorOf({ driver: "postgres", vendor: "tidb" }).id).toBe("postgres");
expect(vendorOf({ driver: "mysql" }).id).toBe("mysql");
expect(sourceLabel({ name: "tidb", driver: "mysql", vendor: "tidb" })).toBe("tidb (TiDB)");
});
it("previews the JDBC URL of the fields", () => {
expect(sourceUrl({ driver: "postgres", host: "localhost", port: 5432, database: "postgres" })).toBe(
"jdbc:postgresql://localhost:5432/postgres",
);
expect(sourceUrl({ driver: "postgres", vendor: "redshift", host: "r", port: null, database: "" })).toBe("jdbc:redshift://r:5439");
expect(sourceUrl({ driver: "mysql", vendor: "mariadb", host: "::1", port: 3307, database: "a b" })).toBe(
"jdbc:mariadb://[::1]:3307/a%20b",
);
expect(sourceUrl({ driver: "sqlite", file: "app.db" })).toBe("jdbc:sqlite:app.db");
});
it("reads URLs of compatible products, SSL settings and driver properties", () => {
const tidb = parseSourceUrl("jdbc:tidb://[email protected]:4000/test?sslmode=verify-full&sslrootcert=/ca.pem&sql_mode=ANSI");
expect(tidb?.source).toEqual({
driver: "mysql",
vendor: "tidb",
host: "tidb.local",
port: 4000,
user: "root",
database: "test",
ssl: "verifyFull",
sslCaFile: "/ca.pem",
properties: [{ name: "sql_mode", value: "ANSI" }],
});
expect(parseSourceUrl("redshift://r.example:5439/dev")?.source.vendor).toBe("redshift");
expect(parseSourceUrl("postgresql://h:70000/db")).toBeNull();
});
});
describe("Schemas tab", () => {
it("matches names and * / ? patterns", () => {
expect(matchesPattern("app_1", "app_*")).toBe(true);
expect(matchesPattern("app.1", "app?1")).toBe(true);
expect(matchesPattern("apps", "app")).toBe(false);
expect(matchesPattern("a.b", "a.b")).toBe(true);
});
it("shows all names, or the chosen ones and the connected database", () => {
const names = ["app", "app_test", "billing", "postgres"];
expect(shownNames(names, undefined, undefined)).toEqual(names);
expect(shownNames(names, true, ["billing"])).toEqual(names);
expect(shownNames(names, false, ["app*"], "postgres")).toEqual(["app", "app_test", "postgres"]);
expect(shownNames(names, false, [])).toEqual([]);
});
});
describe("Options tab", () => {
it("finds connections idle past their auto-disconnect", () => {
const sources = [
{ id: "a", options: { autoDisconnectSecs: 60 } },
{ id: "b", options: { autoDisconnectSecs: null } },
];
const used = new Map([
["a", 0],
["a:other", 50_000],
["b", 0],
]);
const idle = (busy: string[]) => idleTargets(["a", "a:other", "b", "c"], sources, used, (t) => busy.includes(t), 60_000);
expect(idle([])).toEqual(["a"]);
expect(idle(["a"])).toEqual([]);
});
it("names the colour tokens", () => {
expect(colorVar("red")).toBe("var(--db-color-red)");
expect(colorVar(null)).toBeUndefined();
});
});