@fresh-editor/fresh-editor 0.2.25 → 0.3.1

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/README.md +6 -0
  3. package/package.json +1 -1
  4. package/plugins/astro-lsp.ts +6 -12
  5. package/plugins/audit_mode.i18n.json +14 -14
  6. package/plugins/audit_mode.ts +182 -146
  7. package/plugins/bash-lsp.ts +15 -22
  8. package/plugins/clangd-lsp.ts +15 -24
  9. package/plugins/clojure-lsp.ts +9 -12
  10. package/plugins/cmake-lsp.ts +9 -12
  11. package/plugins/code-tour.ts +15 -16
  12. package/plugins/config-schema.json +79 -6
  13. package/plugins/csharp_support.ts +25 -30
  14. package/plugins/css-lsp.ts +15 -22
  15. package/plugins/dart-lsp.ts +9 -12
  16. package/plugins/dashboard.ts +1903 -0
  17. package/plugins/devcontainer.i18n.json +1472 -0
  18. package/plugins/devcontainer.ts +2793 -0
  19. package/plugins/diagnostics_panel.ts +10 -17
  20. package/plugins/elixir-lsp.ts +9 -12
  21. package/plugins/erlang-lsp.ts +9 -12
  22. package/plugins/examples/bookmarks.ts +10 -16
  23. package/plugins/find_references.ts +5 -9
  24. package/plugins/flash.ts +577 -0
  25. package/plugins/fsharp-lsp.ts +9 -12
  26. package/plugins/git_explorer.ts +16 -20
  27. package/plugins/git_gutter.ts +65 -79
  28. package/plugins/git_log.i18n.json +14 -42
  29. package/plugins/git_log.ts +19 -9
  30. package/plugins/gleam-lsp.ts +9 -12
  31. package/plugins/go-lsp.ts +15 -22
  32. package/plugins/graphql-lsp.ts +9 -12
  33. package/plugins/haskell-lsp.ts +9 -12
  34. package/plugins/html-lsp.ts +15 -24
  35. package/plugins/java-lsp.ts +9 -12
  36. package/plugins/json-lsp.ts +15 -24
  37. package/plugins/julia-lsp.ts +9 -12
  38. package/plugins/kotlin-lsp.ts +15 -22
  39. package/plugins/latex-lsp.ts +9 -12
  40. package/plugins/lib/fresh.d.ts +603 -0
  41. package/plugins/lua-lsp.ts +15 -22
  42. package/plugins/markdown_compose.ts +132 -128
  43. package/plugins/markdown_source.ts +8 -10
  44. package/plugins/marksman-lsp.ts +9 -12
  45. package/plugins/merge_conflict.ts +15 -17
  46. package/plugins/nim-lsp.ts +9 -12
  47. package/plugins/nix-lsp.ts +9 -12
  48. package/plugins/nushell-lsp.ts +9 -12
  49. package/plugins/ocaml-lsp.ts +9 -12
  50. package/plugins/odin-lsp.ts +15 -22
  51. package/plugins/path_complete.ts +5 -6
  52. package/plugins/perl-lsp.ts +9 -12
  53. package/plugins/php-lsp.ts +15 -22
  54. package/plugins/pkg.ts +10 -21
  55. package/plugins/protobuf-lsp.ts +9 -12
  56. package/plugins/python-lsp.ts +15 -24
  57. package/plugins/r-lsp.ts +9 -12
  58. package/plugins/ruby-lsp.ts +15 -22
  59. package/plugins/rust-lsp.ts +18 -28
  60. package/plugins/scala-lsp.ts +9 -12
  61. package/plugins/schemas/theme.schema.json +126 -0
  62. package/plugins/search_replace.ts +10 -13
  63. package/plugins/solidity-lsp.ts +9 -12
  64. package/plugins/sql-lsp.ts +9 -12
  65. package/plugins/svelte-lsp.ts +9 -12
  66. package/plugins/swift-lsp.ts +9 -12
  67. package/plugins/tailwindcss-lsp.ts +9 -12
  68. package/plugins/templ-lsp.ts +9 -12
  69. package/plugins/terraform-lsp.ts +9 -12
  70. package/plugins/theme_editor.i18n.json +98 -14
  71. package/plugins/theme_editor.ts +156 -209
  72. package/plugins/toml-lsp.ts +15 -22
  73. package/plugins/tsconfig.json +100 -0
  74. package/plugins/typescript-lsp.ts +15 -24
  75. package/plugins/typst-lsp.ts +15 -22
  76. package/plugins/vi_mode.ts +77 -290
  77. package/plugins/vue-lsp.ts +9 -12
  78. package/plugins/yaml-lsp.ts +15 -22
  79. package/plugins/zig-lsp.ts +9 -12
  80. package/themes/high-contrast.json +2 -2
  81. package/themes/nord.json +4 -0
  82. package/themes/solarized-dark.json +4 -0
@@ -54,6 +54,7 @@ interface Hunk {
54
54
  oldRange: { start: number; end: number }; // old file line range
55
55
  type: 'add' | 'remove' | 'modify';
56
56
  lines: string[];
57
+ status?: string;
57
58
  contextHeader: string;
58
59
  byteOffset: number; // Position in the virtual buffer
59
60
  gitStatus?: 'staged' | 'unstaged' | 'untracked';
@@ -1579,7 +1580,7 @@ function on_review_mouse_click(data: {
1579
1580
  else state.collapsedSections.add(cat);
1580
1581
  applyFolds();
1581
1582
  const sectionRow = state.sectionHeaderRows[cat];
1582
- if (sectionRow !== undefined) jumpDiffCursorToRow(sectionRow);
1583
+ if (sectionRow !== undefined) jumpDiffCursorToRow(sectionRow, { recenter: false });
1583
1584
  return;
1584
1585
  }
1585
1586
  }
@@ -1591,7 +1592,7 @@ function on_review_mouse_click(data: {
1591
1592
  else state.collapsedFiles.add(key);
1592
1593
  applyFolds();
1593
1594
  const headerRow = state.fileHeaderRows[key];
1594
- if (headerRow !== undefined) jumpDiffCursorToRow(headerRow);
1595
+ if (headerRow !== undefined) jumpDiffCursorToRow(headerRow, { recenter: false });
1595
1596
  return;
1596
1597
  }
1597
1598
  }
@@ -1602,7 +1603,7 @@ function on_review_mouse_click(data: {
1602
1603
  else state.collapsedHunks.add(hunkId);
1603
1604
  applyFolds();
1604
1605
  const hunkRow = state.hunkRowByHunkId[hunkId];
1605
- if (hunkRow !== undefined) jumpDiffCursorToRow(hunkRow);
1606
+ if (hunkRow !== undefined) jumpDiffCursorToRow(hunkRow, { recenter: false });
1606
1607
  return;
1607
1608
  }
1608
1609
  }
@@ -1812,7 +1813,7 @@ function review_toggle_file_collapse() {
1812
1813
  else state.collapsedSections.add(section);
1813
1814
  applyFolds();
1814
1815
  const sectionRow = state.sectionHeaderRows[section];
1815
- if (sectionRow !== undefined) jumpDiffCursorToRow(sectionRow);
1816
+ if (sectionRow !== undefined) jumpDiffCursorToRow(sectionRow, { recenter: false });
1816
1817
  return;
1817
1818
  }
1818
1819
 
@@ -1824,7 +1825,7 @@ function review_toggle_file_collapse() {
1824
1825
  else state.collapsedFiles.add(key);
1825
1826
  applyFolds();
1826
1827
  const headerRow = state.fileHeaderRows[key];
1827
- if (headerRow !== undefined) jumpDiffCursorToRow(headerRow);
1828
+ if (headerRow !== undefined) jumpDiffCursorToRow(headerRow, { recenter: false });
1828
1829
  return;
1829
1830
  }
1830
1831
 
@@ -1835,7 +1836,7 @@ function review_toggle_file_collapse() {
1835
1836
  else state.collapsedHunks.add(hunk.id);
1836
1837
  applyFolds();
1837
1838
  const hunkRow = state.hunkRowByHunkId[hunk.id];
1838
- if (hunkRow !== undefined) jumpDiffCursorToRow(hunkRow);
1839
+ if (hunkRow !== undefined) jumpDiffCursorToRow(hunkRow, { recenter: false });
1839
1840
  return;
1840
1841
  }
1841
1842
 
@@ -1848,7 +1849,7 @@ function review_toggle_file_collapse() {
1848
1849
  else state.collapsedFiles.add(key);
1849
1850
  applyFolds();
1850
1851
  const headerRow = state.fileHeaderRows[key];
1851
- if (headerRow !== undefined) jumpDiffCursorToRow(headerRow);
1852
+ if (headerRow !== undefined) jumpDiffCursorToRow(headerRow, { recenter: false });
1852
1853
  }
1853
1854
  registerHandler("review_toggle_file_collapse", review_toggle_file_collapse);
1854
1855
 
@@ -2462,48 +2463,9 @@ function review_discard_file() {
2462
2463
  }
2463
2464
  registerHandler("review_discard_file", review_discard_file);
2464
2465
 
2465
- async function on_review_discard_hunk_confirm(args: { prompt_type: string; input: string; selected_index: number | null }): Promise<boolean> {
2466
- if (args.prompt_type !== "review-discard-hunk-confirm") return true;
2467
- const response = args.input.trim().toLowerCase();
2468
- if (response === "discard" || args.selected_index === 0) {
2469
- const hunk = getHunkAtDiffCursor();
2470
- if (hunk && hunk.file) {
2471
- const patch = buildHunkPatch(hunk.file, hunk);
2472
- const ok = await applyHunkPatch(patch, ["--reverse"]);
2473
- if (ok) {
2474
- editor.setStatus(editor.t("status.hunk_discarded") || "Hunk discarded");
2475
- await refreshMagitData();
2476
- }
2477
- }
2478
- } else {
2479
- editor.setStatus("Discard cancelled");
2480
- }
2481
- return false;
2482
- }
2483
- registerHandler("on_review_discard_hunk_confirm", on_review_discard_hunk_confirm);
2484
2466
 
2485
- async function on_review_discard_confirm(args: { prompt_type: string; input: string; selected_index: number | null }): Promise<boolean> {
2486
- if (args.prompt_type !== "review-discard-confirm") return true;
2487
2467
 
2488
- const response = args.input.trim().toLowerCase();
2489
- if (response === "discard" || args.selected_index === 0) {
2490
- const f = pendingDiscardFile;
2491
- if (f) {
2492
- if (f.category === 'untracked') {
2493
- await editor.spawnProcess("rm", ["--", f.path]);
2494
- } else {
2495
- await editor.spawnProcess("git", ["checkout", "--", f.path]);
2496
- }
2497
- await refreshMagitData();
2498
- editor.setStatus(`Discarded: ${f.path}`);
2499
- }
2500
- } else {
2501
- editor.setStatus("Discard cancelled");
2502
- }
2503
- pendingDiscardFile = null;
2504
- return false;
2505
- }
2506
- registerHandler("on_review_discard_confirm", on_review_discard_confirm);
2468
+
2507
2469
 
2508
2470
  /**
2509
2471
  * Refresh file list and diffs using the new git status approach, then re-render.
@@ -3147,23 +3109,31 @@ registerHandler("review_drill_down", review_drill_down);
3147
3109
  // --- Hunk navigation for side-by-side diff view ---
3148
3110
 
3149
3111
  /**
3150
- * Move the diff panel's native cursor to the given 1-indexed row, scrolling
3151
- * the viewport so the row is visible.
3112
+ * Move the diff panel's native cursor to the given 1-indexed row.
3113
+ *
3114
+ * `options.recenter` controls whether the viewport is re-centered on the
3115
+ * target row. The default is `true` for user-initiated navigation (next
3116
+ * hunk, jump-to-comment, jump-to-file) — there the caller wants the
3117
+ * target to land at a predictable position in the viewport. Callers
3118
+ * that merely re-anchor the cursor to a nearby header (e.g. after a
3119
+ * collapse/expand toggle) should pass `recenter: false` so the viewport
3120
+ * stays put; `setBufferCursor` still runs `ensure_cursor_visible`, so
3121
+ * the cursor is scrolled into view only when it would otherwise move
3122
+ * off-screen. Without this opt-out every fold toggle re-centers the
3123
+ * cursor's row at ~1/3 from the top of the viewport, which makes the
3124
+ * diff jump around whenever the user is reading anywhere else.
3152
3125
  */
3153
- function jumpDiffCursorToRow(row: number): void {
3126
+ function jumpDiffCursorToRow(row: number, options?: { recenter?: boolean }): void {
3154
3127
  const diffId = state.panelBuffers["diff"];
3155
3128
  if (diffId === undefined) return;
3156
3129
  const idx = row - 1;
3157
3130
  if (idx < 0 || idx >= state.diffLineByteOffsets.length) return;
3158
3131
 
3159
- // Set the cursor by absolute byte offset + scroll the viewport.
3160
- // Trust setBufferCursor — the previous N × executeAction("move_down")
3161
- // walk was O(target_row) round-trips into the editor and made
3162
- // collapsing big diffs visibly slow (thousands of round trips just
3163
- // to land the cursor on a header).
3164
3132
  const byteOffset = state.diffLineByteOffsets[idx];
3165
3133
  editor.setBufferCursor(diffId, byteOffset);
3166
- editor.scrollBufferToLine(diffId, idx);
3134
+ if (options?.recenter !== false) {
3135
+ editor.scrollBufferToLine(diffId, idx);
3136
+ }
3167
3137
  state.diffCursorRow = row;
3168
3138
  applyCursorLineOverlay('diff');
3169
3139
  refreshStickyHeader(idx);
@@ -3448,28 +3418,15 @@ async function review_delete_comment() {
3448
3418
  }
3449
3419
  registerHandler("review_delete_comment", review_delete_comment);
3450
3420
 
3451
- function on_review_delete_comment_confirm(args: { prompt_type: string; input: string; selected_index: number | null }): boolean {
3452
- if (args.prompt_type !== "review-delete-comment-confirm") return true;
3453
- const response = args.input.trim().toLowerCase();
3454
- if ((response === "delete" || args.selected_index === 0) && pendingDeleteCommentId) {
3455
- if (pendingDeleteCommentId === '__note__') {
3456
- state.note = '';
3457
- } else {
3458
- state.comments = state.comments.filter(c => c.id !== pendingDeleteCommentId);
3459
- }
3460
- persistReview();
3461
- updateMagitDisplay();
3462
- editor.setStatus("Deleted");
3463
- } else {
3464
- editor.setStatus("Delete cancelled");
3465
- }
3466
- pendingDeleteCommentId = null;
3467
- return false;
3468
- }
3469
- registerHandler("on_review_delete_comment_confirm", on_review_delete_comment_confirm);
3421
+
3470
3422
 
3471
3423
  // Prompt event handlers
3472
- function on_review_prompt_confirm(args: { prompt_type: string; input: string }): boolean {
3424
+
3425
+
3426
+
3427
+
3428
+ // Register prompt event handlers
3429
+ editor.on("prompt_confirmed", (args) => {
3473
3430
  if (args.prompt_type !== "review-comment") {
3474
3431
  return true;
3475
3432
  }
@@ -3532,38 +3489,47 @@ function on_review_prompt_confirm(args: { prompt_type: string; input: string }):
3532
3489
  }
3533
3490
  pendingCommentInfo = null;
3534
3491
  return true;
3535
- }
3536
- registerHandler("on_review_prompt_confirm", on_review_prompt_confirm);
3492
+ });
3493
+ editor.on("prompt_confirmed", async (args) => {
3494
+ if (args.prompt_type !== "review-discard-confirm") return true;
3537
3495
 
3538
- function on_review_prompt_cancel(args: { prompt_type: string }): boolean {
3539
- if (args.prompt_type === "review-comment") {
3540
- pendingCommentInfo = null;
3541
- editingCommentId = null;
3542
- editor.setStatus(editor.t("status.comment_cancelled"));
3496
+ const response = args.input.trim().toLowerCase();
3497
+ if (response === "discard" || args.selected_index === 0) {
3498
+ const f = pendingDiscardFile;
3499
+ if (f) {
3500
+ if (f.category === 'untracked') {
3501
+ await editor.spawnProcess("rm", ["--", f.path]);
3502
+ } else {
3503
+ await editor.spawnProcess("git", ["checkout", "--", f.path]);
3504
+ }
3505
+ await refreshMagitData();
3506
+ editor.setStatus(`Discarded: ${f.path}`);
3507
+ }
3508
+ } else {
3509
+ editor.setStatus("Discard cancelled");
3543
3510
  }
3544
- return true;
3545
- }
3546
- registerHandler("on_review_prompt_cancel", on_review_prompt_cancel);
3547
-
3548
- // Register prompt event handlers
3549
- editor.on("prompt_confirmed", "on_review_prompt_confirm");
3550
- editor.on("prompt_confirmed", "on_review_discard_confirm");
3551
- editor.on("prompt_confirmed", "on_review_discard_hunk_confirm");
3552
- editor.on("prompt_confirmed", "on_review_edit_note_confirm");
3553
- editor.on("prompt_confirmed", "on_review_delete_comment_confirm");
3554
- editor.on("prompt_cancelled", "on_review_prompt_cancel");
3555
-
3556
- async function review_edit_note() {
3557
- const label = editor.t("prompt.overall_comment") || "Note: ";
3558
- if (state.note) {
3559
- editor.startPromptWithInitial(label, "review-edit-note", state.note);
3511
+ pendingDiscardFile = null;
3512
+ return false;
3513
+ });
3514
+ editor.on("prompt_confirmed", async (args) => {
3515
+ if (args.prompt_type !== "review-discard-hunk-confirm") return true;
3516
+ const response = args.input.trim().toLowerCase();
3517
+ if (response === "discard" || args.selected_index === 0) {
3518
+ const hunk = getHunkAtDiffCursor();
3519
+ if (hunk && hunk.file) {
3520
+ const patch = buildHunkPatch(hunk.file, hunk);
3521
+ const ok = await applyHunkPatch(patch, ["--reverse"]);
3522
+ if (ok) {
3523
+ editor.setStatus(editor.t("status.hunk_discarded") || "Hunk discarded");
3524
+ await refreshMagitData();
3525
+ }
3526
+ }
3560
3527
  } else {
3561
- editor.startPrompt(label, "review-edit-note");
3528
+ editor.setStatus("Discard cancelled");
3562
3529
  }
3563
- }
3564
- registerHandler("review_edit_note", review_edit_note);
3565
-
3566
- function on_review_edit_note_confirm(args: { prompt_type: string; input: string }): boolean {
3530
+ return false;
3531
+ });
3532
+ editor.on("prompt_confirmed", (args) => {
3567
3533
  if (args.prompt_type !== "review-edit-note") return true;
3568
3534
  if (args.input && args.input.trim()) {
3569
3535
  state.note = args.input.trim();
@@ -3577,8 +3543,45 @@ function on_review_edit_note_confirm(args: { prompt_type: string; input: string
3577
3543
  }
3578
3544
  }
3579
3545
  return true;
3546
+ });
3547
+ editor.on("prompt_confirmed", (args) => {
3548
+ if (args.prompt_type !== "review-delete-comment-confirm") return true;
3549
+ const response = args.input.trim().toLowerCase();
3550
+ if ((response === "delete" || args.selected_index === 0) && pendingDeleteCommentId) {
3551
+ if (pendingDeleteCommentId === '__note__') {
3552
+ state.note = '';
3553
+ } else {
3554
+ state.comments = state.comments.filter(c => c.id !== pendingDeleteCommentId);
3555
+ }
3556
+ persistReview();
3557
+ updateMagitDisplay();
3558
+ editor.setStatus("Deleted");
3559
+ } else {
3560
+ editor.setStatus("Delete cancelled");
3561
+ }
3562
+ pendingDeleteCommentId = null;
3563
+ return false;
3564
+ });
3565
+ editor.on("prompt_cancelled", (args) => {
3566
+ if (args.prompt_type === "review-comment") {
3567
+ pendingCommentInfo = null;
3568
+ editingCommentId = null;
3569
+ editor.setStatus(editor.t("status.comment_cancelled"));
3570
+ }
3571
+ return true;
3572
+ });
3573
+
3574
+ async function review_edit_note() {
3575
+ const label = editor.t("prompt.overall_comment") || "Note: ";
3576
+ if (state.note) {
3577
+ editor.startPromptWithInitial(label, "review-edit-note", state.note);
3578
+ } else {
3579
+ editor.startPrompt(label, "review-edit-note");
3580
+ }
3580
3581
  }
3581
- registerHandler("on_review_edit_note_confirm", on_review_edit_note_confirm);
3582
+ registerHandler("review_edit_note", review_edit_note);
3583
+
3584
+
3582
3585
 
3583
3586
  async function review_export_session() {
3584
3587
  const cwd = editor.getCwd();
@@ -3728,15 +3731,15 @@ async function openReviewPanels(groupName: string): Promise<boolean> {
3728
3731
 
3729
3732
  updateMagitDisplay();
3730
3733
 
3731
- editor.focusBufferGroupPanel(state.groupId, "diff");
3734
+ editor.focusBufferGroupPanel(state.groupId!, "diff");
3732
3735
 
3733
- editor.on("resize", "onReviewDiffResize");
3736
+ editor.on("resize", onReviewDiffResize);
3734
3737
  updateReviewStatus();
3735
- editor.on("buffer_activated", "on_review_buffer_activated");
3736
- editor.on("buffer_closed", "on_review_buffer_closed");
3737
- editor.on("cursor_moved", "on_review_cursor_moved");
3738
- editor.on("viewport_changed", "on_review_viewport_changed");
3739
- editor.on("mouse_click", "on_review_mouse_click");
3738
+ editor.on("buffer_activated", on_review_buffer_activated);
3739
+ editor.on("buffer_closed", on_review_buffer_closed);
3740
+ editor.on("cursor_moved", on_review_cursor_moved);
3741
+ editor.on("viewport_changed", on_review_viewport_changed);
3742
+ editor.on("mouse_click", on_review_mouse_click);
3740
3743
  return true;
3741
3744
  }
3742
3745
 
@@ -3812,12 +3815,12 @@ function stop_review_diff() {
3812
3815
  }
3813
3816
  state.reviewBufferId = null;
3814
3817
  editor.setContext("review-mode", false);
3815
- editor.off("resize", "onReviewDiffResize");
3816
- editor.off("buffer_activated", "on_review_buffer_activated");
3817
- editor.off("buffer_closed", "on_review_buffer_closed");
3818
- editor.off("cursor_moved", "on_review_cursor_moved");
3819
- editor.off("viewport_changed", "on_review_viewport_changed");
3820
- editor.off("mouse_click", "on_review_mouse_click");
3818
+ editor.off("resize", onReviewDiffResize);
3819
+ editor.off("buffer_activated", on_review_buffer_activated);
3820
+ editor.off("buffer_closed", on_review_buffer_closed);
3821
+ editor.off("cursor_moved", on_review_cursor_moved);
3822
+ editor.off("viewport_changed", on_review_viewport_changed);
3823
+ editor.off("mouse_click", on_review_mouse_click);
3821
3824
  editor.setStatus(editor.t("status.stopped"));
3822
3825
  }
3823
3826
  registerHandler("stop_review_diff", stop_review_diff);
@@ -3960,7 +3963,8 @@ async function start_review_range(): Promise<void> {
3960
3963
  }
3961
3964
  registerHandler("start_review_range", start_review_range);
3962
3965
 
3963
- function on_review_range_confirm(args: { prompt_type: string; input: string }): boolean {
3966
+
3967
+ editor.on("prompt_confirmed", (args) => {
3964
3968
  if (args.prompt_type !== "review-range") return true;
3965
3969
  const range = parseRangeInput(args.input);
3966
3970
  if (!range) {
@@ -3971,9 +3975,7 @@ function on_review_range_confirm(args: { prompt_type: string; input: string }):
3971
3975
  // can return immediately.
3972
3976
  bootstrapRangeReview(range);
3973
3977
  return true;
3974
- }
3975
- registerHandler("on_review_range_confirm", on_review_range_confirm);
3976
- editor.on("prompt_confirmed", "on_review_range_confirm");
3978
+ });
3977
3979
 
3978
3980
  async function bootstrapRangeReview(range: ReviewRange): Promise<void> {
3979
3981
  editor.setStatus(editor.t("status.generating") || "Generating diff…");
@@ -4388,12 +4390,47 @@ const branchState: ReviewBranchState = {
4388
4390
  detailBufferId: null,
4389
4391
  commits: [],
4390
4392
  selectedIndex: 0,
4391
- baseRef: "main",
4393
+ // Empty means "not yet detected"; start_review_branch fills this in
4394
+ // from the repo's actual default branch (main, master, or whatever
4395
+ // origin/HEAD points at) before showing the prompt.
4396
+ baseRef: "",
4392
4397
  detailCache: null,
4393
4398
  pendingDetailId: 0,
4394
4399
  logRowByteOffsets: [],
4395
4400
  };
4396
4401
 
4402
+ /**
4403
+ * Best-effort detection of the repo's default branch. Checks, in order:
4404
+ * 1. `origin/HEAD` (the remote's notion of the default branch)
4405
+ * 2. local `main`
4406
+ * 3. local `master`
4407
+ * Falls back to `main` if none match, so the prompt still has a sensible
4408
+ * default in an empty / unusual repo.
4409
+ */
4410
+ async function detectDefaultBranch(): Promise<string> {
4411
+ try {
4412
+ const r = await editor.spawnProcess("git", [
4413
+ "symbolic-ref", "--short", "refs/remotes/origin/HEAD",
4414
+ ]);
4415
+ if (r.exit_code === 0) {
4416
+ const name = r.stdout.trim();
4417
+ // Output looks like "origin/main"; strip the remote prefix.
4418
+ const slash = name.indexOf("/");
4419
+ const branch = slash >= 0 ? name.slice(slash + 1) : name;
4420
+ if (branch) return branch;
4421
+ }
4422
+ } catch { /* fall through */ }
4423
+ for (const candidate of ["main", "master"]) {
4424
+ try {
4425
+ const r = await editor.spawnProcess("git", [
4426
+ "show-ref", "--verify", "--quiet", `refs/heads/${candidate}`,
4427
+ ]);
4428
+ if (r.exit_code === 0) return candidate;
4429
+ } catch { /* fall through */ }
4430
+ }
4431
+ return "main";
4432
+ }
4433
+
4397
4434
  // UTF-8 byte length helper, local copy so audit_mode doesn't pull in the one
4398
4435
  // from git_history (keeps the import list tiny).
4399
4436
  function branchUtf8Len(s: string): number {
@@ -4501,16 +4538,20 @@ async function start_review_branch(): Promise<void> {
4501
4538
  return;
4502
4539
  }
4503
4540
  // Prompt for the base ref so the user can review any PR, not just
4504
- // one branched off main.
4505
- const input = await editor.prompt(
4506
- editor.t("prompt.branch_base") || "Base ref (default: main):",
4507
- branchState.baseRef,
4508
- );
4541
+ // one branched off main. The default offered is either what the user
4542
+ // picked last time in this session, or the repo's actual default
4543
+ // branch (main/master/etc.) on first use.
4544
+ const suggested = branchState.baseRef || await detectDefaultBranch();
4545
+ const rawPromptText = editor.t("prompt.branch_base", { default: suggested });
4546
+ const promptText = (rawPromptText && !rawPromptText.startsWith("prompt."))
4547
+ ? rawPromptText
4548
+ : `Base ref to compare against (default: ${suggested}):`;
4549
+ const input = await editor.prompt(promptText + " ", suggested);
4509
4550
  if (input === null) {
4510
4551
  editor.setStatus(editor.t("status.cancelled") || "Cancelled");
4511
4552
  return;
4512
4553
  }
4513
- const base = input.trim() || "main";
4554
+ const base = input.trim() || suggested;
4514
4555
  branchState.baseRef = base;
4515
4556
 
4516
4557
  editor.setStatus(editor.t("status.loading") || "Loading commits…");
@@ -4560,7 +4601,7 @@ async function start_review_branch(): Promise<void> {
4560
4601
  if (branchState.groupId !== null) {
4561
4602
  editor.focusBufferGroupPanel(branchState.groupId, "log");
4562
4603
  }
4563
- editor.on("cursor_moved", "on_review_branch_cursor_moved");
4604
+ editor.on("cursor_moved", on_review_branch_cursor_moved);
4564
4605
 
4565
4606
  editor.setStatus(
4566
4607
  editor.t("status.review_branch_ready", {
@@ -4574,7 +4615,7 @@ registerHandler("start_review_branch", start_review_branch);
4574
4615
  function stop_review_branch(): void {
4575
4616
  if (!branchState.isOpen) return;
4576
4617
  if (branchState.groupId !== null) editor.closeBufferGroup(branchState.groupId);
4577
- editor.off("cursor_moved", "on_review_branch_cursor_moved");
4618
+ editor.off("cursor_moved", on_review_branch_cursor_moved);
4578
4619
  branchState.isOpen = false;
4579
4620
  branchState.groupId = null;
4580
4621
  branchState.logBufferId = null;
@@ -4637,17 +4678,11 @@ registerHandler("on_review_branch_cursor_moved", on_review_branch_cursor_moved);
4637
4678
  editor.defineMode(
4638
4679
  "review-branch",
4639
4680
  [
4640
- // Mode bindings replace globals, so we re-bind the editor's built-in
4641
- // motion actions here explicitly — without this, j/k and Up/Down
4642
- // do nothing in the commit list.
4643
- ["Up", "move_up"],
4644
- ["Down", "move_down"],
4681
+ // vi-style aliases for Up/Down. Everything else (arrows,
4682
+ // Page{Up,Down}, Home/End, selection motion, …) is inherited
4683
+ // from the Normal keymap via `inheritNormalBindings: true`.
4645
4684
  ["k", "move_up"],
4646
4685
  ["j", "move_down"],
4647
- ["PageUp", "page_up"],
4648
- ["PageDown", "page_down"],
4649
- ["Home", "move_line_start"],
4650
- ["End", "move_line_end"],
4651
4686
  // Enter: focus the right-hand detail panel.
4652
4687
  ["Return", "review_branch_enter"],
4653
4688
  ["Tab", "review_branch_enter"],
@@ -4655,7 +4690,9 @@ editor.defineMode(
4655
4690
  ["q", "review_branch_close_or_back"],
4656
4691
  ["Escape", "review_branch_close_or_back"],
4657
4692
  ],
4658
- true,
4693
+ true, // readOnly
4694
+ false, // allowTextInput — keeps plain letters from inserting into the RO buffer
4695
+ true, // inheritNormalBindings — PageUp/PageDown/arrows/Home/End come from Normal
4659
4696
  );
4660
4697
 
4661
4698
  // Register Modes and Commands
@@ -4674,7 +4711,9 @@ editor.registerCommand("%cmd.export_markdown", "%cmd.export_markdown_desc", "rev
4674
4711
  editor.registerCommand("%cmd.export_json", "%cmd.export_json_desc", "review_export_json", "review-mode");
4675
4712
 
4676
4713
  // Handler for when buffers are closed - cleans up scroll sync groups and composite buffers
4677
- function on_buffer_closed(data: any) {
4714
+
4715
+
4716
+ editor.on("buffer_closed", (data) => {
4678
4717
  // If one of the diff view buffers is closed, clean up the scroll sync group
4679
4718
  if (activeSideBySideState) {
4680
4719
  if (data.buffer_id === activeSideBySideState.oldBufferId ||
@@ -4701,10 +4740,7 @@ function on_buffer_closed(data: any) {
4701
4740
  activeCompositeDiffState = null;
4702
4741
  }
4703
4742
  }
4704
- }
4705
- registerHandler("on_buffer_closed", on_buffer_closed);
4706
-
4707
- editor.on("buffer_closed", "on_buffer_closed");
4743
+ });
4708
4744
 
4709
4745
  editor.defineMode("review-mode", [
4710
4746
  // Native cursor motion in the unified diff stream.
@@ -49,7 +49,10 @@ let bashLspError: { serverCommand: string; message: string } | null = null;
49
49
  /**
50
50
  * Handle LSP server errors for Bash
51
51
  */
52
- function on_bash_lsp_server_error(data: LspServerErrorData): void {
52
+
53
+
54
+ // Register hook for LSP server errors
55
+ editor.on("lsp_server_error", (data) => {
53
56
  // Only handle Bash language errors
54
57
  if (data.language !== "bash") {
55
58
  return;
@@ -71,18 +74,15 @@ function on_bash_lsp_server_error(data: LspServerErrorData): void {
71
74
  } else {
72
75
  editor.setStatus(`Bash LSP error: ${data.message}`);
73
76
  }
74
- }
75
- registerHandler("on_bash_lsp_server_error", on_bash_lsp_server_error);
76
-
77
- // Register hook for LSP server errors
78
- editor.on("lsp_server_error", "on_bash_lsp_server_error");
77
+ });
79
78
 
80
79
  /**
81
80
  * Handle status bar click when there's a Bash LSP error
82
81
  */
83
- function on_bash_lsp_status_clicked(
84
- data: LspStatusClickedData
85
- ): void {
82
+
83
+
84
+ // Register hook for status bar clicks
85
+ editor.on("lsp_status_clicked", (data) => {
86
86
  // Only handle Bash language clicks when there's an error
87
87
  if (data.language !== "bash" || !bashLspError) {
88
88
  return;
@@ -103,18 +103,15 @@ function on_bash_lsp_status_clicked(
103
103
  { id: "dismiss", label: "Dismiss (ESC)" },
104
104
  ],
105
105
  });
106
- }
107
- registerHandler("on_bash_lsp_status_clicked", on_bash_lsp_status_clicked);
108
-
109
- // Register hook for status bar clicks
110
- editor.on("lsp_status_clicked", "on_bash_lsp_status_clicked");
106
+ });
111
107
 
112
108
  /**
113
109
  * Handle action popup results for Bash LSP help
114
110
  */
115
- function on_bash_lsp_action_result(
116
- data: ActionPopupResultData
117
- ): void {
111
+
112
+
113
+ // Register hook for action popup results
114
+ editor.on("action_popup_result", (data) => {
118
115
  // Only handle our popup
119
116
  if (data.popup_id !== "bash-lsp-help") {
120
117
  return;
@@ -152,10 +149,6 @@ function on_bash_lsp_action_result(
152
149
  default:
153
150
  editor.debug(`bash-lsp: Unknown action: ${data.action_id}`);
154
151
  }
155
- }
156
- registerHandler("on_bash_lsp_action_result", on_bash_lsp_action_result);
157
-
158
- // Register hook for action popup results
159
- editor.on("action_popup_result", "on_bash_lsp_action_result");
152
+ });
160
153
 
161
154
  editor.debug("bash-lsp: Plugin loaded");
@@ -54,9 +54,10 @@ let clangdLspError: {
54
54
  /**
55
55
  * Handle LSP server errors for C/C++
56
56
  */
57
- function on_clangd_lsp_server_error(
58
- data: LspServerErrorData
59
- ): void {
57
+
58
+
59
+ // Register hook for LSP server errors
60
+ editor.on("lsp_server_error", (data) => {
60
61
  // Only handle C/C++ language errors
61
62
  if (!HANDLED_LANGUAGES.includes(data.language)) {
62
63
  return;
@@ -79,18 +80,15 @@ function on_clangd_lsp_server_error(
79
80
  } else {
80
81
  editor.setStatus(`C/C++ LSP error: ${data.message}`);
81
82
  }
82
- }
83
- registerHandler("on_clangd_lsp_server_error", on_clangd_lsp_server_error);
84
-
85
- // Register hook for LSP server errors
86
- editor.on("lsp_server_error", "on_clangd_lsp_server_error");
83
+ });
87
84
 
88
85
  /**
89
86
  * Handle status bar click when there's a C/C++ LSP error
90
87
  */
91
- function on_clangd_lsp_status_clicked(
92
- data: LspStatusClickedData
93
- ): void {
88
+
89
+
90
+ // Register hook for status bar clicks
91
+ editor.on("lsp_status_clicked", (data) => {
94
92
  // Only handle C/C++ language clicks when there's an error
95
93
  if (!HANDLED_LANGUAGES.includes(data.language) || !clangdLspError) {
96
94
  return;
@@ -111,18 +109,15 @@ function on_clangd_lsp_status_clicked(
111
109
  { id: "dismiss", label: "Dismiss (ESC)" },
112
110
  ],
113
111
  });
114
- }
115
- registerHandler("on_clangd_lsp_status_clicked", on_clangd_lsp_status_clicked);
116
-
117
- // Register hook for status bar clicks
118
- editor.on("lsp_status_clicked", "on_clangd_lsp_status_clicked");
112
+ });
119
113
 
120
114
  /**
121
115
  * Handle action popup results for C/C++ LSP help
122
116
  */
123
- function on_clangd_lsp_action_result(
124
- data: ActionPopupResultData
125
- ): void {
117
+
118
+
119
+ // Register hook for action popup results
120
+ editor.on("action_popup_result", (data) => {
126
121
  // Only handle our popup
127
122
  if (data.popup_id !== "clangd-lsp-help") {
128
123
  return;
@@ -162,10 +157,6 @@ function on_clangd_lsp_action_result(
162
157
  default:
163
158
  editor.debug(`clangd-lsp: Unknown action: ${data.action_id}`);
164
159
  }
165
- }
166
- registerHandler("on_clangd_lsp_action_result", on_clangd_lsp_action_result);
167
-
168
- // Register hook for action popup results
169
- editor.on("action_popup_result", "on_clangd_lsp_action_result");
160
+ });
170
161
 
171
162
  editor.debug("clangd-lsp: Plugin loaded");