@xuhaojun/githunk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/githunk.js +1446 -313
- package/package.json +4 -3
package/dist/githunk.js
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/cli/args.ts
|
|
4
|
+
import { Command, CommanderError } from "commander";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
var cachedVersion;
|
|
9
|
+
function getCliVersion() {
|
|
10
|
+
if (cachedVersion !== undefined)
|
|
11
|
+
return cachedVersion;
|
|
12
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
for (let level = 0;level < 4; level += 1) {
|
|
14
|
+
try {
|
|
15
|
+
const manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
16
|
+
if (manifest.name === "@xuhaojun/githunk" && typeof manifest.version === "string") {
|
|
17
|
+
cachedVersion = manifest.version;
|
|
18
|
+
return cachedVersion;
|
|
19
|
+
}
|
|
20
|
+
} catch {}
|
|
21
|
+
const parent = dirname(directory);
|
|
22
|
+
if (parent === directory)
|
|
23
|
+
break;
|
|
24
|
+
directory = parent;
|
|
25
|
+
}
|
|
26
|
+
cachedVersion = "0.0.0-dev";
|
|
27
|
+
return cachedVersion;
|
|
28
|
+
}
|
|
29
|
+
function parseCliArgs(argv) {
|
|
30
|
+
let stdout = "";
|
|
31
|
+
let stderr = "";
|
|
32
|
+
const program = new Command;
|
|
33
|
+
program.name("githunk").description("A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.").version(getCliVersion(), "-V, --version", "output the version number").option("-p, --path <dir>", "path to the Git repository to open").argument("[path]", "path to the Git repository to open").exitOverride().configureOutput({
|
|
34
|
+
writeOut: (text) => {
|
|
35
|
+
stdout += text;
|
|
36
|
+
},
|
|
37
|
+
writeErr: (text) => {
|
|
38
|
+
stderr += text;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
program.parse([...argv], { from: "user" });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof CommanderError) {
|
|
45
|
+
if (error.code === "commander.helpDisplayed")
|
|
46
|
+
return { kind: "help", text: stdout };
|
|
47
|
+
if (error.code === "commander.version")
|
|
48
|
+
return { kind: "version", text: stdout };
|
|
49
|
+
const message = stderr.trim() === "" ? error.message : stderr.trim();
|
|
50
|
+
return { kind: "error", message, exitCode: error.exitCode };
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
const options = program.opts();
|
|
55
|
+
const positional = program.args[0];
|
|
56
|
+
const startDirectory = options.path ?? positional;
|
|
57
|
+
return startDirectory === undefined ? { kind: "start" } : { kind: "start", startDirectory };
|
|
58
|
+
}
|
|
59
|
+
|
|
3
60
|
// src/main.ts
|
|
61
|
+
import { resolve as resolve4 } from "node:path";
|
|
4
62
|
import { createCliRenderer } from "@opentui/core";
|
|
5
63
|
|
|
6
64
|
// src/domain/command.ts
|
|
@@ -775,6 +833,7 @@ function withCommitStatuses(commits, sets) {
|
|
|
775
833
|
|
|
776
834
|
// src/git/commits.ts
|
|
777
835
|
var LOG_FORMAT = "%H%n%h%n%P%n%an%n%aI%n%s%n%b";
|
|
836
|
+
var COMMITS_LIMIT = 300;
|
|
778
837
|
function parseSummary(record) {
|
|
779
838
|
const fields = record.split(`
|
|
780
839
|
`);
|
|
@@ -802,8 +861,11 @@ function parseSummary(record) {
|
|
|
802
861
|
function parseCommitLog(raw) {
|
|
803
862
|
return raw.split("\x00").map(parseSummary).filter((summary) => summary !== undefined);
|
|
804
863
|
}
|
|
805
|
-
async function listCommits(runner, range, filter) {
|
|
806
|
-
const args = ["log", "-z", "--topo-order", `--format=${LOG_FORMAT}
|
|
864
|
+
async function listCommits(runner, range, filter, options) {
|
|
865
|
+
const args = ["log", "-z", "--topo-order", `--format=${LOG_FORMAT}`];
|
|
866
|
+
if (options?.limit ?? true)
|
|
867
|
+
args.push(`-${COMMITS_LIMIT}`);
|
|
868
|
+
args.push(range);
|
|
807
869
|
if (filter !== undefined && filter.length > 0)
|
|
808
870
|
args.push("--", filter);
|
|
809
871
|
const [result, statusSets] = await Promise.all([
|
|
@@ -916,7 +978,7 @@ function fingerprintWorkingTreeFile(target, filePatch) {
|
|
|
916
978
|
|
|
917
979
|
// src/storage/local-state-file.ts
|
|
918
980
|
import { mkdir, open, rename, stat, unlink, lstat, link, readFile } from "node:fs/promises";
|
|
919
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
981
|
+
import { dirname as dirname2, isAbsolute, join as join2, resolve } from "node:path";
|
|
920
982
|
import { randomUUID } from "node:crypto";
|
|
921
983
|
async function assertNoSymlinkInPath(path, pathKind) {
|
|
922
984
|
const absolute = resolve(path);
|
|
@@ -946,7 +1008,7 @@ class LocalStateFile {
|
|
|
946
1008
|
this.pathKind = options.pathKind ?? "state";
|
|
947
1009
|
}
|
|
948
1010
|
get path() {
|
|
949
|
-
return this.resolvedPath ??
|
|
1011
|
+
return this.resolvedPath ?? join2(this.runner.cwd, ".git", this.relativePath);
|
|
950
1012
|
}
|
|
951
1013
|
async resolvePath() {
|
|
952
1014
|
if (this.resolvedPath !== undefined)
|
|
@@ -954,7 +1016,7 @@ class LocalStateFile {
|
|
|
954
1016
|
const output = (await this.runner.run(["rev-parse", "--git-path", this.relativePath], { readOnly: true })).stdout.trim();
|
|
955
1017
|
if (output.length === 0)
|
|
956
1018
|
throw new Error(`git returned an empty path for ${this.relativePath}`);
|
|
957
|
-
this.resolvedPath = isAbsolute(output) ? output :
|
|
1019
|
+
this.resolvedPath = isAbsolute(output) ? output : join2(this.runner.cwd, output);
|
|
958
1020
|
return this.resolvedPath;
|
|
959
1021
|
}
|
|
960
1022
|
async readText() {
|
|
@@ -971,7 +1033,7 @@ class LocalStateFile {
|
|
|
971
1033
|
async writeText(text) {
|
|
972
1034
|
const path = await this.resolvePath();
|
|
973
1035
|
await assertNoSymlinkInPath(path, this.pathKind);
|
|
974
|
-
await mkdir(
|
|
1036
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
975
1037
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
976
1038
|
await assertNoSymlinkInPath(temporary, this.pathKind);
|
|
977
1039
|
const handle = await open(temporary, "wx", 384);
|
|
@@ -984,7 +1046,7 @@ class LocalStateFile {
|
|
|
984
1046
|
}
|
|
985
1047
|
await rename(temporary, path);
|
|
986
1048
|
try {
|
|
987
|
-
const directory = await open(
|
|
1049
|
+
const directory = await open(dirname2(path), "r");
|
|
988
1050
|
try {
|
|
989
1051
|
await directory.sync();
|
|
990
1052
|
} finally {
|
|
@@ -1008,7 +1070,7 @@ class LocalStateFile {
|
|
|
1008
1070
|
async createTextExclusive(text) {
|
|
1009
1071
|
const path = await this.resolvePath();
|
|
1010
1072
|
await assertNoSymlinkInPath(path, this.pathKind);
|
|
1011
|
-
await mkdir(
|
|
1073
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
1012
1074
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
1013
1075
|
await assertNoSymlinkInPath(temporary, this.pathKind);
|
|
1014
1076
|
const handle = await open(temporary, "wx", 384);
|
|
@@ -1037,7 +1099,7 @@ class LocalStateFile {
|
|
|
1037
1099
|
return;
|
|
1038
1100
|
});
|
|
1039
1101
|
try {
|
|
1040
|
-
const directory = await open(
|
|
1102
|
+
const directory = await open(dirname2(path), "r");
|
|
1041
1103
|
try {
|
|
1042
1104
|
await directory.sync();
|
|
1043
1105
|
} finally {
|
|
@@ -2072,7 +2134,7 @@ async function listReflog(runner, options = {}) {
|
|
|
2072
2134
|
|
|
2073
2135
|
// src/git/worktrees.ts
|
|
2074
2136
|
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
2075
|
-
import { dirname as
|
|
2137
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
2076
2138
|
function finalizeEntry(entry) {
|
|
2077
2139
|
return {
|
|
2078
2140
|
path: entry.path,
|
|
@@ -2174,7 +2236,7 @@ async function resolveRepositoryPaths(runner) {
|
|
|
2174
2236
|
const worktreeGitDirPath = lines[1] ?? "";
|
|
2175
2237
|
const repoGitDirPath = lines[2] ?? "";
|
|
2176
2238
|
const isSubmodule = (lines[3] ?? "").length > 0;
|
|
2177
|
-
const repoPath = worktreeGitDirPath === repoGitDirPath || isSubmodule ? worktreePath :
|
|
2239
|
+
const repoPath = worktreeGitDirPath === repoGitDirPath || isSubmodule ? worktreePath : dirname3(repoGitDirPath);
|
|
2178
2240
|
return { worktreePath, worktreeGitDirPath, repoPath, repoGitDirPath };
|
|
2179
2241
|
}
|
|
2180
2242
|
async function isPathMissing(path) {
|
|
@@ -2207,11 +2269,11 @@ async function readTrimmedFile(path) {
|
|
|
2207
2269
|
}
|
|
2208
2270
|
async function inProgressBranch(gitDir) {
|
|
2209
2271
|
for (const directory of ["rebase-merge", "rebase-apply"]) {
|
|
2210
|
-
const headName = await readTrimmedFile(
|
|
2272
|
+
const headName = await readTrimmedFile(join3(gitDir, directory, "head-name"));
|
|
2211
2273
|
if (headName !== undefined)
|
|
2212
2274
|
return headName.replace(/^refs\/heads\//, "");
|
|
2213
2275
|
}
|
|
2214
|
-
return await readTrimmedFile(
|
|
2276
|
+
return await readTrimmedFile(join3(gitDir, "BISECT_START"));
|
|
2215
2277
|
}
|
|
2216
2278
|
async function listWorktrees(runner) {
|
|
2217
2279
|
const repositoryPaths = await resolveRepositoryPaths(runner);
|
|
@@ -2278,7 +2340,7 @@ function worktreeRemovalRequiresForce(error) {
|
|
|
2278
2340
|
|
|
2279
2341
|
// src/git/submodules.ts
|
|
2280
2342
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2281
|
-
import { join as
|
|
2343
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
2282
2344
|
|
|
2283
2345
|
// src/domain/submodule.ts
|
|
2284
2346
|
function submoduleFullName(submodule) {
|
|
@@ -2333,7 +2395,7 @@ function parseGitModules(raw) {
|
|
|
2333
2395
|
}
|
|
2334
2396
|
async function readGitModules(directory) {
|
|
2335
2397
|
try {
|
|
2336
|
-
return await readFile3(
|
|
2398
|
+
return await readFile3(join4(directory, ".gitmodules"), "utf8");
|
|
2337
2399
|
} catch (error) {
|
|
2338
2400
|
if (error instanceof Error && "code" in error) {
|
|
2339
2401
|
const code = error.code;
|
|
@@ -2344,7 +2406,7 @@ async function readGitModules(directory) {
|
|
|
2344
2406
|
}
|
|
2345
2407
|
}
|
|
2346
2408
|
async function collectSubmodules(worktreePath, parentModule, visited) {
|
|
2347
|
-
const directory = parentModule === undefined ? worktreePath :
|
|
2409
|
+
const directory = parentModule === undefined ? worktreePath : join4(worktreePath, submoduleFullPath(parentModule));
|
|
2348
2410
|
const resolved = resolve2(directory);
|
|
2349
2411
|
if (visited.has(resolved))
|
|
2350
2412
|
return [];
|
|
@@ -2474,6 +2536,7 @@ class AppController {
|
|
|
2474
2536
|
loadCommitDetails;
|
|
2475
2537
|
loadCommitFile;
|
|
2476
2538
|
generation = 0;
|
|
2539
|
+
limitCommits = true;
|
|
2477
2540
|
pullRequestRefreshGeneration = 0;
|
|
2478
2541
|
currentState;
|
|
2479
2542
|
reviewDatabase = emptyWorkingTreeReviewDatabase();
|
|
@@ -2494,7 +2557,7 @@ class AppController {
|
|
|
2494
2557
|
return loadWorkingTree(runner, target2.scope, snapshotOptions ?? {});
|
|
2495
2558
|
});
|
|
2496
2559
|
this.loadBranchesListing = options instanceof GitRunner ? () => listBranches(options) : options.loadBranches ?? options.branchesLoader ?? (runner !== undefined ? () => listBranches(runner) : async () => ({ detached: true, localBranches: [], remotes: [] }));
|
|
2497
|
-
this.loadCommitList = options instanceof GitRunner ? (range, filter) => listCommits(options, range, filter) : options.loadCommits ?? options.commitsLoader ?? (runner === undefined ? async () => [] : (range, filter) => listCommits(runner, range, filter));
|
|
2560
|
+
this.loadCommitList = options instanceof GitRunner ? (range, filter, listOptions) => listCommits(options, range, filter, listOptions) : options.loadCommits ?? options.commitsLoader ?? (runner === undefined ? async () => [] : (range, filter, listOptions) => listCommits(runner, range, filter, listOptions));
|
|
2498
2561
|
this.loadCommitDetails = options instanceof GitRunner ? (oid) => loadCommit(options, oid) : options.loadCommit ?? options.commitLoader ?? (runner === undefined ? async () => {
|
|
2499
2562
|
throw new Error("Commit details require a GitRunner");
|
|
2500
2563
|
} : (oid) => loadCommit(runner, oid));
|
|
@@ -2732,6 +2795,7 @@ class AppController {
|
|
|
2732
2795
|
await this.switchLocalBranch(branch);
|
|
2733
2796
|
}
|
|
2734
2797
|
async switchLocalBranch(branch) {
|
|
2798
|
+
this.limitCommits = true;
|
|
2735
2799
|
this.logAction(LOG_ACTIONS.checkoutBranch);
|
|
2736
2800
|
await this.runBranchMutation(() => this.requireRunnerOperation((runner) => switchLocal(runner, branch)));
|
|
2737
2801
|
}
|
|
@@ -3103,7 +3167,20 @@ class AppController {
|
|
|
3103
3167
|
return this.loadCommitDetails(oid);
|
|
3104
3168
|
}
|
|
3105
3169
|
async loadBranchCommits(branch) {
|
|
3106
|
-
return this.loadCommitList(`refs/heads/${branch}
|
|
3170
|
+
return this.loadCommitList(`refs/heads/${branch}`, undefined, { limit: this.limitCommits });
|
|
3171
|
+
}
|
|
3172
|
+
async expandCommits() {
|
|
3173
|
+
if (!this.limitCommits)
|
|
3174
|
+
return false;
|
|
3175
|
+
this.limitCommits = false;
|
|
3176
|
+
const history = await this.loadCommitHistory("HEAD");
|
|
3177
|
+
this.currentState = {
|
|
3178
|
+
...this.currentState,
|
|
3179
|
+
commits: history.commits,
|
|
3180
|
+
...this.commandLogSnapshot(),
|
|
3181
|
+
...history.warning === undefined ? {} : { banner: history.warning }
|
|
3182
|
+
};
|
|
3183
|
+
return true;
|
|
3107
3184
|
}
|
|
3108
3185
|
async loadCommitFileInspection(oid, path) {
|
|
3109
3186
|
return this.loadCommitFile(oid, path);
|
|
@@ -3402,7 +3479,7 @@ class AppController {
|
|
|
3402
3479
|
}
|
|
3403
3480
|
async loadCommitHistory(range) {
|
|
3404
3481
|
try {
|
|
3405
|
-
return { commits: await this.loadCommitList(range) };
|
|
3482
|
+
return { commits: await this.loadCommitList(range, undefined, { limit: this.limitCommits }) };
|
|
3406
3483
|
} catch (error) {
|
|
3407
3484
|
const warning = error instanceof GitCommandError ? error.record.stderr || error.message : error instanceof Error ? error.message : String(error);
|
|
3408
3485
|
return { commits: this.currentState.commits ?? [], warning };
|
|
@@ -4408,6 +4485,13 @@ function syncVerticalScrollbar(bar, text, viewportHeight) {
|
|
|
4408
4485
|
bar.viewportSize = scrollbarViewportOverrides.get(bar) ?? Math.max(0, Math.floor(text.height));
|
|
4409
4486
|
bar.scrollPosition = text.scrollY;
|
|
4410
4487
|
}
|
|
4488
|
+
function clearScrollbarViewportOverride(text) {
|
|
4489
|
+
const bar = scrollbars.get(text);
|
|
4490
|
+
if (bar === undefined)
|
|
4491
|
+
return;
|
|
4492
|
+
scrollbarViewportOverrides.delete(bar);
|
|
4493
|
+
syncVerticalScrollbar(bar, text);
|
|
4494
|
+
}
|
|
4411
4495
|
function createPane(renderer, id, title, content, selectable = false, options = {}) {
|
|
4412
4496
|
const tabsConfig = options.tabs;
|
|
4413
4497
|
const BoxClass = tabsConfig === undefined ? BoxRenderable2 : PaneTabsBoxRenderable;
|
|
@@ -4892,36 +4976,62 @@ function computeColumnLayout(rows, width) {
|
|
|
4892
4976
|
const widths = indexes.map((j) => j === flexIndex ? flexWidth : rawWidths[j]);
|
|
4893
4977
|
return { indexes, widths };
|
|
4894
4978
|
}
|
|
4895
|
-
function
|
|
4896
|
-
const
|
|
4979
|
+
function layoutListRowSegments(row, layout) {
|
|
4980
|
+
const segments = [];
|
|
4897
4981
|
for (let i = 0;i < layout.indexes.length; i++) {
|
|
4898
4982
|
const column = row.columns[layout.indexes[i]];
|
|
4899
4983
|
const cellWidth = layout.widths[i];
|
|
4900
4984
|
const isLast = i === layout.indexes.length - 1;
|
|
4901
4985
|
if (i > 0)
|
|
4902
|
-
|
|
4986
|
+
segments.push({ text: " " });
|
|
4903
4987
|
const text = column?.text ?? "";
|
|
4904
4988
|
const truncated = truncateToWidth(text, cellWidth);
|
|
4905
4989
|
if (truncated.length > 0) {
|
|
4906
|
-
if (column?.segments !== undefined
|
|
4990
|
+
if (column?.segments !== undefined) {
|
|
4991
|
+
let remaining = [...truncated].length;
|
|
4907
4992
|
for (const segment of column.segments) {
|
|
4993
|
+
if (remaining <= 0)
|
|
4994
|
+
break;
|
|
4908
4995
|
if (segment.text.length === 0)
|
|
4909
4996
|
continue;
|
|
4910
|
-
|
|
4997
|
+
const chars = [...segment.text];
|
|
4998
|
+
if (chars.length <= remaining) {
|
|
4999
|
+
segments.push({
|
|
5000
|
+
text: segment.text,
|
|
5001
|
+
...column.style === undefined ? {} : { style: column.style },
|
|
5002
|
+
...segment.color === undefined ? {} : { color: segment.color }
|
|
5003
|
+
});
|
|
5004
|
+
remaining -= chars.length;
|
|
5005
|
+
} else {
|
|
5006
|
+
segments.push({
|
|
5007
|
+
text: chars.slice(0, remaining).join(""),
|
|
5008
|
+
...column.style === undefined ? {} : { style: column.style },
|
|
5009
|
+
...segment.color === undefined ? {} : { color: segment.color }
|
|
5010
|
+
});
|
|
5011
|
+
remaining = 0;
|
|
5012
|
+
break;
|
|
5013
|
+
}
|
|
4911
5014
|
}
|
|
4912
5015
|
} else {
|
|
4913
|
-
|
|
5016
|
+
segments.push({
|
|
5017
|
+
text: truncated,
|
|
5018
|
+
...column?.style === undefined ? {} : { style: column.style },
|
|
5019
|
+
...column?.color === undefined ? {} : { color: column.color }
|
|
5020
|
+
});
|
|
4914
5021
|
}
|
|
4915
5022
|
}
|
|
4916
5023
|
if (!isLast) {
|
|
4917
5024
|
const pad = cellWidth - visualLength(truncated);
|
|
4918
5025
|
if (pad > 0)
|
|
4919
|
-
|
|
5026
|
+
segments.push({ text: " ".repeat(pad) });
|
|
4920
5027
|
}
|
|
4921
5028
|
}
|
|
4922
|
-
while (
|
|
4923
|
-
|
|
4924
|
-
return
|
|
5029
|
+
while (segments.length > 0 && segments[segments.length - 1].text.trim().length === 0)
|
|
5030
|
+
segments.pop();
|
|
5031
|
+
return segments;
|
|
5032
|
+
}
|
|
5033
|
+
function renderColumns(row, layout) {
|
|
5034
|
+
return layoutListRowSegments(row, layout).map((segment) => styleToChunk(segment.text, segment.style, segment.color));
|
|
4925
5035
|
}
|
|
4926
5036
|
function highlightChunk(chunk, selectedBg) {
|
|
4927
5037
|
const current = chunk.fg;
|
|
@@ -5017,6 +5127,393 @@ function renderListRows(state, focused, width, hoveredId) {
|
|
|
5017
5127
|
return new StyledText2(allChunks);
|
|
5018
5128
|
}
|
|
5019
5129
|
|
|
5130
|
+
// src/ui/panes/list-text.ts
|
|
5131
|
+
import { parseColor } from "@opentui/core";
|
|
5132
|
+
|
|
5133
|
+
// src/domain/diff/cell-width.ts
|
|
5134
|
+
var WIDE_RANGES = [
|
|
5135
|
+
[4352, 4447],
|
|
5136
|
+
[11904, 42191],
|
|
5137
|
+
[44032, 55203],
|
|
5138
|
+
[63744, 64255],
|
|
5139
|
+
[65072, 65135],
|
|
5140
|
+
[65280, 65376],
|
|
5141
|
+
[65504, 65510],
|
|
5142
|
+
[127744, 129535],
|
|
5143
|
+
[129648, 129791],
|
|
5144
|
+
[131072, 262141]
|
|
5145
|
+
];
|
|
5146
|
+
function isWide(grapheme) {
|
|
5147
|
+
const code = grapheme.codePointAt(0);
|
|
5148
|
+
if (code === undefined)
|
|
5149
|
+
return false;
|
|
5150
|
+
for (const [low, high] of WIDE_RANGES) {
|
|
5151
|
+
if (code >= low && code <= high)
|
|
5152
|
+
return true;
|
|
5153
|
+
}
|
|
5154
|
+
return false;
|
|
5155
|
+
}
|
|
5156
|
+
function cellWidth(value) {
|
|
5157
|
+
let ascii = true;
|
|
5158
|
+
for (let index = 0;index < value.length; index++) {
|
|
5159
|
+
if (value.charCodeAt(index) > 127) {
|
|
5160
|
+
ascii = false;
|
|
5161
|
+
break;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
if (ascii)
|
|
5165
|
+
return value.length;
|
|
5166
|
+
let width = 0;
|
|
5167
|
+
for (const codePoint of value)
|
|
5168
|
+
width += isWide(codePoint) ? 2 : 1;
|
|
5169
|
+
return width;
|
|
5170
|
+
}
|
|
5171
|
+
|
|
5172
|
+
// src/ui/panes/pane-text.ts
|
|
5173
|
+
function internalsOf(text) {
|
|
5174
|
+
const candidate = text;
|
|
5175
|
+
const buffer = candidate.textBuffer;
|
|
5176
|
+
const style = candidate._textBufferSyntaxStyle;
|
|
5177
|
+
if (buffer === undefined || style === undefined)
|
|
5178
|
+
return;
|
|
5179
|
+
if (typeof buffer.setText !== "function" || typeof buffer.addHighlight !== "function" || typeof buffer.clearAllHighlights !== "function")
|
|
5180
|
+
return;
|
|
5181
|
+
if (typeof buffer.clearLineHighlights !== "function")
|
|
5182
|
+
return;
|
|
5183
|
+
if (typeof style.registerStyle !== "function" || typeof candidate.updateTextInfo !== "function")
|
|
5184
|
+
return;
|
|
5185
|
+
return candidate;
|
|
5186
|
+
}
|
|
5187
|
+
function paneTextBuffer(text) {
|
|
5188
|
+
const internals = internalsOf(text);
|
|
5189
|
+
if (internals === undefined)
|
|
5190
|
+
return;
|
|
5191
|
+
internals._hasManualStyledText = true;
|
|
5192
|
+
return {
|
|
5193
|
+
setText(value) {
|
|
5194
|
+
internals.textBuffer.setText(value);
|
|
5195
|
+
internals.updateTextInfo();
|
|
5196
|
+
},
|
|
5197
|
+
addHighlight(row, highlight) {
|
|
5198
|
+
internals.textBuffer.addHighlight(row, highlight);
|
|
5199
|
+
},
|
|
5200
|
+
clearRow(row) {
|
|
5201
|
+
internals.textBuffer.clearLineHighlights(row);
|
|
5202
|
+
},
|
|
5203
|
+
clearAllHighlights() {
|
|
5204
|
+
internals.textBuffer.clearAllHighlights();
|
|
5205
|
+
},
|
|
5206
|
+
registerStyle(name, definition) {
|
|
5207
|
+
return internals._textBufferSyntaxStyle.registerStyle(name, definition);
|
|
5208
|
+
},
|
|
5209
|
+
refresh() {
|
|
5210
|
+
internals.updateTextInfo();
|
|
5211
|
+
}
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
function onPaneLifecyclePass(text, callback) {
|
|
5215
|
+
const host = text;
|
|
5216
|
+
const previous = host.onLifecyclePass;
|
|
5217
|
+
host.onLifecyclePass = () => {
|
|
5218
|
+
previous?.call(text);
|
|
5219
|
+
callback();
|
|
5220
|
+
};
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
// src/ui/panes/viewport-highlights.ts
|
|
5224
|
+
var MARGIN_LINES = 32;
|
|
5225
|
+
var LINE_END_COLS = 1e6;
|
|
5226
|
+
function createViewportHighlights(text, spec) {
|
|
5227
|
+
const { buffer, paintLine, scrollY: logicalScrollY } = spec;
|
|
5228
|
+
let content;
|
|
5229
|
+
let installed = "";
|
|
5230
|
+
let active = false;
|
|
5231
|
+
let rowSources;
|
|
5232
|
+
let rowSourcesWidth = -1;
|
|
5233
|
+
let appliedScrollY = -1;
|
|
5234
|
+
let appliedHeight = -1;
|
|
5235
|
+
let painted;
|
|
5236
|
+
const paint = (force) => {
|
|
5237
|
+
if (!active)
|
|
5238
|
+
return;
|
|
5239
|
+
const height = Math.max(1, Math.floor(text.height));
|
|
5240
|
+
const scrollY = Math.max(0, Math.floor(logicalScrollY?.(content) ?? text.scrollY));
|
|
5241
|
+
const width = Math.max(1, Math.floor(text.width));
|
|
5242
|
+
if (!force && painted !== undefined && appliedScrollY === scrollY && appliedHeight === height && rowSourcesWidth === width)
|
|
5243
|
+
return;
|
|
5244
|
+
if (rowSources === undefined || rowSourcesWidth !== width) {
|
|
5245
|
+
rowSources = text.lineInfo.lineSources;
|
|
5246
|
+
rowSourcesWidth = width;
|
|
5247
|
+
}
|
|
5248
|
+
const sources = rowSources;
|
|
5249
|
+
const lastRow = Math.max(0, Math.min(scrollY + height - 1, sources.length - 1));
|
|
5250
|
+
const firstLine = sources[Math.min(scrollY, lastRow)] ?? scrollY;
|
|
5251
|
+
const lastLine = sources[lastRow] ?? lastRow;
|
|
5252
|
+
const from = Math.max(0, firstLine - MARGIN_LINES);
|
|
5253
|
+
const to = lastLine + MARGIN_LINES;
|
|
5254
|
+
const previous = painted;
|
|
5255
|
+
if (force || previous === undefined || previous.to < from || previous.from > to) {
|
|
5256
|
+
buffer.clearAllHighlights();
|
|
5257
|
+
for (let line = from;line <= to; line++)
|
|
5258
|
+
paintLine(line, content);
|
|
5259
|
+
} else {
|
|
5260
|
+
for (let line = previous.from;line < from; line++)
|
|
5261
|
+
buffer.clearRow(line);
|
|
5262
|
+
for (let line = to + 1;line <= previous.to; line++)
|
|
5263
|
+
buffer.clearRow(line);
|
|
5264
|
+
for (let line = from;line < previous.from; line++)
|
|
5265
|
+
paintLine(line, content);
|
|
5266
|
+
for (let line = previous.to + 1;line <= to; line++)
|
|
5267
|
+
paintLine(line, content);
|
|
5268
|
+
}
|
|
5269
|
+
painted = { from, to };
|
|
5270
|
+
appliedScrollY = scrollY;
|
|
5271
|
+
appliedHeight = height;
|
|
5272
|
+
};
|
|
5273
|
+
onPaneLifecyclePass(text, () => paint(false));
|
|
5274
|
+
return {
|
|
5275
|
+
install(full, next) {
|
|
5276
|
+
content = next;
|
|
5277
|
+
active = true;
|
|
5278
|
+
const changed = installed !== full;
|
|
5279
|
+
if (changed) {
|
|
5280
|
+
installed = full;
|
|
5281
|
+
buffer.setText(full);
|
|
5282
|
+
rowSources = undefined;
|
|
5283
|
+
painted = undefined;
|
|
5284
|
+
}
|
|
5285
|
+
paint(changed);
|
|
5286
|
+
},
|
|
5287
|
+
repaint() {
|
|
5288
|
+
paint(true);
|
|
5289
|
+
},
|
|
5290
|
+
release() {
|
|
5291
|
+
if (!active)
|
|
5292
|
+
return;
|
|
5293
|
+
buffer.clearAllHighlights();
|
|
5294
|
+
active = false;
|
|
5295
|
+
installed = "";
|
|
5296
|
+
rowSources = undefined;
|
|
5297
|
+
rowSourcesWidth = -1;
|
|
5298
|
+
appliedScrollY = -1;
|
|
5299
|
+
appliedHeight = -1;
|
|
5300
|
+
painted = undefined;
|
|
5301
|
+
}
|
|
5302
|
+
};
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
// src/ui/panes/list-text.ts
|
|
5306
|
+
var painters = new WeakMap;
|
|
5307
|
+
function resolveSegment(text, style, color) {
|
|
5308
|
+
let fg3;
|
|
5309
|
+
let dim2;
|
|
5310
|
+
if (color !== undefined) {
|
|
5311
|
+
fg3 = parseColor(color);
|
|
5312
|
+
} else {
|
|
5313
|
+
switch (style) {
|
|
5314
|
+
case "dim":
|
|
5315
|
+
dim2 = true;
|
|
5316
|
+
break;
|
|
5317
|
+
case "cyan":
|
|
5318
|
+
fg3 = ANSI_CYAN;
|
|
5319
|
+
break;
|
|
5320
|
+
case "green":
|
|
5321
|
+
fg3 = ANSI_GREEN;
|
|
5322
|
+
break;
|
|
5323
|
+
case "yellow":
|
|
5324
|
+
fg3 = ANSI_YELLOW;
|
|
5325
|
+
break;
|
|
5326
|
+
case "magenta":
|
|
5327
|
+
fg3 = ANSI_MAGENTA;
|
|
5328
|
+
break;
|
|
5329
|
+
case "default":
|
|
5330
|
+
case undefined:
|
|
5331
|
+
break;
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
return {
|
|
5335
|
+
text,
|
|
5336
|
+
...fg3 === undefined ? {} : { fg: fg3 },
|
|
5337
|
+
...dim2 === undefined ? {} : { dim: dim2 }
|
|
5338
|
+
};
|
|
5339
|
+
}
|
|
5340
|
+
function styleKey(fg3, bold3, dim2, bg2) {
|
|
5341
|
+
const fgKey = fg3 === undefined ? "" : fg3.toInts().join(",");
|
|
5342
|
+
const bgKey = bg2 === undefined ? "" : bg2.toInts().join(",");
|
|
5343
|
+
return `${fgKey}|${bold3 ? 1 : 0}|${dim2 ? 1 : 0}|${bgKey}`;
|
|
5344
|
+
}
|
|
5345
|
+
function styleIdFor(record, fg3, bold3, dim2, bg2) {
|
|
5346
|
+
const key = styleKey(fg3, bold3, dim2, bg2);
|
|
5347
|
+
const cached = record.styleIds.get(key);
|
|
5348
|
+
if (cached !== undefined)
|
|
5349
|
+
return cached;
|
|
5350
|
+
const id = record.buffer.registerStyle(`githunk.list.${record.styleIds.size}`, {
|
|
5351
|
+
...fg3 === undefined ? {} : { fg: fg3 },
|
|
5352
|
+
...bg2 === undefined ? {} : { bg: bg2 },
|
|
5353
|
+
...bold3 ? { bold: bold3 } : {},
|
|
5354
|
+
...dim2 ? { dim: dim2 } : {}
|
|
5355
|
+
});
|
|
5356
|
+
record.styleIds.set(key, id);
|
|
5357
|
+
return id;
|
|
5358
|
+
}
|
|
5359
|
+
function rowVisual(state, focused, hoveredId, rowIndexById, range, displayRow) {
|
|
5360
|
+
if (displayRow.kind !== "item")
|
|
5361
|
+
return 0;
|
|
5362
|
+
if (focused && displayRow.id === state.selectedId)
|
|
5363
|
+
return 1;
|
|
5364
|
+
if (range !== undefined) {
|
|
5365
|
+
const index = rowIndexById.get(displayRow.id);
|
|
5366
|
+
if (index !== undefined && index >= range.startIndex && index <= range.endIndex)
|
|
5367
|
+
return 2;
|
|
5368
|
+
}
|
|
5369
|
+
if (displayRow.id === hoveredId)
|
|
5370
|
+
return 3;
|
|
5371
|
+
return 0;
|
|
5372
|
+
}
|
|
5373
|
+
function layoutFor(state, safeWidth) {
|
|
5374
|
+
const displayRows = state.displayRows;
|
|
5375
|
+
const rowMap = new Map(state.rows.map((row) => [row.id, row]));
|
|
5376
|
+
const visibleRows = displayRows.flatMap((dr) => {
|
|
5377
|
+
if (dr.kind !== "item")
|
|
5378
|
+
return [];
|
|
5379
|
+
const row = rowMap.get(dr.id);
|
|
5380
|
+
return row === undefined ? [] : [row];
|
|
5381
|
+
});
|
|
5382
|
+
const layout = computeColumnLayout(visibleRows, safeWidth);
|
|
5383
|
+
const rowTexts = [];
|
|
5384
|
+
const rowSegments = [];
|
|
5385
|
+
for (const dr of displayRows) {
|
|
5386
|
+
if (dr.kind !== "item") {
|
|
5387
|
+
const truncated = [...dr.text].slice(0, safeWidth).join("");
|
|
5388
|
+
rowTexts.push(truncated);
|
|
5389
|
+
rowSegments.push(truncated.length === 0 ? [] : [{ text: truncated }]);
|
|
5390
|
+
continue;
|
|
5391
|
+
}
|
|
5392
|
+
const row = rowMap.get(dr.id);
|
|
5393
|
+
const laidOut = row === undefined ? [] : layoutListRowSegments(row, layout).map((segment) => resolveSegment(segment.text, segment.style, segment.color));
|
|
5394
|
+
const joined = laidOut.map((segment) => segment.text).join("");
|
|
5395
|
+
const pad = Math.max(0, safeWidth - cellWidth(joined));
|
|
5396
|
+
rowTexts.push(pad === 0 ? joined : `${joined}${" ".repeat(pad)}`);
|
|
5397
|
+
rowSegments.push(laidOut);
|
|
5398
|
+
}
|
|
5399
|
+
return { rows: state.rows, width: safeWidth, rowTexts, rowSegments, joined: rowTexts.join(`
|
|
5400
|
+
`) };
|
|
5401
|
+
}
|
|
5402
|
+
function visualsFor(state, focused, hoveredId) {
|
|
5403
|
+
const rowIndexById = new Map(state.rows.map((row, index) => [row.id, index]));
|
|
5404
|
+
const rangeActive = focused && isListRangeActive(state);
|
|
5405
|
+
const range = rangeActive ? getListSelectionRange(state) : undefined;
|
|
5406
|
+
return state.displayRows.map((dr) => rowVisual(state, focused, hoveredId, rowIndexById, range, dr));
|
|
5407
|
+
}
|
|
5408
|
+
function paintRow(record, line) {
|
|
5409
|
+
const snap = record.ref.snap;
|
|
5410
|
+
if (snap === undefined)
|
|
5411
|
+
return;
|
|
5412
|
+
const segments = snap.rowSegments[line];
|
|
5413
|
+
const visual = snap.visuals[line];
|
|
5414
|
+
if (segments === undefined || visual === undefined)
|
|
5415
|
+
return;
|
|
5416
|
+
const { buffer } = record;
|
|
5417
|
+
buffer.clearRow(line);
|
|
5418
|
+
const bg2 = visual === 1 || visual === 2 ? SELECTED_LINE_BG : visual === 3 ? HOVER_LINE_BG : undefined;
|
|
5419
|
+
let column = 0;
|
|
5420
|
+
for (const segment of segments) {
|
|
5421
|
+
const cells = cellWidth(segment.text);
|
|
5422
|
+
if (cells > 0) {
|
|
5423
|
+
if (visual === 1) {
|
|
5424
|
+
buffer.addHighlight(line, {
|
|
5425
|
+
start: column,
|
|
5426
|
+
end: column + cells,
|
|
5427
|
+
styleId: styleIdFor(record, segment.fg === undefined ? undefined : brightenAnsiForeground(segment.fg), true, segment.dim === true, bg2)
|
|
5428
|
+
});
|
|
5429
|
+
} else if (bg2 !== undefined) {
|
|
5430
|
+
buffer.addHighlight(line, {
|
|
5431
|
+
start: column,
|
|
5432
|
+
end: column + cells,
|
|
5433
|
+
styleId: styleIdFor(record, segment.fg, false, segment.dim === true, bg2)
|
|
5434
|
+
});
|
|
5435
|
+
} else if (segment.fg !== undefined || segment.dim === true) {
|
|
5436
|
+
buffer.addHighlight(line, {
|
|
5437
|
+
start: column,
|
|
5438
|
+
end: column + cells,
|
|
5439
|
+
styleId: styleIdFor(record, segment.fg, false, segment.dim === true, undefined)
|
|
5440
|
+
});
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
column += cells;
|
|
5444
|
+
}
|
|
5445
|
+
if (bg2 !== undefined) {
|
|
5446
|
+
buffer.addHighlight(line, { start: column, end: LINE_END_COLS, styleId: styleIdFor(record, undefined, false, false, bg2) });
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
function ensurePainter(text, buffer) {
|
|
5450
|
+
const existing = painters.get(text);
|
|
5451
|
+
if (existing !== undefined)
|
|
5452
|
+
return existing;
|
|
5453
|
+
const ref = { snap: undefined };
|
|
5454
|
+
const record = {
|
|
5455
|
+
buffer,
|
|
5456
|
+
viewport: undefined,
|
|
5457
|
+
ref,
|
|
5458
|
+
styleIds: new Map,
|
|
5459
|
+
cache: undefined,
|
|
5460
|
+
joined: "",
|
|
5461
|
+
visuals: []
|
|
5462
|
+
};
|
|
5463
|
+
record.viewport = createViewportHighlights(text, {
|
|
5464
|
+
buffer,
|
|
5465
|
+
paintLine: (line) => paintRow(record, line)
|
|
5466
|
+
});
|
|
5467
|
+
painters.set(text, record);
|
|
5468
|
+
return record;
|
|
5469
|
+
}
|
|
5470
|
+
function installListText(text, content) {
|
|
5471
|
+
const buffer = paneTextBuffer(text);
|
|
5472
|
+
if (buffer === undefined) {
|
|
5473
|
+
text.content = renderListRows(content.state, content.focused, content.width, content.hoveredId);
|
|
5474
|
+
return;
|
|
5475
|
+
}
|
|
5476
|
+
const record = ensurePainter(text, buffer);
|
|
5477
|
+
const safeWidth = Math.max(0, Math.floor(content.width));
|
|
5478
|
+
const previousCache = record.cache;
|
|
5479
|
+
const cache = previousCache !== undefined && previousCache.rows === content.state.rows && previousCache.width === safeWidth ? previousCache : layoutFor(content.state, safeWidth);
|
|
5480
|
+
record.cache = cache;
|
|
5481
|
+
const visuals = visualsFor(content.state, content.focused, content.hoveredId);
|
|
5482
|
+
record.ref.snap = { rowSegments: cache.rowSegments, visuals };
|
|
5483
|
+
if (cache.joined !== record.joined) {
|
|
5484
|
+
record.joined = cache.joined;
|
|
5485
|
+
record.visuals = visuals;
|
|
5486
|
+
record.viewport.install(cache.joined, record.ref);
|
|
5487
|
+
return;
|
|
5488
|
+
}
|
|
5489
|
+
if (cache !== previousCache) {
|
|
5490
|
+
record.visuals = visuals;
|
|
5491
|
+
record.viewport.repaint();
|
|
5492
|
+
record.buffer.refresh();
|
|
5493
|
+
return;
|
|
5494
|
+
}
|
|
5495
|
+
const previous = record.visuals;
|
|
5496
|
+
record.visuals = visuals;
|
|
5497
|
+
let repainted = false;
|
|
5498
|
+
const lines = Math.max(previous.length, visuals.length);
|
|
5499
|
+
for (let line = 0;line < lines; line++) {
|
|
5500
|
+
if (previous[line] !== visuals[line]) {
|
|
5501
|
+
paintRow(record, line);
|
|
5502
|
+
repainted = true;
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
if (repainted)
|
|
5506
|
+
record.buffer.refresh();
|
|
5507
|
+
}
|
|
5508
|
+
function releaseListText(text) {
|
|
5509
|
+
const record = painters.get(text);
|
|
5510
|
+
if (record === undefined)
|
|
5511
|
+
return;
|
|
5512
|
+
record.viewport.release();
|
|
5513
|
+
record.buffer.refresh();
|
|
5514
|
+
painters.delete(text);
|
|
5515
|
+
}
|
|
5516
|
+
|
|
5020
5517
|
// src/ui/panes/branches-pane.ts
|
|
5021
5518
|
function localBranchRows(model, filter = "", options = {}) {
|
|
5022
5519
|
const listing = model.branches;
|
|
@@ -5067,8 +5564,7 @@ function createBranchesPane(renderer, model) {
|
|
|
5067
5564
|
const rows = localBranchRows(model);
|
|
5068
5565
|
const displayRows = rows.length === 0 ? [{ kind: "message", text: "No branches" }] : undefined;
|
|
5069
5566
|
const state = createListState(rows, displayRows);
|
|
5070
|
-
|
|
5071
|
-
pane.update(content);
|
|
5567
|
+
installListText(pane.text, { state, width: 80, focused: false });
|
|
5072
5568
|
pane.syncScrollbar();
|
|
5073
5569
|
return pane;
|
|
5074
5570
|
}
|
|
@@ -5389,47 +5885,6 @@ function commitGraphRows(commits, getColor) {
|
|
|
5389
5885
|
|
|
5390
5886
|
// src/ui/author-style.ts
|
|
5391
5887
|
import { createHash as createHash2 } from "node:crypto";
|
|
5392
|
-
|
|
5393
|
-
// src/ui/cell-width.ts
|
|
5394
|
-
var WIDE_RANGES = [
|
|
5395
|
-
[4352, 4447],
|
|
5396
|
-
[11904, 42191],
|
|
5397
|
-
[44032, 55203],
|
|
5398
|
-
[63744, 64255],
|
|
5399
|
-
[65072, 65135],
|
|
5400
|
-
[65280, 65376],
|
|
5401
|
-
[65504, 65510],
|
|
5402
|
-
[127744, 129535],
|
|
5403
|
-
[129648, 129791],
|
|
5404
|
-
[131072, 262141]
|
|
5405
|
-
];
|
|
5406
|
-
function isWide(grapheme) {
|
|
5407
|
-
const code = grapheme.codePointAt(0);
|
|
5408
|
-
if (code === undefined)
|
|
5409
|
-
return false;
|
|
5410
|
-
for (const [low, high] of WIDE_RANGES) {
|
|
5411
|
-
if (code >= low && code <= high)
|
|
5412
|
-
return true;
|
|
5413
|
-
}
|
|
5414
|
-
return false;
|
|
5415
|
-
}
|
|
5416
|
-
function cellWidth(value) {
|
|
5417
|
-
let ascii = true;
|
|
5418
|
-
for (let index = 0;index < value.length; index++) {
|
|
5419
|
-
if (value.charCodeAt(index) > 127) {
|
|
5420
|
-
ascii = false;
|
|
5421
|
-
break;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
if (ascii)
|
|
5425
|
-
return value.length;
|
|
5426
|
-
let width = 0;
|
|
5427
|
-
for (const codePoint of value)
|
|
5428
|
-
width += isWide(codePoint) ? 2 : 1;
|
|
5429
|
-
return width;
|
|
5430
|
-
}
|
|
5431
|
-
|
|
5432
|
-
// src/ui/author-style.ts
|
|
5433
5888
|
var initialsCache = new Map;
|
|
5434
5889
|
var colorCache = new Map;
|
|
5435
5890
|
function randInt(bytes, max) {
|
|
@@ -5518,6 +5973,7 @@ function reflogRows(model, filter = "") {
|
|
|
5518
5973
|
|
|
5519
5974
|
// src/ui/panes/commits-pane.ts
|
|
5520
5975
|
var paneStates = new WeakMap;
|
|
5976
|
+
var COMMIT_THRESHOLD = 200;
|
|
5521
5977
|
function formatRelativeTime(authoredAt, now) {
|
|
5522
5978
|
const date = new Date(authoredAt);
|
|
5523
5979
|
if (Number.isNaN(date.getTime()))
|
|
@@ -5559,20 +6015,22 @@ function buildCommitRows(commits, now, filter = "") {
|
|
|
5559
6015
|
const shortHash2 = commit.oid.length >= 8 ? commit.oid.slice(0, 8) : commit.shortOid;
|
|
5560
6016
|
const initials = authorInitials(commit.authorName).padEnd(AUTHOR_COLUMN_WIDTH, " ");
|
|
5561
6017
|
const relative = formatRelativeTime(commit.authoredAt, now);
|
|
6018
|
+
const graphText = graph?.text ?? "";
|
|
6019
|
+
const graphSegments = graph?.segments ?? [];
|
|
6020
|
+
const subjectSegments = commit.subject.length === 0 ? [] : [{ text: commit.subject }];
|
|
5562
6021
|
return {
|
|
5563
6022
|
id: commit.oid,
|
|
5564
6023
|
columns: [
|
|
5565
6024
|
{ text: shortHash2, priority: 1, color: commitHashColor(commit.status) },
|
|
5566
|
-
{ text: initials, priority:
|
|
5567
|
-
{ text:
|
|
5568
|
-
{ text:
|
|
5569
|
-
{ text: relative, priority: 4, style: "dim" }
|
|
6025
|
+
{ text: initials, priority: 2, color: authorColor(commit.authorName) },
|
|
6026
|
+
{ text: `${graphText}${commit.subject}`, priority: 2, flex: true, segments: [...graphSegments, ...subjectSegments] },
|
|
6027
|
+
{ text: relative, priority: 0, style: "dim" }
|
|
5570
6028
|
]
|
|
5571
6029
|
};
|
|
5572
6030
|
});
|
|
5573
6031
|
if (filter.length === 0)
|
|
5574
6032
|
return rows;
|
|
5575
|
-
return [...filterItems(filter, rows, (row) => `${row.columns[0]?.text ?? ""} ${row.columns[
|
|
6033
|
+
return [...filterItems(filter, rows, (row) => `${row.columns[0]?.text ?? ""} ${row.columns[2]?.text ?? row.id}`)];
|
|
5576
6034
|
}
|
|
5577
6035
|
function createCommitsPane(renderer, model) {
|
|
5578
6036
|
const pane = createPane(renderer, "commits", "", "No commit selected", false, {
|
|
@@ -5586,6 +6044,7 @@ function updateCommitsPane(pane, model) {
|
|
|
5586
6044
|
if (commits.length === 0) {
|
|
5587
6045
|
const empty = createListState([]);
|
|
5588
6046
|
paneStates.set(pane, empty);
|
|
6047
|
+
releaseListText(pane.text);
|
|
5589
6048
|
pane.update(model.loading ? "Loading…" : "No commits");
|
|
5590
6049
|
return;
|
|
5591
6050
|
}
|
|
@@ -5599,8 +6058,7 @@ function updateCommitsPane(pane, model) {
|
|
|
5599
6058
|
state = withPrev;
|
|
5600
6059
|
}
|
|
5601
6060
|
paneStates.set(pane, state);
|
|
5602
|
-
|
|
5603
|
-
pane.update(content);
|
|
6061
|
+
installListText(pane.text, { state, width: 80, focused: false });
|
|
5604
6062
|
pane.box.bottomTitle = undefined;
|
|
5605
6063
|
}
|
|
5606
6064
|
|
|
@@ -5769,150 +6227,23 @@ function parseAnsi(input) {
|
|
|
5769
6227
|
out.push(char);
|
|
5770
6228
|
row++;
|
|
5771
6229
|
column = 0;
|
|
5772
|
-
runStart = 0;
|
|
5773
|
-
index += 1;
|
|
5774
|
-
continue;
|
|
5775
|
-
}
|
|
5776
|
-
const codePoint = input.codePointAt(index);
|
|
5777
|
-
const size = codePoint > 65535 ? 2 : 1;
|
|
5778
|
-
out.push(input.slice(index, index + size));
|
|
5779
|
-
column += 1;
|
|
5780
|
-
index += size;
|
|
5781
|
-
}
|
|
5782
|
-
closeRun();
|
|
5783
|
-
return { text: out.join(""), spans };
|
|
5784
|
-
}
|
|
5785
|
-
|
|
5786
|
-
// src/ui/panes/command-log-pane.ts
|
|
5787
|
-
import { BoxRenderable as BoxRenderable3, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
5788
|
-
|
|
5789
|
-
// src/ui/panes/pane-text.ts
|
|
5790
|
-
function internalsOf(text) {
|
|
5791
|
-
const candidate = text;
|
|
5792
|
-
const buffer = candidate.textBuffer;
|
|
5793
|
-
const style = candidate._textBufferSyntaxStyle;
|
|
5794
|
-
if (buffer === undefined || style === undefined)
|
|
5795
|
-
return;
|
|
5796
|
-
if (typeof buffer.setText !== "function" || typeof buffer.addHighlight !== "function" || typeof buffer.clearAllHighlights !== "function")
|
|
5797
|
-
return;
|
|
5798
|
-
if (typeof buffer.clearLineHighlights !== "function")
|
|
5799
|
-
return;
|
|
5800
|
-
if (typeof style.registerStyle !== "function" || typeof candidate.updateTextInfo !== "function")
|
|
5801
|
-
return;
|
|
5802
|
-
return candidate;
|
|
5803
|
-
}
|
|
5804
|
-
function paneTextBuffer(text) {
|
|
5805
|
-
const internals = internalsOf(text);
|
|
5806
|
-
if (internals === undefined)
|
|
5807
|
-
return;
|
|
5808
|
-
internals._hasManualStyledText = true;
|
|
5809
|
-
return {
|
|
5810
|
-
setText(value) {
|
|
5811
|
-
internals.textBuffer.setText(value);
|
|
5812
|
-
internals.updateTextInfo();
|
|
5813
|
-
},
|
|
5814
|
-
addHighlight(row, highlight) {
|
|
5815
|
-
internals.textBuffer.addHighlight(row, highlight);
|
|
5816
|
-
},
|
|
5817
|
-
clearRow(row) {
|
|
5818
|
-
internals.textBuffer.clearLineHighlights(row);
|
|
5819
|
-
},
|
|
5820
|
-
clearAllHighlights() {
|
|
5821
|
-
internals.textBuffer.clearAllHighlights();
|
|
5822
|
-
},
|
|
5823
|
-
registerStyle(name, definition) {
|
|
5824
|
-
return internals._textBufferSyntaxStyle.registerStyle(name, definition);
|
|
5825
|
-
}
|
|
5826
|
-
};
|
|
5827
|
-
}
|
|
5828
|
-
function onPaneLifecyclePass(text, callback) {
|
|
5829
|
-
const host = text;
|
|
5830
|
-
const previous = host.onLifecyclePass;
|
|
5831
|
-
host.onLifecyclePass = () => {
|
|
5832
|
-
previous?.call(text);
|
|
5833
|
-
callback();
|
|
5834
|
-
};
|
|
5835
|
-
}
|
|
5836
|
-
|
|
5837
|
-
// src/ui/panes/viewport-highlights.ts
|
|
5838
|
-
var MARGIN_LINES = 32;
|
|
5839
|
-
var LINE_END_COLS = 1e6;
|
|
5840
|
-
function createViewportHighlights(text, spec) {
|
|
5841
|
-
const { buffer, paintLine } = spec;
|
|
5842
|
-
let content;
|
|
5843
|
-
let installed = "";
|
|
5844
|
-
let active = false;
|
|
5845
|
-
let rowSources;
|
|
5846
|
-
let rowSourcesWidth = -1;
|
|
5847
|
-
let appliedScrollY = -1;
|
|
5848
|
-
let appliedHeight = -1;
|
|
5849
|
-
let painted;
|
|
5850
|
-
const paint = (force) => {
|
|
5851
|
-
if (!active)
|
|
5852
|
-
return;
|
|
5853
|
-
const height = Math.max(1, Math.floor(text.height));
|
|
5854
|
-
const scrollY = Math.max(0, Math.floor(text.scrollY));
|
|
5855
|
-
const width = Math.max(1, Math.floor(text.width));
|
|
5856
|
-
if (!force && painted !== undefined && appliedScrollY === scrollY && appliedHeight === height && rowSourcesWidth === width)
|
|
5857
|
-
return;
|
|
5858
|
-
if (rowSources === undefined || rowSourcesWidth !== width) {
|
|
5859
|
-
rowSources = text.lineInfo.lineSources;
|
|
5860
|
-
rowSourcesWidth = width;
|
|
5861
|
-
}
|
|
5862
|
-
const sources = rowSources;
|
|
5863
|
-
const lastRow = Math.max(0, Math.min(scrollY + height - 1, sources.length - 1));
|
|
5864
|
-
const firstLine = sources[Math.min(scrollY, lastRow)] ?? scrollY;
|
|
5865
|
-
const lastLine = sources[lastRow] ?? lastRow;
|
|
5866
|
-
const from = Math.max(0, firstLine - MARGIN_LINES);
|
|
5867
|
-
const to = lastLine + MARGIN_LINES;
|
|
5868
|
-
const previous = painted;
|
|
5869
|
-
if (previous === undefined || previous.to < from || previous.from > to) {
|
|
5870
|
-
buffer.clearAllHighlights();
|
|
5871
|
-
for (let line = from;line <= to; line++)
|
|
5872
|
-
paintLine(line, content);
|
|
5873
|
-
} else {
|
|
5874
|
-
for (let line = previous.from;line < from; line++)
|
|
5875
|
-
buffer.clearRow(line);
|
|
5876
|
-
for (let line = to + 1;line <= previous.to; line++)
|
|
5877
|
-
buffer.clearRow(line);
|
|
5878
|
-
for (let line = from;line < previous.from; line++)
|
|
5879
|
-
paintLine(line, content);
|
|
5880
|
-
for (let line = previous.to + 1;line <= to; line++)
|
|
5881
|
-
paintLine(line, content);
|
|
5882
|
-
}
|
|
5883
|
-
painted = { from, to };
|
|
5884
|
-
appliedScrollY = scrollY;
|
|
5885
|
-
appliedHeight = height;
|
|
5886
|
-
};
|
|
5887
|
-
onPaneLifecyclePass(text, () => paint(false));
|
|
5888
|
-
return {
|
|
5889
|
-
install(full, next) {
|
|
5890
|
-
content = next;
|
|
5891
|
-
active = true;
|
|
5892
|
-
const changed = installed !== full;
|
|
5893
|
-
if (changed) {
|
|
5894
|
-
installed = full;
|
|
5895
|
-
buffer.setText(full);
|
|
5896
|
-
rowSources = undefined;
|
|
5897
|
-
painted = undefined;
|
|
5898
|
-
}
|
|
5899
|
-
paint(changed);
|
|
5900
|
-
},
|
|
5901
|
-
release() {
|
|
5902
|
-
if (!active)
|
|
5903
|
-
return;
|
|
5904
|
-
buffer.clearAllHighlights();
|
|
5905
|
-
active = false;
|
|
5906
|
-
installed = "";
|
|
5907
|
-
rowSources = undefined;
|
|
5908
|
-
rowSourcesWidth = -1;
|
|
5909
|
-
appliedScrollY = -1;
|
|
5910
|
-
appliedHeight = -1;
|
|
5911
|
-
painted = undefined;
|
|
6230
|
+
runStart = 0;
|
|
6231
|
+
index += 1;
|
|
6232
|
+
continue;
|
|
5912
6233
|
}
|
|
5913
|
-
|
|
6234
|
+
const codePoint = input.codePointAt(index);
|
|
6235
|
+
const size = codePoint > 65535 ? 2 : 1;
|
|
6236
|
+
out.push(input.slice(index, index + size));
|
|
6237
|
+
column += 1;
|
|
6238
|
+
index += size;
|
|
6239
|
+
}
|
|
6240
|
+
closeRun();
|
|
6241
|
+
return { text: out.join(""), spans };
|
|
5914
6242
|
}
|
|
5915
6243
|
|
|
6244
|
+
// src/ui/panes/command-log-pane.ts
|
|
6245
|
+
import { BoxRenderable as BoxRenderable3, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
6246
|
+
|
|
5916
6247
|
// src/ui/panes/command-log-text.ts
|
|
5917
6248
|
var STYLE_DEFINITIONS = {
|
|
5918
6249
|
action: { fg: ANSI_YELLOW },
|
|
@@ -5947,7 +6278,7 @@ function registerStyles(buffer) {
|
|
|
5947
6278
|
}
|
|
5948
6279
|
return ids;
|
|
5949
6280
|
}
|
|
5950
|
-
var
|
|
6281
|
+
var painters2 = new WeakMap;
|
|
5951
6282
|
function installCommandLogText(text, lines) {
|
|
5952
6283
|
const full = lines.map((line) => line.spans.map((span) => span.text).join("")).join(`
|
|
5953
6284
|
`);
|
|
@@ -5956,7 +6287,7 @@ function installCommandLogText(text, lines) {
|
|
|
5956
6287
|
text.content = full;
|
|
5957
6288
|
return;
|
|
5958
6289
|
}
|
|
5959
|
-
let painter =
|
|
6290
|
+
let painter = painters2.get(text);
|
|
5960
6291
|
if (painter === undefined) {
|
|
5961
6292
|
const styleIds = registerStyles(buffer);
|
|
5962
6293
|
painter = createViewportHighlights(text, {
|
|
@@ -5970,7 +6301,7 @@ function installCommandLogText(text, lines) {
|
|
|
5970
6301
|
}
|
|
5971
6302
|
}
|
|
5972
6303
|
});
|
|
5973
|
-
|
|
6304
|
+
painters2.set(text, painter);
|
|
5974
6305
|
}
|
|
5975
6306
|
painter.install(full, lines);
|
|
5976
6307
|
}
|
|
@@ -6452,7 +6783,7 @@ function createFilesPane(renderer, model) {
|
|
|
6452
6783
|
const initialRows = filesTreeRows(createFilesTreeState(model), model);
|
|
6453
6784
|
const displayRows = initialRows.length === 0 ? [{ kind: "message", text: NO_CHANGED_FILES }] : undefined;
|
|
6454
6785
|
const state = createListState(initialRows, displayRows);
|
|
6455
|
-
pane.
|
|
6786
|
+
installListText(pane.text, { state, width: 80, focused: false });
|
|
6456
6787
|
return pane;
|
|
6457
6788
|
}
|
|
6458
6789
|
function anyStagedChanges(model) {
|
|
@@ -6737,14 +7068,14 @@ function changedIndexesInDiffLineRange(document, state) {
|
|
|
6737
7068
|
|
|
6738
7069
|
// src/ui/panes/ansi-text.ts
|
|
6739
7070
|
import { StyledText as StyledText3, bold as boldChunk, dim as dimChunk, fg as fgChunk } from "@opentui/core";
|
|
6740
|
-
var
|
|
6741
|
-
function
|
|
7071
|
+
var painters3 = new WeakMap;
|
|
7072
|
+
function styleKey2(span) {
|
|
6742
7073
|
const color = span.fg;
|
|
6743
7074
|
const colorKey = color === undefined ? "-" : `${color.intent}:${color.slot}:${color.toInts().join(",")}`;
|
|
6744
7075
|
return `${colorKey}|${span.bold === true ? "b" : "-"}${span.dim === true ? "d" : "-"}`;
|
|
6745
7076
|
}
|
|
6746
|
-
function
|
|
6747
|
-
const key =
|
|
7077
|
+
function styleIdFor2(buffer, styleIds, span) {
|
|
7078
|
+
const key = styleKey2(span);
|
|
6748
7079
|
const existing = styleIds.get(key);
|
|
6749
7080
|
if (existing !== undefined)
|
|
6750
7081
|
return existing;
|
|
@@ -6826,7 +7157,7 @@ function installAnsiText(text, content) {
|
|
|
6826
7157
|
}
|
|
6827
7158
|
const { text: full, firstBodyRow } = joined(content);
|
|
6828
7159
|
const spansByRow = groupByRow(content.spans, firstBodyRow);
|
|
6829
|
-
let painter =
|
|
7160
|
+
let painter = painters3.get(text);
|
|
6830
7161
|
if (painter === undefined) {
|
|
6831
7162
|
const styleIds = new Map;
|
|
6832
7163
|
painter = {
|
|
@@ -6838,17 +7169,17 @@ function installAnsiText(text, content) {
|
|
|
6838
7169
|
if (spans === undefined)
|
|
6839
7170
|
return;
|
|
6840
7171
|
for (const span of spans) {
|
|
6841
|
-
buffer.addHighlight(row, { start: span.start, end: span.end, styleId:
|
|
7172
|
+
buffer.addHighlight(row, { start: span.start, end: span.end, styleId: styleIdFor2(buffer, styleIds, span) });
|
|
6842
7173
|
}
|
|
6843
7174
|
}
|
|
6844
7175
|
})
|
|
6845
7176
|
};
|
|
6846
|
-
|
|
7177
|
+
painters3.set(text, painter);
|
|
6847
7178
|
}
|
|
6848
7179
|
painter.highlights.install(full, spansByRow);
|
|
6849
7180
|
}
|
|
6850
7181
|
function releaseAnsiText(text) {
|
|
6851
|
-
|
|
7182
|
+
painters3.get(text)?.highlights.release();
|
|
6852
7183
|
}
|
|
6853
7184
|
|
|
6854
7185
|
// src/ui/panes/diff-text.ts
|
|
@@ -6937,7 +7268,7 @@ function statSpansForRow(value, spans) {
|
|
|
6937
7268
|
function preambleSpansForRow(value, spans) {
|
|
6938
7269
|
return spans === undefined ? [plainChunk3(value)] : statSpansForRow(value, spans);
|
|
6939
7270
|
}
|
|
6940
|
-
var
|
|
7271
|
+
var painters4 = new WeakMap;
|
|
6941
7272
|
function registerStyles2(buffer) {
|
|
6942
7273
|
const ids = {};
|
|
6943
7274
|
for (const [name, definition] of Object.entries(STYLE_DEFINITIONS2)) {
|
|
@@ -6975,7 +7306,7 @@ function styledChunk(style, value) {
|
|
|
6975
7306
|
}
|
|
6976
7307
|
function paintAsChunks2(text, content) {
|
|
6977
7308
|
const { text: full, firstDiffRow } = joined2(content);
|
|
6978
|
-
const preambleSpans = statSpansForPreamble(content.preamble);
|
|
7309
|
+
const preambleSpans = content.preambleSpans ?? statSpansForPreamble(content.preamble);
|
|
6979
7310
|
const rows = full.split(`
|
|
6980
7311
|
`);
|
|
6981
7312
|
const chunks = [];
|
|
@@ -7008,13 +7339,15 @@ function installDiffText(text, content) {
|
|
|
7008
7339
|
const paint = {
|
|
7009
7340
|
displayLines: content.displayLines,
|
|
7010
7341
|
firstDiffRow,
|
|
7011
|
-
preambleSpans: statSpansForPreamble(content.preamble)
|
|
7342
|
+
preambleSpans: content.preambleSpans ?? statSpansForPreamble(content.preamble),
|
|
7343
|
+
...content.highlightScrollY === undefined ? {} : { highlightScrollY: content.highlightScrollY }
|
|
7012
7344
|
};
|
|
7013
|
-
let painter =
|
|
7345
|
+
let painter = painters4.get(text);
|
|
7014
7346
|
if (painter === undefined) {
|
|
7015
7347
|
const styleIds = registerStyles2(buffer);
|
|
7016
7348
|
painter = createViewportHighlights(text, {
|
|
7017
7349
|
buffer,
|
|
7350
|
+
scrollY: (current) => current.highlightScrollY?.() ?? text.scrollY,
|
|
7018
7351
|
paintLine: (row, current) => {
|
|
7019
7352
|
const preambleSpans = current.preambleSpans.get(row);
|
|
7020
7353
|
if (preambleSpans !== undefined) {
|
|
@@ -7031,12 +7364,508 @@ function installDiffText(text, content) {
|
|
|
7031
7364
|
buffer.addHighlight(row, { start: display.gutterCols, end: LINE_END_COLS, styleId: styleIds[display.style] });
|
|
7032
7365
|
}
|
|
7033
7366
|
});
|
|
7034
|
-
|
|
7367
|
+
painters4.set(text, painter);
|
|
7035
7368
|
}
|
|
7036
7369
|
painter.install(full, paint);
|
|
7037
7370
|
}
|
|
7038
7371
|
function releaseDiffText(text) {
|
|
7039
|
-
|
|
7372
|
+
painters4.get(text)?.release();
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
// src/domain/diff/virtual.ts
|
|
7376
|
+
var VIRTUAL_DIFF_LINE_THRESHOLD = 1e4;
|
|
7377
|
+
function isSourceLine(line) {
|
|
7378
|
+
return line.kind === "context" || line.kind === "addition" || line.kind === "deletion";
|
|
7379
|
+
}
|
|
7380
|
+
function styleFor2(line) {
|
|
7381
|
+
if (line.kind === "addition")
|
|
7382
|
+
return "addition";
|
|
7383
|
+
if (line.kind === "deletion")
|
|
7384
|
+
return "deletion";
|
|
7385
|
+
if (line.kind === "hunk-header")
|
|
7386
|
+
return "hunk-header";
|
|
7387
|
+
if (line.kind === "metadata" || line.kind === "no-newline")
|
|
7388
|
+
return "metadata";
|
|
7389
|
+
return "plain";
|
|
7390
|
+
}
|
|
7391
|
+
function lineNumberWidth2(document) {
|
|
7392
|
+
let largest = 1;
|
|
7393
|
+
for (const line of document.lines) {
|
|
7394
|
+
largest = Math.max(largest, line.oldLine ?? 0, line.newLine ?? 0);
|
|
7395
|
+
}
|
|
7396
|
+
return String(largest).length;
|
|
7397
|
+
}
|
|
7398
|
+
function linePrefix(line, width) {
|
|
7399
|
+
if (!isSourceLine(line))
|
|
7400
|
+
return "";
|
|
7401
|
+
const old = line.oldLine === undefined ? "" : String(line.oldLine);
|
|
7402
|
+
const next = line.newLine === undefined ? "" : String(line.newLine);
|
|
7403
|
+
return `${old.padStart(width, " ")} ${next.padStart(width, " ")} `;
|
|
7404
|
+
}
|
|
7405
|
+
function withoutLineEnding2(raw) {
|
|
7406
|
+
if (raw.endsWith(`\r
|
|
7407
|
+
`))
|
|
7408
|
+
return raw.slice(0, -2);
|
|
7409
|
+
if (raw.endsWith(`
|
|
7410
|
+
`) || raw.endsWith("\r"))
|
|
7411
|
+
return raw.slice(0, -1);
|
|
7412
|
+
return raw;
|
|
7413
|
+
}
|
|
7414
|
+
function normalizePreamble(preamble) {
|
|
7415
|
+
if (preamble.length === 0)
|
|
7416
|
+
return { text: "", rows: [], starts: [], ends: [] };
|
|
7417
|
+
const text = preamble.endsWith(`
|
|
7418
|
+
`) ? preamble : `${preamble}
|
|
7419
|
+
`;
|
|
7420
|
+
const rows = text.slice(0, -1).split(`
|
|
7421
|
+
`).map(withoutLineEnding2);
|
|
7422
|
+
const starts = [];
|
|
7423
|
+
const ends = [];
|
|
7424
|
+
let start = 0;
|
|
7425
|
+
for (let row = 0;row < rows.length; row += 1) {
|
|
7426
|
+
const newline = text.indexOf(`
|
|
7427
|
+
`, start);
|
|
7428
|
+
starts.push(start);
|
|
7429
|
+
ends.push(newline < 0 ? text.length : newline + 1);
|
|
7430
|
+
start = newline < 0 ? text.length : newline + 1;
|
|
7431
|
+
}
|
|
7432
|
+
return { text, rows, starts, ends };
|
|
7433
|
+
}
|
|
7434
|
+
function boundedIndex(value, length) {
|
|
7435
|
+
if (!Number.isFinite(value))
|
|
7436
|
+
return value < 0 ? 0 : length;
|
|
7437
|
+
return Math.min(length, Math.max(0, Math.floor(value)));
|
|
7438
|
+
}
|
|
7439
|
+
function createVirtualDiffLayout(document, preamble) {
|
|
7440
|
+
const normalized = normalizePreamble(preamble);
|
|
7441
|
+
const width = lineNumberWidth2(document);
|
|
7442
|
+
const prefixes = document.lines.map((line) => linePrefix(line, width));
|
|
7443
|
+
const displayStarts = new Array(document.lines.length);
|
|
7444
|
+
let displayLength = normalized.text.length;
|
|
7445
|
+
let contentWidth = 0;
|
|
7446
|
+
for (const row of normalized.rows)
|
|
7447
|
+
contentWidth = Math.max(contentWidth, cellWidth(row));
|
|
7448
|
+
for (let index = 0;index < document.lines.length; index += 1) {
|
|
7449
|
+
const line = document.lines[index];
|
|
7450
|
+
const prefix = prefixes[index];
|
|
7451
|
+
const text = `${prefix}${withoutLineEnding2(line.raw)}`;
|
|
7452
|
+
displayStarts[index] = displayLength;
|
|
7453
|
+
displayLength += prefix.length + line.raw.length;
|
|
7454
|
+
contentWidth = Math.max(contentWidth, cellWidth(text));
|
|
7455
|
+
}
|
|
7456
|
+
const totalRows = normalized.rows.length + document.lines.length;
|
|
7457
|
+
const bodyRow = (lineIndex) => {
|
|
7458
|
+
const line = document.lines[lineIndex];
|
|
7459
|
+
const prefix = prefixes[lineIndex];
|
|
7460
|
+
return {
|
|
7461
|
+
text: `${prefix}${withoutLineEnding2(line.raw)}`,
|
|
7462
|
+
gutterCols: prefix.length,
|
|
7463
|
+
style: styleFor2(line),
|
|
7464
|
+
lineIndex,
|
|
7465
|
+
rawStartUtf16: line.startUtf16,
|
|
7466
|
+
rawEndUtf16: line.endUtf16,
|
|
7467
|
+
displayStartUtf16: displayStarts[lineIndex],
|
|
7468
|
+
displayEndUtf16: displayStarts[lineIndex] + prefix.length + line.raw.length
|
|
7469
|
+
};
|
|
7470
|
+
};
|
|
7471
|
+
const rowAt = (row) => {
|
|
7472
|
+
if (!Number.isSafeInteger(row) || row < 0 || row >= totalRows)
|
|
7473
|
+
return;
|
|
7474
|
+
if (row < normalized.rows.length) {
|
|
7475
|
+
const text = normalized.rows[row];
|
|
7476
|
+
return {
|
|
7477
|
+
text,
|
|
7478
|
+
gutterCols: 0,
|
|
7479
|
+
style: "plain",
|
|
7480
|
+
displayStartUtf16: normalized.starts[row],
|
|
7481
|
+
displayEndUtf16: normalized.ends[row]
|
|
7482
|
+
};
|
|
7483
|
+
}
|
|
7484
|
+
return bodyRow(row - normalized.rows.length);
|
|
7485
|
+
};
|
|
7486
|
+
const window = (scrollTop, viewportHeight, overscan) => {
|
|
7487
|
+
const viewport = Math.max(0, Math.floor(Number.isFinite(viewportHeight) ? viewportHeight : 0));
|
|
7488
|
+
if (totalRows === 0 || viewport === 0)
|
|
7489
|
+
return [0, -1];
|
|
7490
|
+
const margin = Math.max(0, Math.floor(Number.isFinite(overscan) ? overscan : 0));
|
|
7491
|
+
const maxScroll = Math.max(0, totalRows - viewport);
|
|
7492
|
+
const top = Math.min(maxScroll, Math.max(0, Math.floor(Number.isFinite(scrollTop) ? scrollTop : 0)));
|
|
7493
|
+
return [Math.max(0, top - margin), Math.min(totalRows - 1, top + viewport - 1 + margin)];
|
|
7494
|
+
};
|
|
7495
|
+
const displayOffsetsForLines = (startIndex, endIndex) => {
|
|
7496
|
+
let start = boundedIndex(startIndex, document.lines.length);
|
|
7497
|
+
let end = boundedIndex(endIndex, document.lines.length);
|
|
7498
|
+
if (end < start)
|
|
7499
|
+
[start, end] = [end, start];
|
|
7500
|
+
const rawStartUtf16 = start < document.lines.length ? document.lines[start].startUtf16 : document.text.length;
|
|
7501
|
+
const rawEndUtf16 = end < document.lines.length ? document.lines[end].startUtf16 : document.text.length;
|
|
7502
|
+
const displayStartUtf16 = start < document.lines.length ? displayStarts[start] : displayLength;
|
|
7503
|
+
const displayEndUtf16 = end < document.lines.length ? displayStarts[end] : displayLength;
|
|
7504
|
+
return { rawStartUtf16, rawEndUtf16, displayStartUtf16, displayEndUtf16 };
|
|
7505
|
+
};
|
|
7506
|
+
const rawOffsetAt = (row, column) => {
|
|
7507
|
+
const value = rowAt(row);
|
|
7508
|
+
if (value?.lineIndex === undefined || value.rawStartUtf16 === undefined || value.rawEndUtf16 === undefined)
|
|
7509
|
+
return;
|
|
7510
|
+
const line = document.lines[value.lineIndex];
|
|
7511
|
+
const target = Math.max(0, Math.floor(Number.isFinite(column) ? column : 0));
|
|
7512
|
+
if (target <= value.gutterCols)
|
|
7513
|
+
return line.startUtf16;
|
|
7514
|
+
const body = withoutLineEnding2(line.raw);
|
|
7515
|
+
const bodyColumn = target - value.gutterCols;
|
|
7516
|
+
let cells = 0;
|
|
7517
|
+
let utf16 = 0;
|
|
7518
|
+
for (const codePoint of body) {
|
|
7519
|
+
const widthInCells = cellWidth(codePoint);
|
|
7520
|
+
if (bodyColumn <= cells)
|
|
7521
|
+
return line.startUtf16 + utf16;
|
|
7522
|
+
utf16 += codePoint.length;
|
|
7523
|
+
cells += widthInCells;
|
|
7524
|
+
if (bodyColumn <= cells)
|
|
7525
|
+
return line.startUtf16 + utf16;
|
|
7526
|
+
}
|
|
7527
|
+
return line.endUtf16;
|
|
7528
|
+
};
|
|
7529
|
+
return { preambleRows: normalized.rows.length, totalRows, contentWidth, rowAt, window, displayOffsetsForLines, rawOffsetAt };
|
|
7530
|
+
}
|
|
7531
|
+
|
|
7532
|
+
// src/ui/panes/virtual-main-pane.ts
|
|
7533
|
+
var ACCESSORS = ["scrollY", "scrollHeight", "maxScrollY", "scrollX", "scrollWidth", "maxScrollX"];
|
|
7534
|
+
var virtualPanes = new WeakMap;
|
|
7535
|
+
var VIRTUAL_MAIN_OVERSCAN_MIN = 10;
|
|
7536
|
+
function prototypeDescriptor(text, name) {
|
|
7537
|
+
let prototype = Object.getPrototypeOf(text);
|
|
7538
|
+
while (prototype !== null) {
|
|
7539
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
7540
|
+
if (descriptor !== undefined)
|
|
7541
|
+
return descriptor;
|
|
7542
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
7543
|
+
}
|
|
7544
|
+
return;
|
|
7545
|
+
}
|
|
7546
|
+
function isFiniteNonNegative(value) {
|
|
7547
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
7548
|
+
}
|
|
7549
|
+
function documentSelection(document, startUtf16, endUtf16) {
|
|
7550
|
+
const line = document.lines.find((entry) => startUtf16 >= entry.startUtf16 && startUtf16 <= entry.endUtf16) ?? document.lines[0];
|
|
7551
|
+
return {
|
|
7552
|
+
valid: true,
|
|
7553
|
+
startUtf16,
|
|
7554
|
+
endUtf16,
|
|
7555
|
+
...line === undefined ? {} : {
|
|
7556
|
+
fileIndex: line.fileIndex,
|
|
7557
|
+
...line.hunkIndex === undefined ? {} : { hunkIndex: line.hunkIndex }
|
|
7558
|
+
},
|
|
7559
|
+
active: true
|
|
7560
|
+
};
|
|
7561
|
+
}
|
|
7562
|
+
function dimensions(text) {
|
|
7563
|
+
return {
|
|
7564
|
+
height: Math.max(1, Math.floor(isFiniteNonNegative(text.height))),
|
|
7565
|
+
width: Math.max(0, Math.floor(isFiniteNonNegative(text.width)))
|
|
7566
|
+
};
|
|
7567
|
+
}
|
|
7568
|
+
function padDisplayRow(value, width) {
|
|
7569
|
+
return `${value}${" ".repeat(Math.max(0, width - cellWidth(value)))}`;
|
|
7570
|
+
}
|
|
7571
|
+
function withoutLineEnding3(raw) {
|
|
7572
|
+
if (raw.endsWith(`\r
|
|
7573
|
+
`))
|
|
7574
|
+
return raw.slice(0, -2);
|
|
7575
|
+
if (raw.endsWith(`
|
|
7576
|
+
`) || raw.endsWith("\r"))
|
|
7577
|
+
return raw.slice(0, -1);
|
|
7578
|
+
return raw;
|
|
7579
|
+
}
|
|
7580
|
+
function rawDisplayCells(raw, relativeUtf16, includeLineEnding) {
|
|
7581
|
+
const body = withoutLineEnding3(raw);
|
|
7582
|
+
const prefixLength = Math.min(body.length, Math.max(0, Math.floor(relativeUtf16)));
|
|
7583
|
+
const cells = cellWidth(body.slice(0, prefixLength));
|
|
7584
|
+
return cells + (includeLineEnding && prefixLength >= body.length && body.length < raw.length ? 1 : 0);
|
|
7585
|
+
}
|
|
7586
|
+
function installAccessors(pane, state, rerender) {
|
|
7587
|
+
const text = pane.text;
|
|
7588
|
+
for (const name of ACCESSORS) {
|
|
7589
|
+
const descriptor = state.originalDescriptors.get(name);
|
|
7590
|
+
if (descriptor === undefined)
|
|
7591
|
+
continue;
|
|
7592
|
+
const nextDescriptor = {
|
|
7593
|
+
configurable: true,
|
|
7594
|
+
enumerable: descriptor.enumerable ?? false,
|
|
7595
|
+
get: () => {
|
|
7596
|
+
if (!state.active)
|
|
7597
|
+
return descriptor.get?.call(text);
|
|
7598
|
+
const layout = state.layout;
|
|
7599
|
+
if (name === "scrollY")
|
|
7600
|
+
return state.scrollY;
|
|
7601
|
+
if (name === "scrollX")
|
|
7602
|
+
return state.scrollX;
|
|
7603
|
+
if (name === "scrollHeight")
|
|
7604
|
+
return layout?.totalRows ?? descriptor.get?.call(text) ?? 0;
|
|
7605
|
+
if (name === "scrollWidth")
|
|
7606
|
+
return layout?.contentWidth ?? descriptor.get?.call(text) ?? 0;
|
|
7607
|
+
const viewport = name === "maxScrollY" ? state.viewportHeight : state.viewportWidth;
|
|
7608
|
+
const size = name === "maxScrollY" ? layout?.totalRows ?? 0 : layout?.contentWidth ?? 0;
|
|
7609
|
+
return Math.max(0, size - viewport);
|
|
7610
|
+
},
|
|
7611
|
+
...name === "scrollY" || name === "scrollX" ? {
|
|
7612
|
+
set: (value) => {
|
|
7613
|
+
if (!state.active) {
|
|
7614
|
+
descriptor.set?.call(text, value);
|
|
7615
|
+
return;
|
|
7616
|
+
}
|
|
7617
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
7618
|
+
if (name === "scrollY") {
|
|
7619
|
+
const max = Math.max(0, (state.layout?.totalRows ?? 0) - state.viewportHeight);
|
|
7620
|
+
const next = Math.min(max, Math.floor(isFiniteNonNegative(numeric)));
|
|
7621
|
+
if (state.scrollY === next)
|
|
7622
|
+
return;
|
|
7623
|
+
state.scrollY = next;
|
|
7624
|
+
rerender();
|
|
7625
|
+
} else {
|
|
7626
|
+
const max = Math.max(0, (state.layout?.contentWidth ?? 0) - state.viewportWidth);
|
|
7627
|
+
const next = Math.min(max, Math.floor(isFiniteNonNegative(numeric)));
|
|
7628
|
+
state.scrollX = next;
|
|
7629
|
+
descriptor.set?.call(text, next);
|
|
7630
|
+
pane.text.requestRender();
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
} : {}
|
|
7634
|
+
};
|
|
7635
|
+
Object.defineProperty(text, name, nextDescriptor);
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
function createAdapter(pane) {
|
|
7639
|
+
const text = pane.text;
|
|
7640
|
+
const originalOwnDescriptors = new Map(ACCESSORS.map((name) => [name, Object.getOwnPropertyDescriptor(text, name)]));
|
|
7641
|
+
const originalDescriptors = new Map(ACCESSORS.map((name) => [name, Object.getOwnPropertyDescriptor(text, name) ?? prototypeDescriptor(text, name)]));
|
|
7642
|
+
const state = {
|
|
7643
|
+
active: false,
|
|
7644
|
+
document: undefined,
|
|
7645
|
+
layout: undefined,
|
|
7646
|
+
preamble: "",
|
|
7647
|
+
scrollY: 0,
|
|
7648
|
+
scrollX: 0,
|
|
7649
|
+
viewportHeight: Math.max(1, Math.floor(text.height)),
|
|
7650
|
+
viewportWidth: Math.max(0, Math.floor(text.width)),
|
|
7651
|
+
rawSelection: undefined,
|
|
7652
|
+
renderedWindow: undefined,
|
|
7653
|
+
preambleSpans: new Map,
|
|
7654
|
+
originalDescriptors,
|
|
7655
|
+
originalOwnDescriptors
|
|
7656
|
+
};
|
|
7657
|
+
const restoreAccessors = () => {
|
|
7658
|
+
const target = text;
|
|
7659
|
+
for (const name of ACCESSORS) {
|
|
7660
|
+
delete target[name];
|
|
7661
|
+
const own = state.originalOwnDescriptors.get(name);
|
|
7662
|
+
if (own !== undefined)
|
|
7663
|
+
Object.defineProperty(target, name, own);
|
|
7664
|
+
}
|
|
7665
|
+
};
|
|
7666
|
+
const visibleSelection = () => {
|
|
7667
|
+
const selection = state.rawSelection;
|
|
7668
|
+
const layout = state.layout;
|
|
7669
|
+
const window = state.renderedWindow;
|
|
7670
|
+
if (selection === undefined || layout === undefined || window === undefined)
|
|
7671
|
+
return;
|
|
7672
|
+
let start;
|
|
7673
|
+
let end;
|
|
7674
|
+
let localOffset = 0;
|
|
7675
|
+
for (let row = window[0];row <= window[1]; row += 1) {
|
|
7676
|
+
const current = layout.rowAt(row);
|
|
7677
|
+
if (current === undefined)
|
|
7678
|
+
continue;
|
|
7679
|
+
const rowStart = localOffset;
|
|
7680
|
+
localOffset += layout.contentWidth + (row < window[1] ? 1 : 0);
|
|
7681
|
+
if (current.lineIndex === undefined || current.rawStartUtf16 === undefined || current.rawEndUtf16 === undefined)
|
|
7682
|
+
continue;
|
|
7683
|
+
const line = state.document?.lines[current.lineIndex];
|
|
7684
|
+
if (line === undefined)
|
|
7685
|
+
continue;
|
|
7686
|
+
const overlapStart = Math.max(selection.startUtf16, current.rawStartUtf16);
|
|
7687
|
+
const overlapEnd = Math.min(selection.endUtf16, current.rawEndUtf16);
|
|
7688
|
+
if (overlapStart >= overlapEnd)
|
|
7689
|
+
continue;
|
|
7690
|
+
const displayStart = rowStart + (overlapStart === current.rawStartUtf16 ? 0 : current.gutterCols + rawDisplayCells(line.raw, overlapStart - current.rawStartUtf16, false));
|
|
7691
|
+
const displayEnd = rowStart + current.gutterCols + rawDisplayCells(line.raw, overlapEnd - current.rawStartUtf16, overlapEnd > current.rawStartUtf16 + withoutLineEnding3(line.raw).length);
|
|
7692
|
+
start = start === undefined ? displayStart : Math.min(start, displayStart);
|
|
7693
|
+
end = end === undefined ? displayEnd : Math.max(end, displayEnd);
|
|
7694
|
+
}
|
|
7695
|
+
return start === undefined || end === undefined ? undefined : { start, end };
|
|
7696
|
+
};
|
|
7697
|
+
const paintSelection = () => {
|
|
7698
|
+
const selected = visibleSelection();
|
|
7699
|
+
const surface = text;
|
|
7700
|
+
if (selected === undefined)
|
|
7701
|
+
surface.resetSelection?.();
|
|
7702
|
+
else
|
|
7703
|
+
surface.setSelection?.(selected.start, selected.end);
|
|
7704
|
+
};
|
|
7705
|
+
const renderWindow = () => {
|
|
7706
|
+
if (!state.active || state.layout === undefined)
|
|
7707
|
+
return;
|
|
7708
|
+
const current = dimensions(text);
|
|
7709
|
+
state.viewportHeight = current.height;
|
|
7710
|
+
state.viewportWidth = current.width;
|
|
7711
|
+
const max = Math.max(0, state.layout.totalRows - state.viewportHeight);
|
|
7712
|
+
if (state.scrollY > max)
|
|
7713
|
+
state.scrollY = max;
|
|
7714
|
+
const maxX = Math.max(0, state.layout.contentWidth - state.viewportWidth);
|
|
7715
|
+
if (state.scrollX > maxX)
|
|
7716
|
+
state.scrollX = maxX;
|
|
7717
|
+
const overscan = Math.max(VIRTUAL_MAIN_OVERSCAN_MIN, state.viewportHeight);
|
|
7718
|
+
const window = state.layout.window(state.scrollY, state.viewportHeight, overscan);
|
|
7719
|
+
const localScrollY = state.scrollY - window[0];
|
|
7720
|
+
state.renderedWindow = window;
|
|
7721
|
+
const rows = [];
|
|
7722
|
+
const displays = [];
|
|
7723
|
+
const preambleRows = [];
|
|
7724
|
+
const preambleSpans = new Map;
|
|
7725
|
+
const first = window[0];
|
|
7726
|
+
const last = window[1];
|
|
7727
|
+
if (last >= first && first < state.layout.preambleRows) {
|
|
7728
|
+
const preambleLast = Math.min(last, state.layout.preambleRows - 1);
|
|
7729
|
+
for (let row = first;row <= preambleLast; row += 1) {
|
|
7730
|
+
const value = state.layout.rowAt(row);
|
|
7731
|
+
if (value !== undefined)
|
|
7732
|
+
preambleRows.push(padDisplayRow(value.text, state.layout.contentWidth));
|
|
7733
|
+
const spans = state.preambleSpans.get(row);
|
|
7734
|
+
if (spans !== undefined)
|
|
7735
|
+
preambleSpans.set(row - first, spans);
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
const bodyFirst = Math.max(first, state.layout.preambleRows);
|
|
7739
|
+
for (let row = bodyFirst;row <= last; row += 1) {
|
|
7740
|
+
const value = state.layout.rowAt(row);
|
|
7741
|
+
if (value === undefined || value.lineIndex === undefined)
|
|
7742
|
+
continue;
|
|
7743
|
+
rows.push(padDisplayRow(value.text, state.layout.contentWidth));
|
|
7744
|
+
displays.push({ gutterCols: value.gutterCols, style: value.style });
|
|
7745
|
+
}
|
|
7746
|
+
const preamble = preambleRows.length === 0 ? "" : `${preambleRows.join(`
|
|
7747
|
+
`)}
|
|
7748
|
+
`;
|
|
7749
|
+
installDiffText(text, { preamble, body: rows.join(`
|
|
7750
|
+
`), displayLines: displays, highlightScrollY: () => localScrollY, preambleSpans });
|
|
7751
|
+
const originalScrollY = state.originalDescriptors.get("scrollY")?.set;
|
|
7752
|
+
originalScrollY?.call(text, localScrollY);
|
|
7753
|
+
const originalScrollX = state.originalDescriptors.get("scrollX")?.set;
|
|
7754
|
+
originalScrollX?.call(text, state.scrollX);
|
|
7755
|
+
paintSelection();
|
|
7756
|
+
pane.syncScrollbar(state.viewportHeight);
|
|
7757
|
+
text.requestRender?.();
|
|
7758
|
+
};
|
|
7759
|
+
const rerender = () => renderWindow();
|
|
7760
|
+
const adapter = {
|
|
7761
|
+
install(document, preamble) {
|
|
7762
|
+
if (!state.active) {
|
|
7763
|
+
state.scrollY = isFiniteNonNegative(Number(text.scrollY));
|
|
7764
|
+
state.scrollX = isFiniteNonNegative(Number(text.scrollX));
|
|
7765
|
+
}
|
|
7766
|
+
state.active = true;
|
|
7767
|
+
state.document = document;
|
|
7768
|
+
state.layout = createVirtualDiffLayout(document, preamble);
|
|
7769
|
+
state.preamble = preamble;
|
|
7770
|
+
state.preambleSpans = statSpansForPreamble(preamble);
|
|
7771
|
+
const current = dimensions(text);
|
|
7772
|
+
state.viewportHeight = current.height;
|
|
7773
|
+
state.viewportWidth = current.width;
|
|
7774
|
+
installAccessors(pane, state, rerender);
|
|
7775
|
+
text.wrapMode = "none";
|
|
7776
|
+
renderWindow();
|
|
7777
|
+
},
|
|
7778
|
+
deactivate() {
|
|
7779
|
+
if (!state.active)
|
|
7780
|
+
return;
|
|
7781
|
+
const originalY = state.originalDescriptors.get("scrollY")?.set;
|
|
7782
|
+
const originalX = state.originalDescriptors.get("scrollX")?.set;
|
|
7783
|
+
originalY?.call(text, state.scrollY);
|
|
7784
|
+
originalX?.call(text, state.scrollX);
|
|
7785
|
+
state.active = false;
|
|
7786
|
+
state.document = undefined;
|
|
7787
|
+
state.layout = undefined;
|
|
7788
|
+
state.rawSelection = undefined;
|
|
7789
|
+
state.renderedWindow = undefined;
|
|
7790
|
+
releaseDiffText(text);
|
|
7791
|
+
state.preambleSpans = new Map;
|
|
7792
|
+
restoreAccessors();
|
|
7793
|
+
clearScrollbarViewportOverride(text);
|
|
7794
|
+
text.wrapMode = "char";
|
|
7795
|
+
},
|
|
7796
|
+
isActive: () => state.active,
|
|
7797
|
+
layout: () => state.layout,
|
|
7798
|
+
lineOffsets: (startIndex, endIndex) => state.layout?.displayOffsetsForLines(startIndex, endIndex),
|
|
7799
|
+
visualRowRange: (startIndex, endIndex) => {
|
|
7800
|
+
if (state.layout === undefined)
|
|
7801
|
+
return;
|
|
7802
|
+
const start = state.layout.preambleRows + Math.max(0, startIndex);
|
|
7803
|
+
const end = state.layout.preambleRows + Math.max(startIndex, endIndex);
|
|
7804
|
+
return { startRow: start, endRow: end };
|
|
7805
|
+
},
|
|
7806
|
+
setLineSelection(startUtf16, endUtf16) {
|
|
7807
|
+
if (!state.active || state.document === undefined)
|
|
7808
|
+
return;
|
|
7809
|
+
state.rawSelection = documentSelection(state.document, startUtf16, endUtf16);
|
|
7810
|
+
paintSelection();
|
|
7811
|
+
},
|
|
7812
|
+
setPointerSelection(startRow, startColumn, endRow, endColumn) {
|
|
7813
|
+
if (!state.active || state.layout === undefined || state.document === undefined)
|
|
7814
|
+
return;
|
|
7815
|
+
const start = state.layout.rawOffsetAt(Math.max(0, Math.floor(state.scrollY + startRow)), Math.max(0, Math.floor(state.scrollX + startColumn)));
|
|
7816
|
+
const end = state.layout.rawOffsetAt(Math.max(0, Math.floor(state.scrollY + endRow)), Math.max(0, Math.floor(state.scrollX + endColumn)));
|
|
7817
|
+
if (start === undefined || end === undefined) {
|
|
7818
|
+
state.rawSelection = undefined;
|
|
7819
|
+
paintSelection();
|
|
7820
|
+
return;
|
|
7821
|
+
}
|
|
7822
|
+
const selection = documentSelection(state.document, Math.min(start, end), Math.max(start, end));
|
|
7823
|
+
state.rawSelection = selection;
|
|
7824
|
+
paintSelection();
|
|
7825
|
+
return selection;
|
|
7826
|
+
},
|
|
7827
|
+
selection: () => state.rawSelection,
|
|
7828
|
+
resetSelection() {
|
|
7829
|
+
state.rawSelection = undefined;
|
|
7830
|
+
text.resetSelection?.();
|
|
7831
|
+
},
|
|
7832
|
+
clampScroll() {
|
|
7833
|
+
if (!state.active || state.layout === undefined) {
|
|
7834
|
+
const originalY = state.originalDescriptors.get("scrollY")?.set;
|
|
7835
|
+
const originalX = state.originalDescriptors.get("scrollX")?.set;
|
|
7836
|
+
originalY?.call(text, text.scrollY);
|
|
7837
|
+
originalX?.call(text, text.scrollX);
|
|
7838
|
+
return;
|
|
7839
|
+
}
|
|
7840
|
+
const maxY = Math.max(0, state.layout.totalRows - state.viewportHeight);
|
|
7841
|
+
const maxX = Math.max(0, state.layout.contentWidth - state.viewportWidth);
|
|
7842
|
+
state.scrollY = Math.min(maxY, Math.max(0, state.scrollY));
|
|
7843
|
+
state.scrollX = Math.min(maxX, Math.max(0, state.scrollX));
|
|
7844
|
+
renderWindow();
|
|
7845
|
+
}
|
|
7846
|
+
};
|
|
7847
|
+
onPaneLifecyclePass(text, () => {
|
|
7848
|
+
if (!state.active || state.layout === undefined)
|
|
7849
|
+
return;
|
|
7850
|
+
const current = dimensions(text);
|
|
7851
|
+
if (current.height !== state.viewportHeight || current.width !== state.viewportWidth)
|
|
7852
|
+
renderWindow();
|
|
7853
|
+
});
|
|
7854
|
+
return adapter;
|
|
7855
|
+
}
|
|
7856
|
+
function createVirtualMainPane(pane) {
|
|
7857
|
+
const existing = virtualPanes.get(pane);
|
|
7858
|
+
if (existing !== undefined)
|
|
7859
|
+
return existing;
|
|
7860
|
+
const adapter = createAdapter(pane);
|
|
7861
|
+
virtualPanes.set(pane, adapter);
|
|
7862
|
+
return adapter;
|
|
7863
|
+
}
|
|
7864
|
+
function virtualMainPaneFor(pane) {
|
|
7865
|
+
return virtualPanes.get(pane);
|
|
7866
|
+
}
|
|
7867
|
+
function isVirtualDiffDocument(document) {
|
|
7868
|
+
return document.lines.length > VIRTUAL_DIFF_LINE_THRESHOLD;
|
|
7040
7869
|
}
|
|
7041
7870
|
|
|
7042
7871
|
// src/ui/panes/main-pane.ts
|
|
@@ -7071,6 +7900,7 @@ function createMainPane(renderer, _model) {
|
|
|
7071
7900
|
const pane = createPane(renderer, "main", "0 Main", "", true);
|
|
7072
7901
|
pane.text.selectionBg = SELECTED_LINE_BG;
|
|
7073
7902
|
ensureMainTextSelectionSurface(pane);
|
|
7903
|
+
createVirtualMainPane(pane);
|
|
7074
7904
|
pane.box.title = "0 Main";
|
|
7075
7905
|
paneTitles.set(pane, "0 Main");
|
|
7076
7906
|
return pane;
|
|
@@ -7119,6 +7949,9 @@ function mainDiffVisualRowRange(pane, startIndex, endIndex) {
|
|
|
7119
7949
|
const document = documents.get(pane);
|
|
7120
7950
|
if (document === undefined)
|
|
7121
7951
|
return;
|
|
7952
|
+
const virtual = virtualMainPaneFor(pane);
|
|
7953
|
+
if (virtual?.isActive())
|
|
7954
|
+
return virtual.visualRowRange(startIndex, endIndex);
|
|
7122
7955
|
const content = installedContents.get(pane);
|
|
7123
7956
|
const preambleRows = (normalizedPreamble(content?.preamble ?? "").match(/\n/g) ?? []).length;
|
|
7124
7957
|
const firstSource = preambleRows + Math.max(0, startIndex);
|
|
@@ -7143,7 +7976,16 @@ function getMainDiffLineSelection(pane) {
|
|
|
7143
7976
|
if (document === undefined || state === undefined || state.rangeMode === "none")
|
|
7144
7977
|
return;
|
|
7145
7978
|
const range = diffLineSelectionRange(state);
|
|
7146
|
-
const
|
|
7979
|
+
const virtual = virtualMainPaneFor(pane);
|
|
7980
|
+
const offsets = virtual?.isActive() ? (() => {
|
|
7981
|
+
const value = virtual.lineOffsets(range.startIndex, range.endIndex + 1);
|
|
7982
|
+
return value === undefined ? undefined : {
|
|
7983
|
+
startUtf16: value.rawStartUtf16,
|
|
7984
|
+
endUtf16: value.rawEndUtf16,
|
|
7985
|
+
displayStartUtf16: value.displayStartUtf16,
|
|
7986
|
+
displayEndUtf16: value.displayEndUtf16
|
|
7987
|
+
};
|
|
7988
|
+
})() : mainDiffLineOffsets(document, range.startIndex, range.endIndex, installedContents.get(pane)?.preamble ?? "");
|
|
7147
7989
|
if (offsets === undefined)
|
|
7148
7990
|
return;
|
|
7149
7991
|
return {
|
|
@@ -7154,16 +7996,42 @@ function getMainDiffLineSelection(pane) {
|
|
|
7154
7996
|
};
|
|
7155
7997
|
}
|
|
7156
7998
|
function applyMainDiffLineVisualSelection(pane, resetWhenInactive = true) {
|
|
7157
|
-
const
|
|
7999
|
+
const virtual = virtualMainPaneFor(pane);
|
|
7158
8000
|
const selection = getMainDiffLineSelection(pane);
|
|
7159
|
-
if (
|
|
8001
|
+
if (virtual?.isActive()) {
|
|
8002
|
+
if (selection !== undefined)
|
|
8003
|
+
virtual.setLineSelection(selection.startUtf16, selection.endUtf16);
|
|
8004
|
+
else if (resetWhenInactive)
|
|
8005
|
+
virtual.resetSelection();
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
8008
|
+
const text = pane.text;
|
|
8009
|
+
if (selection !== undefined)
|
|
7160
8010
|
text.setSelection?.(selection.displayStartUtf16, selection.displayEndUtf16);
|
|
7161
|
-
|
|
8011
|
+
else if (resetWhenInactive)
|
|
7162
8012
|
text.resetSelection?.();
|
|
7163
|
-
|
|
8013
|
+
}
|
|
8014
|
+
function getMainPointerSelection(pane) {
|
|
8015
|
+
return virtualMainPaneFor(pane)?.selection();
|
|
7164
8016
|
}
|
|
7165
8017
|
function setMainDiffLineRangeState(pane, state) {
|
|
7166
8018
|
lineRanges.set(pane, state);
|
|
8019
|
+
const virtual = virtualMainPaneFor(pane);
|
|
8020
|
+
if (state.rangeMode === "none") {
|
|
8021
|
+
if (virtual?.isActive())
|
|
8022
|
+
virtual.resetSelection();
|
|
8023
|
+
else
|
|
8024
|
+
applyMainDiffLineVisualSelection(pane);
|
|
8025
|
+
return;
|
|
8026
|
+
}
|
|
8027
|
+
const document = documents.get(pane);
|
|
8028
|
+
const range = diffLineSelectionRange(state);
|
|
8029
|
+
const start = document?.lines[range.startIndex];
|
|
8030
|
+
const end = document?.lines[range.endIndex];
|
|
8031
|
+
if (virtual?.isActive() && start !== undefined && end !== undefined) {
|
|
8032
|
+
virtual.setLineSelection(start.startUtf16, end.endUtf16);
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
7167
8035
|
applyMainDiffLineVisualSelection(pane);
|
|
7168
8036
|
}
|
|
7169
8037
|
function getMainDocument(pane) {
|
|
@@ -7243,11 +8111,17 @@ function buildPlainContent(content) {
|
|
|
7243
8111
|
return "No content";
|
|
7244
8112
|
}
|
|
7245
8113
|
function clampMainScroll(pane) {
|
|
8114
|
+
const virtual = virtualMainPaneFor(pane);
|
|
8115
|
+
if (virtual?.isActive()) {
|
|
8116
|
+
virtual.clampScroll();
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
7246
8119
|
pane.text.scrollY = Math.max(0, Math.min(pane.text.maxScrollY, pane.text.scrollY));
|
|
7247
8120
|
pane.text.scrollX = Math.max(0, Math.min(pane.text.maxScrollX, pane.text.scrollX));
|
|
7248
8121
|
pane.syncScrollbar();
|
|
7249
8122
|
}
|
|
7250
8123
|
function updatePlain(pane, value) {
|
|
8124
|
+
virtualMainPaneFor(pane)?.deactivate();
|
|
7251
8125
|
releaseDiffText(pane.text);
|
|
7252
8126
|
releaseAnsiText(pane.text);
|
|
7253
8127
|
pane.update(value);
|
|
@@ -7255,20 +8129,25 @@ function updatePlain(pane, value) {
|
|
|
7255
8129
|
function installMainContent(pane, content, tooSmall) {
|
|
7256
8130
|
const previousContent = installedContents.get(pane);
|
|
7257
8131
|
const previousText = renderedTexts.get(pane);
|
|
7258
|
-
const
|
|
8132
|
+
const virtualDocument = content.document !== undefined && isVirtualDiffDocument(content.document);
|
|
8133
|
+
const nextText = virtualDocument ? undefined : renderedTextFor(content);
|
|
7259
8134
|
const previousIdentity = previousContent === undefined ? undefined : `${previousContent.source}:${previousContent.stableId}`;
|
|
7260
8135
|
const nextIdentity = `${content.source}:${content.stableId}`;
|
|
7261
8136
|
const sameIdentity = previousIdentity !== undefined && previousIdentity === nextIdentity;
|
|
7262
|
-
const identicalText = previousText !== undefined && previousText === nextText;
|
|
8137
|
+
const identicalText = virtualDocument ? previousContent?.document?.text === content.document?.text && previousContent?.preamble === content.preamble : previousText !== undefined && previousText === nextText;
|
|
7263
8138
|
const previousWasDocument = documents.has(pane);
|
|
7264
8139
|
const replacingDocument = previousWasDocument && content.document === undefined;
|
|
7265
8140
|
const enteringDocument = !previousWasDocument && content.document !== undefined;
|
|
7266
8141
|
const leavingAnsi = previousContent?.ansi !== undefined && content.ansi === undefined;
|
|
7267
8142
|
installedContents.set(pane, content);
|
|
7268
|
-
|
|
8143
|
+
if (nextText === undefined)
|
|
8144
|
+
renderedTexts.delete(pane);
|
|
8145
|
+
else
|
|
8146
|
+
renderedTexts.set(pane, nextText);
|
|
7269
8147
|
paneTitles.set(pane, `0 Main — ${content.label}`);
|
|
7270
8148
|
const previousRange = lineRanges.get(pane);
|
|
7271
8149
|
const clearSelection = () => {
|
|
8150
|
+
virtualMainPaneFor(pane)?.resetSelection();
|
|
7272
8151
|
const view = pane.text;
|
|
7273
8152
|
if (view !== null && typeof view === "object" && "resetSelection" in view) {
|
|
7274
8153
|
const reset = view.resetSelection;
|
|
@@ -7339,6 +8218,16 @@ function installMainContent(pane, content, tooSmall) {
|
|
|
7339
8218
|
} else {
|
|
7340
8219
|
cursorTargets.delete(pane);
|
|
7341
8220
|
}
|
|
8221
|
+
const virtual = virtualMainPaneFor(pane);
|
|
8222
|
+
if (virtualDocument && virtual !== undefined) {
|
|
8223
|
+
releaseAnsiText(pane.text);
|
|
8224
|
+
virtual.install(doc, content.preamble ?? "");
|
|
8225
|
+
renderedTexts.delete(pane);
|
|
8226
|
+
if (previousRange?.rangeMode !== "none")
|
|
8227
|
+
applyMainDiffLineVisualSelection(pane);
|
|
8228
|
+
return;
|
|
8229
|
+
}
|
|
8230
|
+
virtual?.deactivate();
|
|
7342
8231
|
pane.text.wrapMode = "char";
|
|
7343
8232
|
releaseAnsiText(pane.text);
|
|
7344
8233
|
if (enteringDocument || !sameIdentity || !identicalText) {
|
|
@@ -7353,6 +8242,7 @@ function installMainContent(pane, content, tooSmall) {
|
|
|
7353
8242
|
return;
|
|
7354
8243
|
}
|
|
7355
8244
|
if (content.ansi !== undefined) {
|
|
8245
|
+
virtualMainPaneFor(pane)?.deactivate();
|
|
7356
8246
|
documents.delete(pane);
|
|
7357
8247
|
if (replacingDocument || !sameIdentity || !identicalText)
|
|
7358
8248
|
clearSelection();
|
|
@@ -7364,6 +8254,7 @@ function installMainContent(pane, content, tooSmall) {
|
|
|
7364
8254
|
pane.syncScrollbar();
|
|
7365
8255
|
return;
|
|
7366
8256
|
}
|
|
8257
|
+
virtualMainPaneFor(pane)?.deactivate();
|
|
7367
8258
|
documents.delete(pane);
|
|
7368
8259
|
if (replacingDocument || leavingAnsi || !sameIdentity || !identicalText)
|
|
7369
8260
|
clearSelection();
|
|
@@ -7415,8 +8306,7 @@ function createStashPane(renderer, model) {
|
|
|
7415
8306
|
const rows = stashRows(model);
|
|
7416
8307
|
const displayRows = stashDisplayRows(model, rows);
|
|
7417
8308
|
const state = createListState(rows, displayRows);
|
|
7418
|
-
|
|
7419
|
-
pane.update(content);
|
|
8309
|
+
installListText(pane.text, { state, width: 80, focused: false });
|
|
7420
8310
|
return pane;
|
|
7421
8311
|
}
|
|
7422
8312
|
function selectedStashEntryFromState(state, model) {
|
|
@@ -9336,6 +10226,7 @@ class RootView {
|
|
|
9336
10226
|
lastSplitterPress;
|
|
9337
10227
|
activeSplitterDrag;
|
|
9338
10228
|
gestureOwner;
|
|
10229
|
+
mainPointerAnchor;
|
|
9339
10230
|
pendingClick;
|
|
9340
10231
|
hoveredListRow;
|
|
9341
10232
|
model;
|
|
@@ -9399,6 +10290,7 @@ class RootView {
|
|
|
9399
10290
|
onInspectStash;
|
|
9400
10291
|
onBrowseRemote;
|
|
9401
10292
|
loadBranchCommits;
|
|
10293
|
+
onExpandCommits;
|
|
9402
10294
|
onInspectBranch;
|
|
9403
10295
|
onCheckoutRemoteTracking;
|
|
9404
10296
|
onFilterBranches;
|
|
@@ -9492,6 +10384,7 @@ class RootView {
|
|
|
9492
10384
|
this.loadTagInspection = options.loadTagInspection;
|
|
9493
10385
|
this.loadRefLogInspection = options.loadRefLogInspection;
|
|
9494
10386
|
this.loadBranchCommits = options.loadBranchCommits;
|
|
10387
|
+
this.onExpandCommits = options.onExpandCommits;
|
|
9495
10388
|
this.onPreviewError = options.onPreviewError;
|
|
9496
10389
|
this.onCommitMessage = options.onCommitMessage;
|
|
9497
10390
|
this.onAmendMessage = options.onAmendMessage;
|
|
@@ -9624,6 +10517,10 @@ class RootView {
|
|
|
9624
10517
|
this.syncPreviewForFocus(focus);
|
|
9625
10518
|
};
|
|
9626
10519
|
this.handleResize = () => {
|
|
10520
|
+
if (this.gestureOwner?.kind === "main-selection") {
|
|
10521
|
+
virtualMainPaneFor(this.panes.main)?.resetSelection();
|
|
10522
|
+
this.cancelGesture();
|
|
10523
|
+
}
|
|
9627
10524
|
this.recomputeLayout();
|
|
9628
10525
|
};
|
|
9629
10526
|
this.handleKey = (key) => {
|
|
@@ -9672,9 +10569,13 @@ class RootView {
|
|
|
9672
10569
|
this.applyLayout();
|
|
9673
10570
|
}
|
|
9674
10571
|
update(model, options = {}) {
|
|
10572
|
+
if (this.gestureOwner?.kind === "main-selection") {
|
|
10573
|
+
virtualMainPaneFor(this.panes.main)?.resetSelection();
|
|
10574
|
+
this.cancelGesture();
|
|
10575
|
+
}
|
|
9675
10576
|
this.branchActionGeneration += 1;
|
|
9676
10577
|
this.invalidateBranchCommitsRequest();
|
|
9677
|
-
if (!options.preserveRemoteCheckout) {
|
|
10578
|
+
if (!options.preserveRemoteCheckout && !options.preserveFilterInput) {
|
|
9678
10579
|
this.branchFilterActive = false;
|
|
9679
10580
|
this.branchFilter = "";
|
|
9680
10581
|
this.filterInput.clear();
|
|
@@ -9902,7 +10803,11 @@ class RootView {
|
|
|
9902
10803
|
};
|
|
9903
10804
|
}
|
|
9904
10805
|
cancelGesture() {
|
|
10806
|
+
if (this.gestureOwner?.kind === "main-selection") {
|
|
10807
|
+
virtualMainPaneFor(this.panes.main)?.resetSelection();
|
|
10808
|
+
}
|
|
9905
10809
|
this.gestureOwner = undefined;
|
|
10810
|
+
this.mainPointerAnchor = undefined;
|
|
9906
10811
|
this.activeSplitterDrag = undefined;
|
|
9907
10812
|
}
|
|
9908
10813
|
get activeBranchesTab() {
|
|
@@ -10201,14 +11106,15 @@ class RootView {
|
|
|
10201
11106
|
const focused = this.focusManager.active === "branches";
|
|
10202
11107
|
const activeView = this.activeListView("branches");
|
|
10203
11108
|
if (activeView === undefined) {
|
|
11109
|
+
releaseListText(pane.text);
|
|
10204
11110
|
pane.update("");
|
|
10205
11111
|
return;
|
|
10206
11112
|
}
|
|
10207
11113
|
const { state } = activeView;
|
|
10208
11114
|
const win = this.geometry.windows.branches;
|
|
10209
11115
|
const width = sidePaneListWidth(win, state);
|
|
10210
|
-
const
|
|
10211
|
-
pane.
|
|
11116
|
+
const hoveredId = this.hoveredIdFor(activeView);
|
|
11117
|
+
installListText(pane.text, { state, width, focused, ...hoveredId === undefined ? {} : { hoveredId } });
|
|
10212
11118
|
pane.syncScrollbar(sidePaneViewportHeight(win));
|
|
10213
11119
|
}
|
|
10214
11120
|
get filesTitleStyled() {
|
|
@@ -10268,13 +11174,15 @@ class RootView {
|
|
|
10268
11174
|
pane.setTabs?.({ tabs: tabsInput.tabs, activeIndex: tabsInput.activeIndex, focused: tabsInput.focused });
|
|
10269
11175
|
const activeView = this.activeListView("files");
|
|
10270
11176
|
if (activeView === undefined) {
|
|
11177
|
+
releaseListText(pane.text);
|
|
10271
11178
|
pane.update("");
|
|
10272
11179
|
return;
|
|
10273
11180
|
}
|
|
10274
11181
|
const { state } = activeView;
|
|
10275
11182
|
const win = this.geometry.windows.files;
|
|
10276
11183
|
const width = sidePaneListWidth(win, state);
|
|
10277
|
-
|
|
11184
|
+
const hoveredId = this.hoveredIdFor(activeView);
|
|
11185
|
+
installListText(pane.text, { state, width, focused: tabsInput.focused, ...hoveredId === undefined ? {} : { hoveredId } });
|
|
10278
11186
|
pane.syncScrollbar(sidePaneViewportHeight(win));
|
|
10279
11187
|
}
|
|
10280
11188
|
refreshStashState(model) {
|
|
@@ -10288,6 +11196,7 @@ class RootView {
|
|
|
10288
11196
|
const pane = this.panes.stash;
|
|
10289
11197
|
const activeView = this.activeListView("stash");
|
|
10290
11198
|
if (activeView === undefined) {
|
|
11199
|
+
releaseListText(pane.text);
|
|
10291
11200
|
pane.update("");
|
|
10292
11201
|
return;
|
|
10293
11202
|
}
|
|
@@ -10295,8 +11204,8 @@ class RootView {
|
|
|
10295
11204
|
const focused = this.focusManager.active === "stash";
|
|
10296
11205
|
const win = this.geometry.windows.stash;
|
|
10297
11206
|
const width = sidePaneListWidth(win, state);
|
|
10298
|
-
const
|
|
10299
|
-
pane.
|
|
11207
|
+
const hoveredId = this.hoveredIdFor(activeView);
|
|
11208
|
+
installListText(pane.text, { state, width, focused, ...hoveredId === undefined ? {} : { hoveredId } });
|
|
10300
11209
|
pane.syncScrollbar(sidePaneViewportHeight(win));
|
|
10301
11210
|
}
|
|
10302
11211
|
renderSidePanes() {
|
|
@@ -10857,6 +11766,7 @@ class RootView {
|
|
|
10857
11766
|
this.renderCommitsPane();
|
|
10858
11767
|
this.revealListRow("commits", this.panes.commits, nextView.selectedIndex);
|
|
10859
11768
|
this.syncPreviewForFocus("commits");
|
|
11769
|
+
this.maybeExpandCommits(nextView.selectedIndex);
|
|
10860
11770
|
}
|
|
10861
11771
|
}
|
|
10862
11772
|
return;
|
|
@@ -10945,8 +11855,8 @@ class RootView {
|
|
|
10945
11855
|
}
|
|
10946
11856
|
focusedPageStep() {
|
|
10947
11857
|
const focus = this.focusManager.active;
|
|
10948
|
-
const
|
|
10949
|
-
return Math.max(1, heightOf(
|
|
11858
|
+
const dimensions2 = focus === "command-log" ? this.geometry.windows.log : this.geometry.windows[focus] ?? this.geometry.windows.main;
|
|
11859
|
+
return Math.max(1, heightOf(dimensions2) - 2);
|
|
10950
11860
|
}
|
|
10951
11861
|
actionPage(direction) {
|
|
10952
11862
|
if (this.focusManager.active === "main") {
|
|
@@ -10979,11 +11889,63 @@ class RootView {
|
|
|
10979
11889
|
}
|
|
10980
11890
|
return;
|
|
10981
11891
|
}
|
|
10982
|
-
|
|
10983
|
-
const
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
11892
|
+
this.clearTransientMenus();
|
|
11893
|
+
const paneId = this.focusManager.active;
|
|
11894
|
+
if (paneId !== "files" && paneId !== "branches" && paneId !== "commits" && paneId !== "stash")
|
|
11895
|
+
return;
|
|
11896
|
+
if (paneId === "commits" && edge === "bottom") {
|
|
11897
|
+
this.jumpCommitsToBottom();
|
|
11898
|
+
return;
|
|
11899
|
+
}
|
|
11900
|
+
const active = this.activeListView(paneId);
|
|
11901
|
+
if (active === undefined || active.state.rows.length === 0)
|
|
11902
|
+
return;
|
|
11903
|
+
const rows = active.state.rows;
|
|
11904
|
+
const targetId = (edge === "bottom" ? rows[rows.length - 1] : rows[0]).id;
|
|
11905
|
+
const next = this.edgeSelection(active.state, targetId);
|
|
11906
|
+
if (next === active.state)
|
|
11907
|
+
return;
|
|
11908
|
+
this.updateActiveListState(paneId, next);
|
|
11909
|
+
this.renderListPane(paneId);
|
|
11910
|
+
this.revealListRow(paneId, this.panes[paneId], next.selectedIndex);
|
|
11911
|
+
this.syncListSelectionAfterChange(paneId);
|
|
11912
|
+
this.root.requestRender();
|
|
11913
|
+
}
|
|
11914
|
+
edgeSelection(state, targetId) {
|
|
11915
|
+
const direct = selectListRow(state, targetId);
|
|
11916
|
+
if (direct === state)
|
|
11917
|
+
return state;
|
|
11918
|
+
if (state.rangeMode !== "sticky" || state.rangeStartId === undefined)
|
|
11919
|
+
return direct;
|
|
11920
|
+
if (!state.rows.some((row) => row.id === state.rangeStartId))
|
|
11921
|
+
return direct;
|
|
11922
|
+
return { ...direct, rangeMode: state.rangeMode, rangeStartId: state.rangeStartId };
|
|
11923
|
+
}
|
|
11924
|
+
jumpCommitsToBottom() {
|
|
11925
|
+
(async () => {
|
|
11926
|
+
await this.onExpandCommits?.();
|
|
11927
|
+
const active = this.activeListView("commits");
|
|
11928
|
+
if (active === undefined || active.state.rows.length === 0)
|
|
11929
|
+
return;
|
|
11930
|
+
const rows = active.state.rows;
|
|
11931
|
+
const next = this.edgeSelection(active.state, rows[rows.length - 1].id);
|
|
11932
|
+
if (next === active.state)
|
|
11933
|
+
return;
|
|
11934
|
+
this.updateActiveListState("commits", next);
|
|
11935
|
+
this.renderListPane("commits");
|
|
11936
|
+
this.revealListRow("commits", this.panes.commits, next.selectedIndex);
|
|
11937
|
+
this.syncListSelectionAfterChange("commits");
|
|
11938
|
+
this.root.requestRender();
|
|
11939
|
+
})();
|
|
11940
|
+
}
|
|
11941
|
+
maybeExpandCommits(selectedIndex) {
|
|
11942
|
+
if (this.commitsPanel.activeTab !== "commits")
|
|
11943
|
+
return;
|
|
11944
|
+
if (this.commitsPanel.child !== undefined)
|
|
11945
|
+
return;
|
|
11946
|
+
if (selectedIndex <= COMMIT_THRESHOLD)
|
|
11947
|
+
return;
|
|
11948
|
+
this.onExpandCommits?.();
|
|
10987
11949
|
}
|
|
10988
11950
|
actionInspect() {
|
|
10989
11951
|
switch (this.focusManager.active) {
|
|
@@ -12333,6 +13295,9 @@ class RootView {
|
|
|
12333
13295
|
const existing = this.getFilterForKey(target.key);
|
|
12334
13296
|
if (existing.length > 0)
|
|
12335
13297
|
this.filters.delete(target.key);
|
|
13298
|
+
if (target.key === this.filterKey("commits", "commits")) {
|
|
13299
|
+
this.onExpandCommits?.();
|
|
13300
|
+
}
|
|
12336
13301
|
this.filterInput.open("");
|
|
12337
13302
|
this.refreshForFilterKey(target.key);
|
|
12338
13303
|
this.renderForFilterKey(target.key);
|
|
@@ -12352,7 +13317,7 @@ class RootView {
|
|
|
12352
13317
|
let next = -1;
|
|
12353
13318
|
for (let i = current + 1;i < view.rows.length; i++) {
|
|
12354
13319
|
const row = view.rows[i];
|
|
12355
|
-
const text = `${row.columns[0]?.text ?? ""} ${row.columns[
|
|
13320
|
+
const text = `${row.columns[0]?.text ?? ""} ${row.columns[2]?.text ?? ""}`.toLowerCase();
|
|
12356
13321
|
if (text.includes(normalizedQuery)) {
|
|
12357
13322
|
next = i;
|
|
12358
13323
|
break;
|
|
@@ -12361,7 +13326,7 @@ class RootView {
|
|
|
12361
13326
|
if (next === -1) {
|
|
12362
13327
|
for (let i = 0;i <= current; i++) {
|
|
12363
13328
|
const row = view.rows[i];
|
|
12364
|
-
const text = `${row.columns[0]?.text ?? ""} ${row.columns[
|
|
13329
|
+
const text = `${row.columns[0]?.text ?? ""} ${row.columns[2]?.text ?? ""}`.toLowerCase();
|
|
12365
13330
|
if (text.includes(normalizedQuery)) {
|
|
12366
13331
|
next = i;
|
|
12367
13332
|
break;
|
|
@@ -12387,11 +13352,44 @@ class RootView {
|
|
|
12387
13352
|
const query = this.getFilterForKey(this.filterKey("main"));
|
|
12388
13353
|
if (query.length === 0)
|
|
12389
13354
|
return;
|
|
12390
|
-
const
|
|
13355
|
+
const document = getMainDocument(this.panes.main);
|
|
13356
|
+
const virtual = virtualMainPaneFor(this.panes.main);
|
|
13357
|
+
const normalizedQuery = query.toLowerCase();
|
|
13358
|
+
if (document !== undefined && virtual?.isActive()) {
|
|
13359
|
+
const layout = virtual.layout();
|
|
13360
|
+
const preambleRows = layout?.preambleRows ?? 0;
|
|
13361
|
+
const totalRows = layout?.totalRows ?? preambleRows + document.lines.length;
|
|
13362
|
+
const currentLine2 = this.panes.main.text.scrollY - preambleRows;
|
|
13363
|
+
let nextLine2 = -1;
|
|
13364
|
+
const firstCandidate = Math.max(0, currentLine2 + 1);
|
|
13365
|
+
for (let i = firstCandidate;i < document.lines.length; i += 1) {
|
|
13366
|
+
if (document.lines[i].raw.toLowerCase().includes(normalizedQuery)) {
|
|
13367
|
+
nextLine2 = i;
|
|
13368
|
+
break;
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13371
|
+
if (nextLine2 === -1) {
|
|
13372
|
+
for (let i = 0;i <= currentLine2 && i < document.lines.length; i += 1) {
|
|
13373
|
+
if (document.lines[i].raw.toLowerCase().includes(normalizedQuery)) {
|
|
13374
|
+
nextLine2 = i;
|
|
13375
|
+
break;
|
|
13376
|
+
}
|
|
13377
|
+
}
|
|
13378
|
+
}
|
|
13379
|
+
if (nextLine2 !== -1) {
|
|
13380
|
+
this.clearNonStickyMainRange();
|
|
13381
|
+
const targetRow = preambleRows + nextLine2;
|
|
13382
|
+
this.panes.main.text.scrollY = Math.max(0, targetRow - 2);
|
|
13383
|
+
this.panes.main.syncScrollbar();
|
|
13384
|
+
this.panes.main.box.bottomTitle = `Search: ${query} (${targetRow + 1}/${totalRows})`;
|
|
13385
|
+
this.root.requestRender();
|
|
13386
|
+
}
|
|
13387
|
+
return;
|
|
13388
|
+
}
|
|
13389
|
+
const text = getMainRenderedText(this.panes.main) ?? document?.text ?? "";
|
|
12391
13390
|
if (text.length === 0)
|
|
12392
13391
|
return;
|
|
12393
13392
|
const normalizedText = text.toLowerCase();
|
|
12394
|
-
const normalizedQuery = query.toLowerCase();
|
|
12395
13393
|
const currentY = this.panes.main.text.scrollY;
|
|
12396
13394
|
const lines = text.split(`
|
|
12397
13395
|
`);
|
|
@@ -12415,6 +13413,7 @@ class RootView {
|
|
|
12415
13413
|
this.clearNonStickyMainRange();
|
|
12416
13414
|
const targetY = Math.max(0, nextLine - 2);
|
|
12417
13415
|
this.panes.main.text.scrollY = targetY;
|
|
13416
|
+
this.panes.main.syncScrollbar();
|
|
12418
13417
|
this.panes.main.box.bottomTitle = `Search: ${query} (${nextLine + 1}/${lines.length})`;
|
|
12419
13418
|
this.root.requestRender();
|
|
12420
13419
|
}
|
|
@@ -12435,7 +13434,7 @@ class RootView {
|
|
|
12435
13434
|
let prev = -1;
|
|
12436
13435
|
for (let i = current - 1;i >= 0; i--) {
|
|
12437
13436
|
const row = view.rows[i];
|
|
12438
|
-
const text = `${row.columns[0]?.text ?? ""} ${row.columns[
|
|
13437
|
+
const text = `${row.columns[0]?.text ?? ""} ${row.columns[2]?.text ?? ""}`.toLowerCase();
|
|
12439
13438
|
if (text.includes(normalizedQuery)) {
|
|
12440
13439
|
prev = i;
|
|
12441
13440
|
break;
|
|
@@ -12444,7 +13443,7 @@ class RootView {
|
|
|
12444
13443
|
if (prev === -1) {
|
|
12445
13444
|
for (let i = view.rows.length - 1;i >= current; i--) {
|
|
12446
13445
|
const row = view.rows[i];
|
|
12447
|
-
const text = `${row.columns[0]?.text ?? ""} ${row.columns[
|
|
13446
|
+
const text = `${row.columns[0]?.text ?? ""} ${row.columns[2]?.text ?? ""}`.toLowerCase();
|
|
12448
13447
|
if (text.includes(normalizedQuery)) {
|
|
12449
13448
|
prev = i;
|
|
12450
13449
|
break;
|
|
@@ -12470,13 +13469,45 @@ class RootView {
|
|
|
12470
13469
|
const query = this.getFilterForKey(this.filterKey("main"));
|
|
12471
13470
|
if (query.length === 0)
|
|
12472
13471
|
return;
|
|
12473
|
-
const
|
|
13472
|
+
const document = getMainDocument(this.panes.main);
|
|
13473
|
+
const virtual = virtualMainPaneFor(this.panes.main);
|
|
13474
|
+
const normalizedQuery = query.toLowerCase();
|
|
13475
|
+
if (document !== undefined && virtual?.isActive()) {
|
|
13476
|
+
const layout = virtual.layout();
|
|
13477
|
+
const preambleRows = layout?.preambleRows ?? 0;
|
|
13478
|
+
const totalRows = layout?.totalRows ?? preambleRows + document.lines.length;
|
|
13479
|
+
const currentLine = this.panes.main.text.scrollY - preambleRows;
|
|
13480
|
+
let previousLine = -1;
|
|
13481
|
+
for (let i = Math.min(document.lines.length - 1, currentLine - 1);i >= 0; i -= 1) {
|
|
13482
|
+
if (document.lines[i].raw.toLowerCase().includes(normalizedQuery)) {
|
|
13483
|
+
previousLine = i;
|
|
13484
|
+
break;
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
if (previousLine === -1) {
|
|
13488
|
+
for (let i = document.lines.length - 1;i >= currentLine && i >= 0; i -= 1) {
|
|
13489
|
+
if (document.lines[i].raw.toLowerCase().includes(normalizedQuery)) {
|
|
13490
|
+
previousLine = i;
|
|
13491
|
+
break;
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13494
|
+
}
|
|
13495
|
+
if (previousLine !== -1) {
|
|
13496
|
+
this.clearNonStickyMainRange();
|
|
13497
|
+
const targetRow = preambleRows + previousLine;
|
|
13498
|
+
this.panes.main.text.scrollY = Math.max(0, targetRow - 2);
|
|
13499
|
+
this.panes.main.syncScrollbar();
|
|
13500
|
+
this.panes.main.box.bottomTitle = `Search: ${query} (${targetRow + 1}/${totalRows})`;
|
|
13501
|
+
this.root.requestRender();
|
|
13502
|
+
}
|
|
13503
|
+
return;
|
|
13504
|
+
}
|
|
13505
|
+
const text = getMainRenderedText(this.panes.main) ?? document?.text ?? "";
|
|
12474
13506
|
if (text.length === 0)
|
|
12475
13507
|
return;
|
|
12476
13508
|
const lines = text.split(`
|
|
12477
13509
|
`);
|
|
12478
13510
|
const currentY = this.panes.main.text.scrollY;
|
|
12479
|
-
const normalizedQuery = query.toLowerCase();
|
|
12480
13511
|
let prevLine = -1;
|
|
12481
13512
|
for (let i = currentY - 1;i >= 0; i--) {
|
|
12482
13513
|
if (lines[i].toLowerCase().includes(normalizedQuery)) {
|
|
@@ -12496,6 +13527,7 @@ class RootView {
|
|
|
12496
13527
|
this.clearNonStickyMainRange();
|
|
12497
13528
|
const targetY = Math.max(0, prevLine - 2);
|
|
12498
13529
|
this.panes.main.text.scrollY = targetY;
|
|
13530
|
+
this.panes.main.syncScrollbar();
|
|
12499
13531
|
this.panes.main.box.bottomTitle = `Search: ${query} (${prevLine + 1}/${lines.length})`;
|
|
12500
13532
|
this.root.requestRender();
|
|
12501
13533
|
}
|
|
@@ -12530,6 +13562,13 @@ class RootView {
|
|
|
12530
13562
|
return [...paths];
|
|
12531
13563
|
}
|
|
12532
13564
|
mainActionTarget(document) {
|
|
13565
|
+
const pointer = getMainPointerSelection(this.panes.main);
|
|
13566
|
+
if (pointer?.fileIndex !== undefined && pointer.valid && pointer.endUtf16 > pointer.startUtf16) {
|
|
13567
|
+
return {
|
|
13568
|
+
fileIndex: pointer.fileIndex,
|
|
13569
|
+
...pointer.hunkIndex === undefined ? {} : { hunkIndex: pointer.hunkIndex }
|
|
13570
|
+
};
|
|
13571
|
+
}
|
|
12533
13572
|
const selected = getMainDiffLineSelection(this.panes.main);
|
|
12534
13573
|
const firstIndex = selected?.indexes[0];
|
|
12535
13574
|
const line = firstIndex === undefined ? undefined : document.lines[firstIndex];
|
|
@@ -12541,10 +13580,43 @@ class RootView {
|
|
|
12541
13580
|
}
|
|
12542
13581
|
return getMainCursorTarget(this.panes.main);
|
|
12543
13582
|
}
|
|
13583
|
+
mainPointerCoordinates(event) {
|
|
13584
|
+
const geometry = this.paneTextGeometry("main");
|
|
13585
|
+
if (geometry === undefined)
|
|
13586
|
+
return;
|
|
13587
|
+
const row = event.y - geometry.screenY;
|
|
13588
|
+
const column = event.x - geometry.screenX;
|
|
13589
|
+
if (!Number.isSafeInteger(row) || !Number.isSafeInteger(column) || row < 0 || row >= geometry.height || column < 0 || column >= geometry.width)
|
|
13590
|
+
return;
|
|
13591
|
+
return { row, column };
|
|
13592
|
+
}
|
|
13593
|
+
updateVirtualMainPointer(event) {
|
|
13594
|
+
const virtual = virtualMainPaneFor(this.panes.main);
|
|
13595
|
+
if (!virtual?.isActive())
|
|
13596
|
+
return;
|
|
13597
|
+
const anchor = this.mainPointerAnchor;
|
|
13598
|
+
if (anchor === undefined) {
|
|
13599
|
+
virtual.resetSelection();
|
|
13600
|
+
return;
|
|
13601
|
+
}
|
|
13602
|
+
const point = this.mainPointerCoordinates(event);
|
|
13603
|
+
if (point === undefined) {
|
|
13604
|
+
virtual.resetSelection();
|
|
13605
|
+
return;
|
|
13606
|
+
}
|
|
13607
|
+
virtual.setPointerSelection(anchor.row, anchor.column, point.row, point.column);
|
|
13608
|
+
}
|
|
12544
13609
|
mainChangeSelection() {
|
|
12545
13610
|
const document = getMainDocument(this.panes.main);
|
|
12546
13611
|
if (!document)
|
|
12547
13612
|
return;
|
|
13613
|
+
const pointerSelection = getMainPointerSelection(this.panes.main);
|
|
13614
|
+
if (pointerSelection?.valid && pointerSelection.endUtf16 > pointerSelection.startUtf16) {
|
|
13615
|
+
return {
|
|
13616
|
+
document,
|
|
13617
|
+
indexes: changeLineIndexes(document, pointerSelection.startUtf16, pointerSelection.endUtf16)
|
|
13618
|
+
};
|
|
13619
|
+
}
|
|
12548
13620
|
const keyboardSelection = getMainDiffLineSelection(this.panes.main);
|
|
12549
13621
|
if (keyboardSelection !== undefined) {
|
|
12550
13622
|
return {
|
|
@@ -12939,7 +14011,7 @@ class RootView {
|
|
|
12939
14011
|
let commitsState = setListRows(panel.views.commits, rows, displayRows);
|
|
12940
14012
|
if (commitsSearch.length > 0 && rows.length > 0) {
|
|
12941
14013
|
const normalized = commitsSearch.toLowerCase();
|
|
12942
|
-
const matchIndex = rows.findIndex((row) => (row.columns[
|
|
14014
|
+
const matchIndex = rows.findIndex((row) => (row.columns[2]?.text ?? "").toLowerCase().includes(normalized) || (row.columns[0]?.text ?? "").toLowerCase().includes(normalized));
|
|
12943
14015
|
if (matchIndex >= 0) {
|
|
12944
14016
|
const matchId = rows[matchIndex].id;
|
|
12945
14017
|
commitsState = selectListRow(commitsState, matchId);
|
|
@@ -12970,14 +14042,15 @@ class RootView {
|
|
|
12970
14042
|
}
|
|
12971
14043
|
const activeView = this.activeListView("commits");
|
|
12972
14044
|
if (activeView === undefined) {
|
|
14045
|
+
releaseListText(pane.text);
|
|
12973
14046
|
pane.update("");
|
|
12974
14047
|
return;
|
|
12975
14048
|
}
|
|
12976
14049
|
const { state } = activeView;
|
|
12977
14050
|
const width = sidePaneListWidth(this.geometry.windows.commits, state);
|
|
12978
14051
|
const focused = this.focusManager.active === "commits";
|
|
12979
|
-
const
|
|
12980
|
-
pane.
|
|
14052
|
+
const hoveredId = this.hoveredIdFor(activeView);
|
|
14053
|
+
installListText(pane.text, { state, width, focused, ...hoveredId === undefined ? {} : { hoveredId } });
|
|
12981
14054
|
pane.syncScrollbar(sidePaneViewportHeight(this.geometry.windows.commits));
|
|
12982
14055
|
}
|
|
12983
14056
|
installInitialMainContent(model) {
|
|
@@ -13209,16 +14282,19 @@ class RootView {
|
|
|
13209
14282
|
this.root.requestRender();
|
|
13210
14283
|
return;
|
|
13211
14284
|
}
|
|
14285
|
+
const rawPointerSelection = getMainPointerSelection(pane);
|
|
14286
|
+
const pointerSelection = rawPointerSelection !== undefined && rawPointerSelection.valid && rawPointerSelection.endUtf16 > rawPointerSelection.startUtf16 ? rawPointerSelection : undefined;
|
|
13212
14287
|
const keyboardSelection = getMainDiffLineSelection(pane);
|
|
13213
14288
|
const nativeRange = pane.text.getSelection();
|
|
13214
|
-
let selection = mode === "hunk" || mode === "file"
|
|
14289
|
+
let selection = mode === "hunk" || mode === "file" ? undefined : pointerSelection ?? (keyboardSelection === undefined ? undefined : {
|
|
13215
14290
|
valid: true,
|
|
13216
14291
|
startUtf16: keyboardSelection.startUtf16,
|
|
13217
14292
|
endUtf16: keyboardSelection.endUtf16,
|
|
13218
14293
|
active: true
|
|
13219
|
-
};
|
|
13220
|
-
if (selection === undefined && keyboardSelection === undefined && nativeRange)
|
|
14294
|
+
});
|
|
14295
|
+
if (selection === undefined && pointerSelection === undefined && keyboardSelection === undefined && nativeRange) {
|
|
13221
14296
|
selection = selectionFromRenderable(document, nativeRange, pane.text.getSelectedText());
|
|
14297
|
+
}
|
|
13222
14298
|
if (!selection && (mode === "hunk" || mode === "file")) {
|
|
13223
14299
|
const target = getMainCursorTarget(pane);
|
|
13224
14300
|
if (target) {
|
|
@@ -13538,6 +14614,9 @@ class RootView {
|
|
|
13538
14614
|
bar.onMouseDown = undefined;
|
|
13539
14615
|
bar.onMouseDrag = undefined;
|
|
13540
14616
|
bar.onMouseUp = undefined;
|
|
14617
|
+
bar.slider.onMouseDown = undefined;
|
|
14618
|
+
bar.slider.onMouseDrag = undefined;
|
|
14619
|
+
bar.slider.onMouseUp = undefined;
|
|
13541
14620
|
}
|
|
13542
14621
|
}
|
|
13543
14622
|
this.renderer.off("resize", this.handleResize);
|
|
@@ -13564,20 +14643,33 @@ class RootView {
|
|
|
13564
14643
|
const bar = paneScrollbar(typedPane.text);
|
|
13565
14644
|
if (!bar)
|
|
13566
14645
|
continue;
|
|
13567
|
-
|
|
14646
|
+
const beginScrollbarGesture = (event) => {
|
|
14647
|
+
this.updateHoveredListRow(event.x, event.y);
|
|
13568
14648
|
this.pendingClick = undefined;
|
|
13569
14649
|
this.lastSplitterPress = undefined;
|
|
13570
14650
|
this.gestureOwner = { kind: "scrollbar", paneId: typedPane.id };
|
|
14651
|
+
this.scrollPaneByScrollbarPosition(typedPane.id, event.y);
|
|
13571
14652
|
event.stopPropagation();
|
|
13572
14653
|
};
|
|
13573
|
-
|
|
13574
|
-
if (this.gestureOwner?.kind === "scrollbar" && this.gestureOwner.paneId === typedPane.id)
|
|
14654
|
+
const continueScrollbarGesture = (event) => {
|
|
14655
|
+
if (this.gestureOwner?.kind === "scrollbar" && this.gestureOwner.paneId === typedPane.id) {
|
|
14656
|
+
this.scrollPaneByScrollbarPosition(typedPane.id, event.y);
|
|
13575
14657
|
event.stopPropagation();
|
|
14658
|
+
}
|
|
13576
14659
|
};
|
|
13577
|
-
|
|
13578
|
-
if (this.gestureOwner?.kind === "scrollbar" && this.gestureOwner.paneId === typedPane.id)
|
|
14660
|
+
const endScrollbarGesture = (event) => {
|
|
14661
|
+
if (this.gestureOwner?.kind === "scrollbar" && this.gestureOwner.paneId === typedPane.id) {
|
|
14662
|
+
this.scrollPaneByScrollbarPosition(typedPane.id, event.y);
|
|
14663
|
+
this.gestureOwner = undefined;
|
|
13579
14664
|
event.stopPropagation();
|
|
14665
|
+
}
|
|
13580
14666
|
};
|
|
14667
|
+
bar.onMouseDown = beginScrollbarGesture;
|
|
14668
|
+
bar.onMouseDrag = continueScrollbarGesture;
|
|
14669
|
+
bar.onMouseUp = endScrollbarGesture;
|
|
14670
|
+
bar.slider.onMouseDown = beginScrollbarGesture;
|
|
14671
|
+
bar.slider.onMouseDrag = continueScrollbarGesture;
|
|
14672
|
+
bar.slider.onMouseUp = endScrollbarGesture;
|
|
13581
14673
|
}
|
|
13582
14674
|
this.root.onMouse = (event) => {
|
|
13583
14675
|
if (this.isBranchReviewActive?.())
|
|
@@ -13686,14 +14778,22 @@ class RootView {
|
|
|
13686
14778
|
return;
|
|
13687
14779
|
}
|
|
13688
14780
|
if (owner.kind === "main-selection") {
|
|
14781
|
+
const virtual = virtualMainPaneFor(this.panes.main);
|
|
13689
14782
|
if (event.type === "drag") {
|
|
13690
14783
|
this.pendingClick = undefined;
|
|
13691
14784
|
this.lastSplitterPress = undefined;
|
|
14785
|
+
if (virtual?.isActive())
|
|
14786
|
+
this.updateVirtualMainPointer(event);
|
|
14787
|
+
event.preventDefault();
|
|
13692
14788
|
event.stopPropagation();
|
|
13693
14789
|
return;
|
|
13694
14790
|
}
|
|
13695
|
-
if (event.type === "up") {
|
|
14791
|
+
if (event.type === "up" || event.type === "cancel") {
|
|
14792
|
+
if (virtual?.isActive() && event.type === "up")
|
|
14793
|
+
this.updateVirtualMainPointer(event);
|
|
13696
14794
|
this.gestureOwner = undefined;
|
|
14795
|
+
this.mainPointerAnchor = undefined;
|
|
14796
|
+
event.preventDefault();
|
|
13697
14797
|
event.stopPropagation();
|
|
13698
14798
|
return;
|
|
13699
14799
|
}
|
|
@@ -13824,10 +14924,22 @@ class RootView {
|
|
|
13824
14924
|
if (paneId === "main") {
|
|
13825
14925
|
this.pendingClick = undefined;
|
|
13826
14926
|
this.lastSplitterPress = undefined;
|
|
13827
|
-
this.gestureOwner = { kind: "main-selection" };
|
|
13828
14927
|
if (this.focusManager.active !== "main")
|
|
13829
14928
|
this.focusManager.focus("main");
|
|
14929
|
+
const virtual = virtualMainPaneFor(this.panes.main);
|
|
14930
|
+
const canSelect = event.button === 0 && !event.modifiers.ctrl;
|
|
14931
|
+
const point = virtual?.isActive() && canSelect ? this.mainPointerCoordinates(event) : undefined;
|
|
14932
|
+
this.mainPointerAnchor = point;
|
|
14933
|
+
if (virtual?.isActive()) {
|
|
14934
|
+
if (point === undefined)
|
|
14935
|
+
virtual.resetSelection();
|
|
14936
|
+
else
|
|
14937
|
+
virtual.setPointerSelection(point.row, point.column, point.row, point.column);
|
|
14938
|
+
}
|
|
14939
|
+
this.gestureOwner = { kind: "main-selection" };
|
|
13830
14940
|
this.clearTransientMenus();
|
|
14941
|
+
if (virtual?.isActive() && canSelect)
|
|
14942
|
+
event.preventDefault();
|
|
13831
14943
|
event.stopPropagation();
|
|
13832
14944
|
return;
|
|
13833
14945
|
}
|
|
@@ -14096,16 +15208,16 @@ class RootView {
|
|
|
14096
15208
|
this.syncPaneBorders();
|
|
14097
15209
|
const windows = this.geometry.windows;
|
|
14098
15210
|
const place = (renderable, name) => {
|
|
14099
|
-
const
|
|
14100
|
-
if (
|
|
15211
|
+
const dimensions2 = windows[name];
|
|
15212
|
+
if (dimensions2 === undefined) {
|
|
14101
15213
|
renderable.visible = false;
|
|
14102
15214
|
return;
|
|
14103
15215
|
}
|
|
14104
|
-
renderable.left =
|
|
14105
|
-
renderable.top =
|
|
14106
|
-
renderable.width = Math.max(1, widthOf(
|
|
14107
|
-
renderable.height = Math.max(1, heightOf(
|
|
14108
|
-
renderable.visible = widthOf(
|
|
15216
|
+
renderable.left = dimensions2.x0;
|
|
15217
|
+
renderable.top = dimensions2.y0;
|
|
15218
|
+
renderable.width = Math.max(1, widthOf(dimensions2));
|
|
15219
|
+
renderable.height = Math.max(1, heightOf(dimensions2));
|
|
15220
|
+
renderable.visible = widthOf(dimensions2) > 0 && heightOf(dimensions2) > 0;
|
|
14109
15221
|
};
|
|
14110
15222
|
for (const name of SIDE_WINDOWS)
|
|
14111
15223
|
place(this.panes[name].box, name);
|
|
@@ -14306,7 +15418,7 @@ async function loadRefsSnapshot(runner) {
|
|
|
14306
15418
|
}
|
|
14307
15419
|
|
|
14308
15420
|
// src/git/editor.ts
|
|
14309
|
-
import { join as
|
|
15421
|
+
import { join as join5, basename } from "node:path";
|
|
14310
15422
|
function standardTerminalPreset(editor) {
|
|
14311
15423
|
return {
|
|
14312
15424
|
edit: `${editor} -- {{filename}}`,
|
|
@@ -14444,7 +15556,7 @@ async function resolveEditCommand(files, options = {}) {
|
|
|
14444
15556
|
function absolutePath(repoRoot, relativePath) {
|
|
14445
15557
|
if (relativePath.startsWith("/"))
|
|
14446
15558
|
return relativePath;
|
|
14447
|
-
return
|
|
15559
|
+
return join5(repoRoot, relativePath);
|
|
14448
15560
|
}
|
|
14449
15561
|
|
|
14450
15562
|
// src/app/create-app.ts
|
|
@@ -14452,7 +15564,7 @@ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
|
14452
15564
|
|
|
14453
15565
|
// src/app/index-watcher.ts
|
|
14454
15566
|
import { watch, statSync } from "node:fs";
|
|
14455
|
-
import { basename as basename2, dirname as
|
|
15567
|
+
import { basename as basename2, dirname as dirname4 } from "node:path";
|
|
14456
15568
|
var DEFAULT_INDEX_EVENT_DEBOUNCE_MS = 50;
|
|
14457
15569
|
var BUSY_RETRY_MS = 50;
|
|
14458
15570
|
function fingerprint(path) {
|
|
@@ -14493,7 +15605,7 @@ class IndexWatcher {
|
|
|
14493
15605
|
this.stopped = false;
|
|
14494
15606
|
this.baseline = fingerprint(this.options.indexPath);
|
|
14495
15607
|
try {
|
|
14496
|
-
const watcher = watch(
|
|
15608
|
+
const watcher = watch(dirname4(this.options.indexPath), { persistent: false }, (_eventType, filename) => {
|
|
14497
15609
|
if (this.stopped)
|
|
14498
15610
|
return;
|
|
14499
15611
|
const name = eventFileName(filename);
|
|
@@ -14705,7 +15817,7 @@ function isWorkerAvailable() {
|
|
|
14705
15817
|
}
|
|
14706
15818
|
|
|
14707
15819
|
// src/ui/review-workspace/ReviewWorkspaceApp.tsx
|
|
14708
|
-
import { StyledText as StyledText8, parseColor as
|
|
15820
|
+
import { StyledText as StyledText8, parseColor as parseColor3 } from "@opentui/core";
|
|
14709
15821
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
14710
15822
|
import { useCallback, useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef2, useState as useState3, useSyncExternalStore } from "react";
|
|
14711
15823
|
|
|
@@ -15159,7 +16271,7 @@ function reviewHelp(focus, state) {
|
|
|
15159
16271
|
}
|
|
15160
16272
|
|
|
15161
16273
|
// src/ui/review-workspace/review-sidebar.ts
|
|
15162
|
-
import { basename as basename3, dirname as
|
|
16274
|
+
import { basename as basename3, dirname as dirname5 } from "node:path/posix";
|
|
15163
16275
|
function normalizeDiffPath(p) {
|
|
15164
16276
|
return p?.replace(/[\r\n]+$/u, "");
|
|
15165
16277
|
}
|
|
@@ -15245,7 +16357,7 @@ function buildReviewSidebarEntries(state) {
|
|
|
15245
16357
|
let activeGroup;
|
|
15246
16358
|
visible.forEach((file, index) => {
|
|
15247
16359
|
const path = formatTerminalPath(normalizeDiffPath(file.path) ?? file.path);
|
|
15248
|
-
const group =
|
|
16360
|
+
const group = dirname5(path);
|
|
15249
16361
|
if (group !== activeGroup) {
|
|
15250
16362
|
activeGroup = group;
|
|
15251
16363
|
entries.push({
|
|
@@ -15731,7 +16843,7 @@ function buildHunkStackRows(file, state, highlight, options) {
|
|
|
15731
16843
|
return rows;
|
|
15732
16844
|
}
|
|
15733
16845
|
// src/ui/review-workspace/components/ReviewDiffRow.tsx
|
|
15734
|
-
import { StyledText as StyledText7, parseColor } from "@opentui/core";
|
|
16846
|
+
import { StyledText as StyledText7, parseColor as parseColor2 } from "@opentui/core";
|
|
15735
16847
|
|
|
15736
16848
|
// src/ui/review-workspace/hunk-code-columns.ts
|
|
15737
16849
|
var HUNK_DIFF_RAIL_WIDTH = 1;
|
|
@@ -15804,7 +16916,7 @@ function color(value) {
|
|
|
15804
16916
|
const cached = colorCache2.get(value);
|
|
15805
16917
|
if (cached)
|
|
15806
16918
|
return cached;
|
|
15807
|
-
const parsed =
|
|
16919
|
+
const parsed = parseColor2(value);
|
|
15808
16920
|
colorCache2.set(value, parsed);
|
|
15809
16921
|
return parsed;
|
|
15810
16922
|
}
|
|
@@ -17916,7 +19028,7 @@ var colorCache3 = new Map;
|
|
|
17916
19028
|
function textChunk(text, style) {
|
|
17917
19029
|
let fg5 = colorCache3.get(COLORS2[style]);
|
|
17918
19030
|
if (!fg5) {
|
|
17919
|
-
fg5 =
|
|
19031
|
+
fg5 = parseColor3(COLORS2[style]);
|
|
17920
19032
|
colorCache3.set(COLORS2[style], fg5);
|
|
17921
19033
|
}
|
|
17922
19034
|
return { __isChunk: true, text, fg: fg5 };
|
|
@@ -18154,14 +19266,14 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
18154
19266
|
const finishSummaryRef = useRef2(null);
|
|
18155
19267
|
const finishSubmitRef = useRef2(false);
|
|
18156
19268
|
const expandedSourceRef = useRef2(new Map);
|
|
18157
|
-
const
|
|
18158
|
-
const maxSidebarWidth = Math.max(REVIEW_SIDEBAR_MIN_WIDTH,
|
|
18159
|
-
const sidebarWidth =
|
|
18160
|
-
const diffWidth = Math.max(1,
|
|
19269
|
+
const dimensions2 = { width: Math.max(1, terminal.width), height: Math.max(1, terminal.height) };
|
|
19270
|
+
const maxSidebarWidth = Math.max(REVIEW_SIDEBAR_MIN_WIDTH, dimensions2.width - REVIEW_RESIZE_BAR_WIDTH - REVIEW_DIFF_BORDER_WIDTH - REVIEW_DIFF_MIN_CONTENT_WIDTH);
|
|
19271
|
+
const sidebarWidth = dimensions2.width >= REVIEW_SIDEBAR_VISIBILITY_WIDTH ? Math.min(Math.max(sidebarWidthPreference, REVIEW_SIDEBAR_MIN_WIDTH), maxSidebarWidth) : 0;
|
|
19272
|
+
const diffWidth = Math.max(1, dimensions2.width - sidebarWidth - (sidebarWidth > 0 ? REVIEW_RESIZE_BAR_WIDTH : 0) - REVIEW_DIFF_BORDER_WIDTH);
|
|
18161
19273
|
const layout = layoutMode === "auto" ? diffWidth >= 64 ? "split" : "stack" : layoutMode;
|
|
18162
19274
|
const composerHeight = state?.draft ? canShowReplacementDraft(state) ? 9 : 6 : 0;
|
|
18163
|
-
const diffHeight = Math.max(1,
|
|
18164
|
-
const resizeBarHeight = Math.max(1,
|
|
19275
|
+
const diffHeight = Math.max(1, dimensions2.height - 4 - composerHeight - 2);
|
|
19276
|
+
const resizeBarHeight = Math.max(1, dimensions2.height - 4 - composerHeight);
|
|
18165
19277
|
const sidebarFocused = focus === "sidebar" || focus === "filter";
|
|
18166
19278
|
const diffFocused = focus === "stream";
|
|
18167
19279
|
const files = useMemo4(() => state ? toHunkReviewFiles(visibleReviewFiles(state)) : [], [state?.document, state?.feedback, state?.filter, state?.viewed]);
|
|
@@ -18953,7 +20065,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
18953
20065
|
id: "react-review-header",
|
|
18954
20066
|
style: { width: "100%", height: 3, flexShrink: 0 },
|
|
18955
20067
|
children: /* @__PURE__ */ jsx4("text", {
|
|
18956
|
-
content: headerText(state,
|
|
20068
|
+
content: headerText(state, dimensions2.width, controller.error),
|
|
18957
20069
|
wrapMode: "none",
|
|
18958
20070
|
truncate: true
|
|
18959
20071
|
})
|
|
@@ -19180,7 +20292,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19180
20292
|
}),
|
|
19181
20293
|
orphanedFeedback.length > 0 ? /* @__PURE__ */ jsx4("box", {
|
|
19182
20294
|
id: "review-orphaned-feedback",
|
|
19183
|
-
style: { position: "absolute", left: 1, bottom: 1, width: Math.max(20,
|
|
20295
|
+
style: { position: "absolute", left: 1, bottom: 1, width: Math.max(20, dimensions2.width - 2), height: Math.min(4, orphanedFeedback.length), zIndex: 50, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
19184
20296
|
children: orphanedFeedback.slice(0, 4).map((feedback) => /* @__PURE__ */ jsxs4("box", {
|
|
19185
20297
|
style: { width: "100%", height: 1, flexDirection: "row" },
|
|
19186
20298
|
onMouseUp: () => {
|
|
@@ -19225,7 +20337,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19225
20337
|
}) : null,
|
|
19226
20338
|
feedbackMessage ? /* @__PURE__ */ jsx4("box", {
|
|
19227
20339
|
id: "review-feedback-message",
|
|
19228
|
-
style: { position: "absolute", left: 1, bottom: orphanedFeedback.length > 0 ? Math.min(5, orphanedFeedback.length + 1) : 1, width: Math.max(20,
|
|
20340
|
+
style: { position: "absolute", left: 1, bottom: orphanedFeedback.length > 0 ? Math.min(5, orphanedFeedback.length + 1) : 1, width: Math.max(20, dimensions2.width - 2), height: 1, zIndex: 55, backgroundColor: "#202020" },
|
|
19229
20341
|
children: /* @__PURE__ */ jsx4("text", {
|
|
19230
20342
|
content: feedbackMessage,
|
|
19231
20343
|
wrapMode: "none",
|
|
@@ -19445,7 +20557,7 @@ function ReviewWorkspaceApp({ session }) {
|
|
|
19445
20557
|
}) : null,
|
|
19446
20558
|
helpOpen ? /* @__PURE__ */ jsx4("box", {
|
|
19447
20559
|
id: "review-help-dialog",
|
|
19448
|
-
style: { position: "absolute", left: Math.max(1, Math.floor(
|
|
20560
|
+
style: { position: "absolute", left: Math.max(1, Math.floor(dimensions2.width / 10)), top: 2, width: Math.max(50, Math.floor(dimensions2.width * 4 / 5)), height: Math.min(26, Math.max(14, dimensions2.height - 4)), zIndex: 70, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
19449
20561
|
children: /* @__PURE__ */ jsx4("text", {
|
|
19450
20562
|
content: `Review commands
|
|
19451
20563
|
${reviewHelp(focus, state)}
|
|
@@ -19456,7 +20568,7 @@ Esc close this help`,
|
|
|
19456
20568
|
}) : null,
|
|
19457
20569
|
finishDialog.isOpen() ? /* @__PURE__ */ jsxs4("box", {
|
|
19458
20570
|
id: "review-finish-dialog",
|
|
19459
|
-
style: { position: "absolute", left: Math.max(1, Math.floor(
|
|
20571
|
+
style: { position: "absolute", left: Math.max(1, Math.floor(dimensions2.width / 8)), top: 3, width: Math.max(40, Math.floor(dimensions2.width * 3 / 4)), height: 10, zIndex: 60, border: true, flexDirection: "column", backgroundColor: "#202020" },
|
|
19460
20572
|
children: [
|
|
19461
20573
|
/* @__PURE__ */ jsx4("text", {
|
|
19462
20574
|
content: `Finish review — ${finishDialog.getDecision()}`,
|
|
@@ -23030,6 +24142,7 @@ function createApp(options) {
|
|
|
23030
24142
|
repositoryRoot: options.repositoryRoot,
|
|
23031
24143
|
runner: options.runner,
|
|
23032
24144
|
...pullRequestLoader === undefined ? {} : { loadPullRequests: pullRequestLoader },
|
|
24145
|
+
...options.loadCommits === undefined ? {} : { loadCommits: options.loadCommits },
|
|
23033
24146
|
onPullRequestsChanged: (state) => {
|
|
23034
24147
|
renderPullRequests?.(state);
|
|
23035
24148
|
}
|
|
@@ -23309,6 +24422,12 @@ function createApp(options) {
|
|
|
23309
24422
|
},
|
|
23310
24423
|
loadCommitInspection: (oid) => controller.loadCommitInspection(oid),
|
|
23311
24424
|
loadBranchCommits: options.loadBranchCommits ?? ((branch) => controller.loadBranchCommits(branch)),
|
|
24425
|
+
onExpandCommits: async () => {
|
|
24426
|
+
const expanded = await controller.expandCommits();
|
|
24427
|
+
if (expanded && (screenController?.shouldRenderRepository() ?? true))
|
|
24428
|
+
view.update(controller.state, { preserveFilterInput: true });
|
|
24429
|
+
return expanded;
|
|
24430
|
+
},
|
|
23312
24431
|
loadCommitFileInspection: (oid, path) => controller.loadCommitFileInspection(oid, path),
|
|
23313
24432
|
loadTagInspection: (tag) => controller.loadTagInspection(tag),
|
|
23314
24433
|
loadRefLogInspection: (target) => controller.loadRefLogInspection(target),
|
|
@@ -23675,8 +24794,9 @@ function createApp(options) {
|
|
|
23675
24794
|
function shouldQueryTerminalPalette(env = process.env, capabilities) {
|
|
23676
24795
|
return env.ZELLIJ === undefined && env.ZELLIJ_SESSION_NAME === undefined && env.TERM_PROGRAM?.toLowerCase() !== "zellij" && capabilities?.multiplexer !== "zellij";
|
|
23677
24796
|
}
|
|
23678
|
-
async function startApp() {
|
|
23679
|
-
const
|
|
24797
|
+
async function startApp(options = {}) {
|
|
24798
|
+
const startDirectory = options.startDirectory === undefined ? process.cwd() : resolve4(process.cwd(), options.startDirectory);
|
|
24799
|
+
const runner = new GitRunner(startDirectory);
|
|
23680
24800
|
let repositoryRoot;
|
|
23681
24801
|
try {
|
|
23682
24802
|
repositoryRoot = (await runner.run(["rev-parse", "--show-toplevel"], { readOnly: true })).stdout.trim();
|
|
@@ -23721,4 +24841,17 @@ ${detail}
|
|
|
23721
24841
|
if (false) {}
|
|
23722
24842
|
|
|
23723
24843
|
// src/cli.ts
|
|
23724
|
-
|
|
24844
|
+
var result = parseCliArgs(process.argv.slice(2));
|
|
24845
|
+
if (result.kind === "help" || result.kind === "version") {
|
|
24846
|
+
process.stdout.write(result.text.endsWith(`
|
|
24847
|
+
`) ? result.text : `${result.text}
|
|
24848
|
+
`);
|
|
24849
|
+
process.exitCode = 0;
|
|
24850
|
+
} else if (result.kind === "error") {
|
|
24851
|
+
process.stderr.write(result.message.endsWith(`
|
|
24852
|
+
`) ? result.message : `${result.message}
|
|
24853
|
+
`);
|
|
24854
|
+
process.exitCode = result.exitCode;
|
|
24855
|
+
} else {
|
|
24856
|
+
process.exitCode = await startApp(result.startDirectory === undefined ? {} : { startDirectory: result.startDirectory });
|
|
24857
|
+
}
|