difit 4.0.5 → 4.0.7
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.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/cli/comment.d.ts +2 -0
- package/dist/cli/comment.js +91 -0
- package/dist/cli/comment.test.d.ts +1 -0
- package/dist/cli/comment.test.js +164 -0
- package/dist/cli/index.js +103 -12
- package/dist/cli/utils.d.ts +1 -0
- package/dist/cli/utils.js +7 -0
- package/dist/client/assets/{_baseFor-DKyA49xd.js → _baseFor-Dq1lbcoh.js} +1 -1
- package/dist/client/assets/{arc-COOp7iVe.js → arc-DX2p9X2Y.js} +1 -1
- package/dist/client/assets/architecture-YZFGNWBL-2zVtKbnG.js +1 -0
- package/dist/client/assets/architectureDiagram-Q4EWVU46-FixTWViB.js +36 -0
- package/dist/client/assets/{blockDiagram-DXYQGD6D-CtNJnEWN.js → blockDiagram-DXYQGD6D-CUAMgGr9.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-BqG-1m6C.js → c4Diagram-AHTNJAMY-BM_HNNZe.js} +1 -1
- package/dist/client/assets/channel-B_ddQhpW.js +1 -0
- package/dist/client/assets/{chunk-2KRD3SAO-DqP2NJNd.js → chunk-2KRD3SAO-DeT59g2K.js} +1 -1
- package/dist/client/assets/chunk-336JU56O-CaGvJA86.js +2 -0
- package/dist/client/assets/chunk-426QAEUC-CMTCMPn4.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-BT78EnQ6.js → chunk-4BX2VUAB-D9mNDl5f.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-C4w_Bwzw.js → chunk-4TB4RGXK-Df3b4HEG.js} +2 -2
- package/dist/client/assets/{chunk-55IACEB6-z3MQSTaj.js → chunk-55IACEB6-dCWLe_n4.js} +1 -1
- package/dist/client/assets/{chunk-5FUZZQ4R-Chei69aj.js → chunk-5FUZZQ4R-EScvXcSN.js} +1 -1
- package/dist/client/assets/{chunk-5PVQY5BW-HgRiIs0X.js → chunk-5PVQY5BW-ail-oj89.js} +1 -1
- package/dist/client/assets/{chunk-67CJDMHE-B2q10-fp.js → chunk-67CJDMHE-CHeCIL1u.js} +1 -1
- package/dist/client/assets/{chunk-7N4EOEYR-DPgxysWq.js → chunk-7N4EOEYR-P0tNRVMZ.js} +1 -1
- package/dist/client/assets/{chunk-AA7GKIK3-BqmVmKLq.js → chunk-AA7GKIK3-DloBHWSo.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-CaIgleFn.js → chunk-BSJP7CBP-CGLThsR8.js} +1 -1
- package/dist/client/assets/{chunk-CIAEETIT-ByD-tlNF.js → chunk-CIAEETIT-rCt2IEMp.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-d3RUKKAj.js → chunk-EDXVE4YY-DIJEIKIq.js} +1 -1
- package/dist/client/assets/{chunk-ENJZ2VHE-CNq5Qmg9.js → chunk-ENJZ2VHE-CdrdxFfV.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-DYfHJ6MV.js → chunk-FMBD7UC4-BH_GgR9u.js} +1 -1
- package/dist/client/assets/{chunk-FOC6F5B3-BRpSWlZj.js → chunk-FOC6F5B3-D71VljSN.js} +1 -1
- package/dist/client/assets/{chunk-ICPOFSXX-B_MThwG6.js → chunk-ICPOFSXX-2vcQKuhB.js} +2 -2
- package/dist/client/assets/{chunk-K5T4RW27-DmamW1Ds.js → chunk-K5T4RW27-BWIFd7pZ.js} +10 -10
- package/dist/client/assets/{chunk-KGLVRYIC-CRbg4c4z.js → chunk-KGLVRYIC-Ck8I8tdt.js} +1 -1
- package/dist/client/assets/{chunk-LIHQZDEY-CHQPSdB3.js → chunk-LIHQZDEY-Cc7TtI-w.js} +1 -1
- package/dist/client/assets/{chunk-ORNJ4GCN-CIsQ4Zi4.js → chunk-ORNJ4GCN-BMSqiphc.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-Cxi0kdGg.js → chunk-OYMX7WX6-B5faFb53.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-B-G9G-FB.js +1 -0
- package/dist/client/assets/{chunk-U2HBQHQK-V_hneCfR.js → chunk-U2HBQHQK-BILTfRyq.js} +1 -1
- package/dist/client/assets/{chunk-X2U36JSP-De4pvO-I.js → chunk-X2U36JSP-D4-56gWx.js} +1 -1
- package/dist/client/assets/{chunk-XPW4576I-B_osXKp6.js → chunk-XPW4576I-SxB401Zg.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-C_kqXssD.js → chunk-YZCP3GAM-CWXUVxFj.js} +1 -1
- package/dist/client/assets/{chunk-ZZ45TVLE-B_xtlma5.js → chunk-ZZ45TVLE-CXjZua4f.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DnUQ2iGN.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-Dwp5vuOB.js +1 -0
- package/dist/client/assets/clone-aWrl-obY.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-YToNpueF.js +1 -0
- package/dist/client/assets/{cytoscape.esm-DRReFUEO.js → cytoscape.esm-DdcHPZAZ.js} +2 -2
- package/dist/client/assets/{dagre-KV5264BT-BWYGReXF.js → dagre-KV5264BT-QFYoTa0z.js} +1 -1
- package/dist/client/assets/{dagre-DU-XBdcU.js → dagre-tvaMpP4D.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-DpUUhvWz.js → diagram-5BDNPKRD-DM0NNmEN.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-BJoTrUAx.js → diagram-G4DWMVQ6-TiLkMmwt.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-CAk1GW5g.js → diagram-MMDJMWI5-DM1ykqrB.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-Cct6g7FA.js → diagram-TYMM5635-BEOLX1wr.js} +1 -1
- package/dist/client/assets/{dist-61sCfOmN.js → dist-CCBhd9az.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-DHs2bXUj.js → erDiagram-SMLLAGMA-DZcjZq6z.js} +1 -1
- package/dist/client/assets/{flatten-mnWyE-RB.js → flatten-SRIRKgqP.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-DLu-6dfC.js → flowDiagram-DWJPFMVM-B1AVT9es.js} +1 -1
- package/dist/client/assets/ganttDiagram-T4ZO3ILL-BCEXws9V.js +292 -0
- package/dist/client/assets/gitGraph-7Q5UKJZL-BE3Mcr-v.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-Bc_rL3_k.js → gitGraphDiagram-UUTBAWPF-CVznBDOl.js} +1 -1
- package/dist/client/assets/{graphlib-BVMK0xYE.js → graphlib-C4fWcyt1.js} +1 -1
- package/dist/client/assets/index-6LShOAAb.js +79 -0
- package/dist/client/assets/index-C16wNcPQ.css +2 -0
- package/dist/client/assets/info-OMHHGYJF-CBpXVhw-.js +1 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-Cf8u4jgP.js → infoDiagram-42DDH7IO-D8Oxr-KJ.js} +1 -1
- package/dist/client/assets/{isEmpty-CiiIHfXR.js → isEmpty-CStpjy4G.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-7n7DvfEb.js → ishikawaDiagram-UXIWVN3A-BE9KniVE.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-BMkeQqJb.js → journeyDiagram-VCZTEJTY-B3lGcz06.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-B8KkeZLS.js → kanban-definition-6JOO6SKY-Bs1QdB0j.js} +1 -1
- package/dist/client/assets/{line-CVpcI6kj.js → line-CO4-KhEq.js} +1 -1
- package/dist/client/assets/{linear-DmhiOOKU.js → linear-CnaJKs0I.js} +1 -1
- package/dist/client/assets/mermaid-parser.core-CravK6bS.js +4 -0
- package/dist/client/assets/{mermaid.core-R7nXpPx-.js → mermaid.core-DTh9KJvF.js} +3 -3
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-CwcHocMZ.js → mindmap-definition-QFDTVHPH-D2xU2hfX.js} +1 -1
- package/dist/client/assets/{ordinal-k--hYEme.js → ordinal-DIg8h6NI.js} +1 -1
- package/dist/client/assets/packet-4T2RLAQJ-abaJ3V5T.js +1 -0
- package/dist/client/assets/pie-ZZUOXDRM-B12dpA7V.js +1 -0
- package/dist/client/assets/{pieDiagram-DEJITSTG-BVAn8Lmr.js → pieDiagram-DEJITSTG-CRX6y4IQ.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-C2XZ_zxa.js → quadrantDiagram-34T5L4WZ-K2HFp8O8.js} +1 -1
- package/dist/client/assets/radar-PYXPWWZC-BbBaJJN8.js +1 -0
- package/dist/client/assets/{reduce-BTlHjXna.js → reduce-CG4cgj93.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-CfO16pkI.js → requirementDiagram-MS252O5E-C-8AW0uI.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-D_4_234M.js → sankeyDiagram-XADWPNL6-Bv-_ZFS5.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-B-yHKMuK.js → sequenceDiagram-FGHM5R23-Bk4QYIPk.js} +1 -1
- package/dist/client/assets/src-XMuEuFcU.js +1 -0
- package/dist/client/assets/{stateDiagram-FHFEXIEX-BeG2di4I.js → stateDiagram-FHFEXIEX-CI1G7zGC.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DQ0U-oto.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-DhtnMGcE.js → timeline-definition-GMOUNBTQ-CnXv8xHg.js} +1 -1
- package/dist/client/assets/treeView-SZITEDCU-CM0rCBUc.js +1 -0
- package/dist/client/assets/treemap-W4RFUUIX-CXoNE_rL.js +1 -0
- package/dist/client/assets/{vennDiagram-DHZGUBPP-CBn69TcQ.js → vennDiagram-DHZGUBPP-M5x471Ar.js} +1 -1
- package/dist/client/assets/wardley-RL74JXVD-B_EtnvOk.js +1 -0
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-CEoSJmN1.js → wardleyDiagram-NUSXRM2D-BG99uPNN.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-BZ_X9tkn.js → xychartDiagram-5P7HB3ND-DO7Upr9G.js} +1 -1
- package/dist/client/index.html +2 -4
- package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
- package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
- package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/manifest.json +1 -0
- package/dist/client/site-data/og-image.png +0 -0
- package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
- package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
- package/dist/server/file-watcher.d.ts +2 -1
- package/dist/server/file-watcher.js +9 -3
- package/dist/server/git-diff.d.ts +5 -0
- package/dist/server/git-diff.js +65 -1
- package/dist/server/git-diff.test.js +50 -0
- package/dist/server/server.js +265 -68
- package/dist/server/server.test.js +228 -0
- package/dist/tui/App.js +0 -1
- package/dist/types/diff.d.ts +15 -4
- package/dist/types/watch.d.ts +30 -1
- package/dist/utils/commentImports.d.ts +2 -0
- package/dist/utils/commentImports.js +119 -1
- package/dist/utils/editorOptions.d.ts +58 -35
- package/dist/utils/editorOptions.js +150 -24
- package/dist/utils/editorOptions.test.js +201 -9
- package/package.json +7 -4
- package/dist/client/assets/architecture-YZFGNWBL-Cs2Q6RQP.js +0 -1
- package/dist/client/assets/architectureDiagram-Q4EWVU46-BO4dVPUA.js +0 -36
- package/dist/client/assets/channel-_xDT1u3-.js +0 -1
- package/dist/client/assets/chunk-336JU56O-D1qa7Qzb.js +0 -2
- package/dist/client/assets/chunk-426QAEUC-6J_A_wvD.js +0 -1
- package/dist/client/assets/chunk-CFjPhJqf.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-C0QzfgZ8.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-5XrS-DAQ.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-Covl2vKy.js +0 -1
- package/dist/client/assets/clone-rhRH8pyW.js +0 -1
- package/dist/client/assets/cose-bilkent-S5V4N54A-BvXFc7Rr.js +0 -1
- package/dist/client/assets/ganttDiagram-T4ZO3ILL-CMIzlKAR.js +0 -292
- package/dist/client/assets/gitGraph-7Q5UKJZL-A_wWsXju.js +0 -1
- package/dist/client/assets/index-BPoqJmrs.js +0 -79
- package/dist/client/assets/index-Cq_APK7Y.css +0 -2
- package/dist/client/assets/info-OMHHGYJF-Bv3kK2Bb.js +0 -1
- package/dist/client/assets/mermaid-parser.core-CnJ9Tv8l.js +0 -4
- package/dist/client/assets/packet-4T2RLAQJ-D2q3-9ae.js +0 -1
- package/dist/client/assets/pie-ZZUOXDRM-GivlQcUF.js +0 -1
- package/dist/client/assets/preload-helper-DSXbuxSR.js +0 -1
- package/dist/client/assets/radar-PYXPWWZC-C9pD6VNR.js +0 -1
- package/dist/client/assets/src-CjDs0_Ij.js +0 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DvcSq7KE.js +0 -1
- package/dist/client/assets/treeView-SZITEDCU-BSNk8_yV.js +0 -1
- package/dist/client/assets/treemap-W4RFUUIX-ym4zQztE.js +0 -1
- package/dist/client/assets/wardley-RL74JXVD-B02H6ReJ.js +0 -1
- /package/dist/client/assets/{array-BNor45A1.js → array-DOVTz2Mq.js} +0 -0
- /package/dist/client/assets/{defaultLocale-DPzUsThw.js → defaultLocale-Ck2Xxk-C.js} +0 -0
- /package/dist/client/assets/{init-C0L3woqb.js → init-Bft5Ffpj.js} +0 -0
- /package/dist/client/assets/{katex-FOM3xZj7.js → katex-CeIlAR55.js} +0 -0
- /package/dist/client/assets/{path-sMK4d_s9.js → path-DfRbCp9y.js} +0 -0
- /package/dist/client/assets/{prism-bash-iQBez6et.js → prism-bash-CPkZUJMA.js} +0 -0
- /package/dist/client/assets/{prism-clojure-CTkJ-FW_.js → prism-clojure-BpoF2XhX.js} +0 -0
- /package/dist/client/assets/{prism-csharp-DAAROvjt.js → prism-csharp-BEk8D1-3.js} +0 -0
- /package/dist/client/assets/{prism-dart-CMjMHaBW.js → prism-dart-ByLYrdQB.js} +0 -0
- /package/dist/client/assets/{prism-elixir-B9cwzXs0.js → prism-elixir-BZtyIEab.js} +0 -0
- /package/dist/client/assets/{prism-haskell-Vgx7BCAm.js → prism-haskell-NAsbeo3V.js} +0 -0
- /package/dist/client/assets/{prism-hcl-Du4YC80h.js → prism-hcl-crnGqmVp.js} +0 -0
- /package/dist/client/assets/{prism-java-CWuFbfVD.js → prism-java-BovStacA.js} +0 -0
- /package/dist/client/assets/{prism-markup-templating-h9TC-ifW.js → prism-markup-templating-Cl8NiLjy.js} +0 -0
- /package/dist/client/assets/{prism-nix-CqauNIYa.js → prism-nix-BS_cm_1n.js} +0 -0
- /package/dist/client/assets/{prism-perl-DhcRwJzx.js → prism-perl-DGLVMq5H.js} +0 -0
- /package/dist/client/assets/{prism-php-DcBIrISj.js → prism-php-BskSwJN8.js} +0 -0
- /package/dist/client/assets/{prism-protobuf-DuPg7Jbg.js → prism-protobuf-DfbIYpO7.js} +0 -0
- /package/dist/client/assets/{prism-ruby-lhDmuasn.js → prism-ruby-FBVh1PRE.js} +0 -0
- /package/dist/client/assets/{prism-scala-YlPat9I4.js → prism-scala--9AfMHPY.js} +0 -0
- /package/dist/client/assets/{prism-solidity-C3nR0EVH.js → prism-solidity-BgJNkj1z.js} +0 -0
- /package/dist/client/assets/{prism-sql-Cz-8DmQS.js → prism-sql-C9Czmpov.js} +0 -0
- /package/dist/client/assets/{prism-vim-C3oukvmk.js → prism-vim-CzUNf0WQ.js} +0 -0
- /package/dist/client/assets/{rough.esm-DeLgKbOI.js → rough.esm-Bbn_-PMU.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Response } from 'express';
|
|
2
|
-
import { DiffMode } from '../types/watch.js';
|
|
2
|
+
import { DiffMode, type WatchEvent } from '../types/watch.js';
|
|
3
3
|
export declare class FileWatcherService {
|
|
4
4
|
private subscriptions;
|
|
5
5
|
private clients;
|
|
@@ -21,6 +21,7 @@ export declare class FileWatcherService {
|
|
|
21
21
|
stop(): Promise<void>;
|
|
22
22
|
addClient(res: Response): void;
|
|
23
23
|
removeClient(res: Response): void;
|
|
24
|
+
broadcast(event: WatchEvent): void;
|
|
24
25
|
private broadcastChange;
|
|
25
26
|
private sendToClient;
|
|
26
27
|
private determineChangeType;
|
|
@@ -198,6 +198,14 @@ export class FileWatcherService {
|
|
|
198
198
|
this.clients.splice(index, 1);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
broadcast(event) {
|
|
202
|
+
if (this.clients.length === 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.clients.forEach((client) => {
|
|
206
|
+
this.sendToClient(client, event);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
201
209
|
broadcastChange() {
|
|
202
210
|
if (this.clients.length === 0 || !this.config) {
|
|
203
211
|
return;
|
|
@@ -210,9 +218,7 @@ export class FileWatcherService {
|
|
|
210
218
|
timestamp: new Date().toISOString(),
|
|
211
219
|
message: `Changes detected in ${this.config.diffMode} mode`,
|
|
212
220
|
};
|
|
213
|
-
this.
|
|
214
|
-
this.sendToClient(client, event);
|
|
215
|
-
});
|
|
221
|
+
this.broadcast(event);
|
|
216
222
|
}
|
|
217
223
|
sendToClient(client, event) {
|
|
218
224
|
try {
|
|
@@ -5,11 +5,16 @@ export declare class GitDiffParser {
|
|
|
5
5
|
private readonly resolvedCommitCache;
|
|
6
6
|
private static readonly RESOLVED_COMMIT_CACHE_TTL_MS;
|
|
7
7
|
private static readonly GENERATED_HEADER_SCAN_BYTES;
|
|
8
|
+
private static readonly GITATTRIBUTES_CHECK_CHUNK_SIZE;
|
|
8
9
|
constructor(repoPath?: string);
|
|
9
10
|
private normalizeRepositoryRelativePath;
|
|
10
11
|
private resolveBaseCommitish;
|
|
11
12
|
parseDiff(selection: DiffSelection, ignoreWhitespace?: boolean, contextLines?: number): Promise<DiffResponse>;
|
|
12
13
|
private parseUnifiedDiff;
|
|
14
|
+
private getGitattributesSourceArgs;
|
|
15
|
+
private parseGitattributesGeneratedOutput;
|
|
16
|
+
private getGitattributesGeneratedPaths;
|
|
17
|
+
private markGitattributesGeneratedFiles;
|
|
13
18
|
private decodeGitPath;
|
|
14
19
|
private extractPathFromLine;
|
|
15
20
|
private parseDiffHeaderPaths;
|
package/dist/server/git-diff.js
CHANGED
|
@@ -9,6 +9,7 @@ export class GitDiffParser {
|
|
|
9
9
|
resolvedCommitCache = new Map();
|
|
10
10
|
static RESOLVED_COMMIT_CACHE_TTL_MS = 5_000;
|
|
11
11
|
static GENERATED_HEADER_SCAN_BYTES = 4 * 1024;
|
|
12
|
+
static GITATTRIBUTES_CHECK_CHUNK_SIZE = 200;
|
|
12
13
|
constructor(repoPath = process.cwd()) {
|
|
13
14
|
this.repoPath = repoPath;
|
|
14
15
|
this.git = simpleGit(repoPath);
|
|
@@ -51,6 +52,7 @@ export class GitDiffParser {
|
|
|
51
52
|
let diffArgs;
|
|
52
53
|
let resolvedBaseCommitish = effectiveBaseCommitish;
|
|
53
54
|
let resolvedTargetCommitish = targetCommitish;
|
|
55
|
+
let attributesRef = targetCommitish;
|
|
54
56
|
// Handle target special chars (base is always a regular commit)
|
|
55
57
|
if (targetCommitish === 'working') {
|
|
56
58
|
// Show unstaged changes (working vs staged)
|
|
@@ -78,6 +80,7 @@ export class GitDiffParser {
|
|
|
78
80
|
resolvedCommit = createCommitRangeString(shortHash(baseHash), shortHash(targetHash));
|
|
79
81
|
resolvedBaseCommitish = shortHash(baseHash);
|
|
80
82
|
resolvedTargetCommitish = shortHash(targetHash);
|
|
83
|
+
attributesRef = targetHash;
|
|
81
84
|
diffArgs = [baseHash, targetHash];
|
|
82
85
|
}
|
|
83
86
|
if (ignoreWhitespace) {
|
|
@@ -91,7 +94,7 @@ export class GitDiffParser {
|
|
|
91
94
|
diffArgs.push('--no-ext-diff', '--color=never');
|
|
92
95
|
// Single git invocation for better startup latency on large repositories.
|
|
93
96
|
const diffRaw = await this.git.diff(diffArgs);
|
|
94
|
-
const files = this.parseUnifiedDiff(diffRaw);
|
|
97
|
+
const files = await this.markGitattributesGeneratedFiles(this.parseUnifiedDiff(diffRaw), attributesRef);
|
|
95
98
|
return {
|
|
96
99
|
commit: resolvedCommit,
|
|
97
100
|
files,
|
|
@@ -119,6 +122,63 @@ export class GitDiffParser {
|
|
|
119
122
|
}
|
|
120
123
|
return files;
|
|
121
124
|
}
|
|
125
|
+
getGitattributesSourceArgs(ref) {
|
|
126
|
+
if (ref === 'working' || ref === '.') {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
if (ref === 'staged') {
|
|
130
|
+
return ['--cached'];
|
|
131
|
+
}
|
|
132
|
+
return ['--source', ref];
|
|
133
|
+
}
|
|
134
|
+
parseGitattributesGeneratedOutput(output) {
|
|
135
|
+
const generatedPaths = new Set();
|
|
136
|
+
const fields = output.split('\0');
|
|
137
|
+
for (let i = 0; i + 2 < fields.length; i += 3) {
|
|
138
|
+
const [path, attribute, value] = fields.slice(i, i + 3);
|
|
139
|
+
if (attribute === 'linguist-generated' && value === 'true') {
|
|
140
|
+
generatedPaths.add(path);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return generatedPaths;
|
|
144
|
+
}
|
|
145
|
+
async getGitattributesGeneratedPaths(filepaths, ref) {
|
|
146
|
+
if (filepaths.length === 0) {
|
|
147
|
+
return new Set();
|
|
148
|
+
}
|
|
149
|
+
const generatedPaths = new Set();
|
|
150
|
+
try {
|
|
151
|
+
for (let i = 0; i < filepaths.length; i += GitDiffParser.GITATTRIBUTES_CHECK_CHUNK_SIZE) {
|
|
152
|
+
const chunk = filepaths.slice(i, i + GitDiffParser.GITATTRIBUTES_CHECK_CHUNK_SIZE);
|
|
153
|
+
const output = await this.git.raw([
|
|
154
|
+
'check-attr',
|
|
155
|
+
'-z',
|
|
156
|
+
...this.getGitattributesSourceArgs(ref),
|
|
157
|
+
'linguist-generated',
|
|
158
|
+
'--',
|
|
159
|
+
...chunk,
|
|
160
|
+
]);
|
|
161
|
+
if (typeof output !== 'string') {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
for (const path of this.parseGitattributesGeneratedOutput(output)) {
|
|
165
|
+
generatedPaths.add(path);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return generatedPaths;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return new Set();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async markGitattributesGeneratedFiles(files, ref) {
|
|
175
|
+
const candidates = files.filter((file) => !file.isGenerated).map((file) => file.path);
|
|
176
|
+
const generatedPaths = await this.getGitattributesGeneratedPaths(candidates, ref);
|
|
177
|
+
if (generatedPaths.size === 0) {
|
|
178
|
+
return files;
|
|
179
|
+
}
|
|
180
|
+
return files.map((file) => generatedPaths.has(file.path) ? { ...file, isGenerated: true } : file);
|
|
181
|
+
}
|
|
122
182
|
decodeGitPath(rawPath) {
|
|
123
183
|
if (typeof rawPath !== 'string') {
|
|
124
184
|
return undefined;
|
|
@@ -454,6 +514,10 @@ export class GitDiffParser {
|
|
|
454
514
|
if (pathResult.isGenerated) {
|
|
455
515
|
return { isGenerated: true, source: 'path' };
|
|
456
516
|
}
|
|
517
|
+
const gitattributesResult = await this.getGitattributesGeneratedPaths([filepath], ref);
|
|
518
|
+
if (gitattributesResult.has(filepath)) {
|
|
519
|
+
return { isGenerated: true, source: 'path' };
|
|
520
|
+
}
|
|
457
521
|
try {
|
|
458
522
|
const buffer = await this.getBlobContent(filepath, ref);
|
|
459
523
|
const lines = this.extractHeaderLines(buffer);
|
|
@@ -985,6 +985,23 @@ index abc123..def456 100644
|
|
|
985
985
|
expect(generatedStatus).toEqual({ isGenerated: true, source: 'path' });
|
|
986
986
|
expect(getBlobContentSpy).not.toHaveBeenCalled();
|
|
987
987
|
});
|
|
988
|
+
it('returns source=path for .gitattributes linguist-generated files without reading content', async () => {
|
|
989
|
+
const gitRaw = parser.git.raw;
|
|
990
|
+
gitRaw.mockResolvedValue('apps/app/web/src/api/index.tsx\0linguist-generated\0true\0');
|
|
991
|
+
const getBlobContentSpy = vi.spyOn(parser, 'getBlobContent');
|
|
992
|
+
const generatedStatus = await parser.getGeneratedStatus('apps/app/web/src/api/index.tsx', 'HEAD');
|
|
993
|
+
expect(gitRaw).toHaveBeenCalledWith([
|
|
994
|
+
'check-attr',
|
|
995
|
+
'-z',
|
|
996
|
+
'--source',
|
|
997
|
+
'HEAD',
|
|
998
|
+
'linguist-generated',
|
|
999
|
+
'--',
|
|
1000
|
+
'apps/app/web/src/api/index.tsx',
|
|
1001
|
+
]);
|
|
1002
|
+
expect(generatedStatus).toEqual({ isGenerated: true, source: 'path' });
|
|
1003
|
+
expect(getBlobContentSpy).not.toHaveBeenCalled();
|
|
1004
|
+
});
|
|
988
1005
|
it('returns false when content cannot be read for content-based generated detection', async () => {
|
|
989
1006
|
const getBlobContentSpy = vi.spyOn(parser, 'getBlobContent');
|
|
990
1007
|
getBlobContentSpy.mockRejectedValue(new Error('missing blob'));
|
|
@@ -1075,6 +1092,39 @@ index abc123..def456 100644
|
|
|
1075
1092
|
});
|
|
1076
1093
|
});
|
|
1077
1094
|
describe('parseDiff', () => {
|
|
1095
|
+
it('marks files with .gitattributes linguist-generated=true as generated', async () => {
|
|
1096
|
+
const file = 'apps/app/web/src/api/index.tsx';
|
|
1097
|
+
const gitDiff = parser.git.diff;
|
|
1098
|
+
const gitRevparse = parser.git.revparse;
|
|
1099
|
+
const gitRaw = parser.git.raw;
|
|
1100
|
+
gitRevparse
|
|
1101
|
+
.mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
|
|
1102
|
+
.mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
|
|
1103
|
+
gitDiff.mockResolvedValue([
|
|
1104
|
+
`diff --git a/${file} b/${file}`,
|
|
1105
|
+
`index abc123..def456 100644`,
|
|
1106
|
+
`--- a/${file}`,
|
|
1107
|
+
`+++ b/${file}`,
|
|
1108
|
+
`@@ -1 +1 @@`,
|
|
1109
|
+
`-old`,
|
|
1110
|
+
`+new`,
|
|
1111
|
+
].join('\n'));
|
|
1112
|
+
gitRaw.mockResolvedValue(`${file}\0linguist-generated\0true\0`);
|
|
1113
|
+
const response = await parser.parseDiff({
|
|
1114
|
+
targetCommitish: 'HEAD',
|
|
1115
|
+
baseCommitish: 'HEAD~1',
|
|
1116
|
+
});
|
|
1117
|
+
expect(gitRaw).toHaveBeenCalledWith([
|
|
1118
|
+
'check-attr',
|
|
1119
|
+
'-z',
|
|
1120
|
+
'--source',
|
|
1121
|
+
'1234567890abcdef1234567890abcdef12345678',
|
|
1122
|
+
'linguist-generated',
|
|
1123
|
+
'--',
|
|
1124
|
+
file,
|
|
1125
|
+
]);
|
|
1126
|
+
expect(response.files[0].isGenerated).toBe(true);
|
|
1127
|
+
});
|
|
1078
1128
|
it('passes context lines through to git diff', async () => {
|
|
1079
1129
|
const gitDiff = parser.git.diff;
|
|
1080
1130
|
const gitRevparse = parser.git.revparse;
|
package/dist/server/server.js
CHANGED
|
@@ -7,9 +7,9 @@ import open from 'open';
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
9
|
import { formatCommentsOutput } from '../utils/commentFormatting.js';
|
|
10
|
-
import { serializeCommentImports } from '../utils/commentImports.js';
|
|
10
|
+
import { mergeCommentImports, normalizeCommentImports, serializeCommentImports, } from '../utils/commentImports.js';
|
|
11
11
|
import { normalizeDiffViewMode } from '../utils/diffMode.js';
|
|
12
|
-
import { resolveEditorOption } from '../utils/editorOptions.js';
|
|
12
|
+
import { buildEditorSpawnSpec, CUSTOM_EDITOR_ID, NONE_EDITOR_ID, resolveEditorOption, } from '../utils/editorOptions.js';
|
|
13
13
|
import { getFileExtension } from '../utils/fileUtils.js';
|
|
14
14
|
import { FileWatcherService } from './file-watcher.js';
|
|
15
15
|
import { GitDiffParser } from './git-diff.js';
|
|
@@ -42,6 +42,15 @@ function setCachedDiffResponse(cache, key, value) {
|
|
|
42
42
|
cache.delete(oldestKey);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
function createResolvedCommentSelection(responseDiffData, fallbackSelection, stdinDiff) {
|
|
46
|
+
const baseCommitish = responseDiffData.baseCommitish ?? (stdinDiff ? 'stdin' : fallbackSelection.baseCommitish);
|
|
47
|
+
const targetCommitish = responseDiffData.targetCommitish ?? (stdinDiff ? 'stdin' : fallbackSelection.targetCommitish);
|
|
48
|
+
const baseMode = responseDiffData.requestedBaseMode ?? fallbackSelection.baseMode;
|
|
49
|
+
return createDiffSelection(baseCommitish, targetCommitish, baseMode);
|
|
50
|
+
}
|
|
51
|
+
function createCommentSessionKey(selection) {
|
|
52
|
+
return getDiffSelectionKey(selection);
|
|
53
|
+
}
|
|
45
54
|
export async function startServer(options) {
|
|
46
55
|
const app = express();
|
|
47
56
|
const repositoryPath = resolve(options.repoPath ?? process.cwd());
|
|
@@ -96,6 +105,7 @@ export async function startServer(options) {
|
|
|
96
105
|
};
|
|
97
106
|
// Track current revisions for cache invalidation
|
|
98
107
|
let currentSelection = initialSelection;
|
|
108
|
+
let currentCommentSelection = createResolvedCommentSelection(initialDiffData, initialSelection, Boolean(options.stdinDiff));
|
|
99
109
|
function parseRepositoryRelativePath(filepath) {
|
|
100
110
|
if (typeof filepath !== 'string' || filepath.length === 0) {
|
|
101
111
|
return { ok: false, error: 'Invalid file path' };
|
|
@@ -111,6 +121,51 @@ export async function startServer(options) {
|
|
|
111
121
|
}
|
|
112
122
|
return { ok: true, path: normalizedFilepath };
|
|
113
123
|
}
|
|
124
|
+
function parseEditorRequest(value) {
|
|
125
|
+
if (!value || typeof value !== 'object') {
|
|
126
|
+
return { id: undefined, command: undefined, argsTemplate: undefined };
|
|
127
|
+
}
|
|
128
|
+
const candidate = value;
|
|
129
|
+
return {
|
|
130
|
+
id: typeof candidate.id === 'string' ? candidate.id : undefined,
|
|
131
|
+
command: typeof candidate.command === 'string' ? candidate.command : undefined,
|
|
132
|
+
argsTemplate: typeof candidate.argsTemplate === 'string' ? candidate.argsTemplate : undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const commentSessions = new Map();
|
|
136
|
+
const initialCommentThreads = mergeCommentImports([], initialCommentImports).threads;
|
|
137
|
+
if (initialCommentThreads.length > 0) {
|
|
138
|
+
commentSessions.set(createCommentSessionKey(currentCommentSelection), {
|
|
139
|
+
threads: initialCommentThreads,
|
|
140
|
+
version: 1,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function getCommentSelectionFromQuery(query) {
|
|
144
|
+
const hasBase = typeof query.base === 'string';
|
|
145
|
+
const hasTarget = typeof query.target === 'string';
|
|
146
|
+
const hasBaseMode = typeof query.baseMode === 'string';
|
|
147
|
+
if (!hasBase && !hasTarget && !hasBaseMode) {
|
|
148
|
+
return currentCommentSelection;
|
|
149
|
+
}
|
|
150
|
+
return createDiffSelection(hasBase ? query.base : currentCommentSelection.baseCommitish, hasTarget ? query.target : currentCommentSelection.targetCommitish, hasBaseMode
|
|
151
|
+
? parseBaseMode(query.baseMode)
|
|
152
|
+
: hasBase || hasTarget
|
|
153
|
+
? undefined
|
|
154
|
+
: currentCommentSelection.baseMode);
|
|
155
|
+
}
|
|
156
|
+
function getOrCreateCommentSession(selection) {
|
|
157
|
+
const key = createCommentSessionKey(selection);
|
|
158
|
+
const existing = commentSessions.get(key);
|
|
159
|
+
if (existing) {
|
|
160
|
+
return existing;
|
|
161
|
+
}
|
|
162
|
+
const nextSession = {
|
|
163
|
+
threads: [],
|
|
164
|
+
version: 0,
|
|
165
|
+
};
|
|
166
|
+
commentSessions.set(key, nextSession);
|
|
167
|
+
return nextSession;
|
|
168
|
+
}
|
|
114
169
|
app.get('/api/diff', async (req, res) => {
|
|
115
170
|
const ignoreWhitespace = req.query.ignoreWhitespace === 'true';
|
|
116
171
|
const hasBase = typeof req.query.base === 'string';
|
|
@@ -137,6 +192,7 @@ export async function startServer(options) {
|
|
|
137
192
|
generatedStatusCache.clear();
|
|
138
193
|
}
|
|
139
194
|
}
|
|
195
|
+
currentCommentSelection = createResolvedCommentSelection(responseDiffData, requestedSelection, Boolean(options.stdinDiff));
|
|
140
196
|
const baseCommitish = responseDiffData.baseCommitish ?? (options.stdinDiff ? 'stdin' : undefined);
|
|
141
197
|
const targetCommitish = responseDiffData.targetCommitish ?? (options.stdinDiff ? 'stdin' : undefined);
|
|
142
198
|
const requestedBaseCommitish = responseDiffData.requestedBaseCommitish ??
|
|
@@ -314,31 +370,58 @@ export async function startServer(options) {
|
|
|
314
370
|
res.status(404).json({ error: 'File not found' });
|
|
315
371
|
}
|
|
316
372
|
});
|
|
317
|
-
|
|
373
|
+
function normalizeLineValue(line) {
|
|
374
|
+
if (Array.isArray(line) && line.length === 2) {
|
|
375
|
+
const start = line[0];
|
|
376
|
+
const end = line[1];
|
|
377
|
+
if (typeof start === 'number' &&
|
|
378
|
+
typeof end === 'number' &&
|
|
379
|
+
Number.isInteger(start) &&
|
|
380
|
+
Number.isInteger(end) &&
|
|
381
|
+
start > 0 &&
|
|
382
|
+
end > 0 &&
|
|
383
|
+
start <= end) {
|
|
384
|
+
return { start, end };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (typeof line === 'number' && Number.isInteger(line) && line > 0) {
|
|
388
|
+
return line;
|
|
389
|
+
}
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
318
392
|
function normalizeComment(comment) {
|
|
393
|
+
const now = new Date().toISOString();
|
|
394
|
+
const timestamp = typeof comment.timestamp === 'string' ? comment.timestamp : now;
|
|
395
|
+
const threadId = typeof comment.id === 'string' && comment.id.length > 0
|
|
396
|
+
? comment.id
|
|
397
|
+
: createHash('sha256').update(JSON.stringify(comment)).digest('hex').slice(0, 12);
|
|
398
|
+
const filePath = typeof comment.file === 'string' && comment.file.length > 0 ? comment.file : '<unknown file>';
|
|
319
399
|
return {
|
|
320
|
-
id:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
400
|
+
id: threadId,
|
|
401
|
+
filePath,
|
|
402
|
+
createdAt: timestamp,
|
|
403
|
+
updatedAt: timestamp,
|
|
404
|
+
position: {
|
|
405
|
+
side: comment.side ?? 'new',
|
|
406
|
+
line: normalizeLineValue(comment.line),
|
|
407
|
+
},
|
|
408
|
+
codeSnapshot: typeof comment.codeContent === 'string'
|
|
409
|
+
? {
|
|
410
|
+
content: comment.codeContent,
|
|
411
|
+
}
|
|
412
|
+
: undefined,
|
|
327
413
|
messages: [
|
|
328
414
|
{
|
|
329
|
-
id:
|
|
415
|
+
id: threadId,
|
|
330
416
|
body: comment.body,
|
|
331
417
|
author: comment.author,
|
|
332
|
-
createdAt:
|
|
333
|
-
updatedAt:
|
|
418
|
+
createdAt: timestamp,
|
|
419
|
+
updatedAt: timestamp,
|
|
334
420
|
},
|
|
335
421
|
],
|
|
336
422
|
};
|
|
337
423
|
}
|
|
338
|
-
function
|
|
339
|
-
if ('file' in thread && 'line' in thread) {
|
|
340
|
-
return thread;
|
|
341
|
-
}
|
|
424
|
+
function toCommentThread(thread) {
|
|
342
425
|
return {
|
|
343
426
|
id: thread.id,
|
|
344
427
|
file: thread.filePath,
|
|
@@ -352,6 +435,51 @@ export async function startServer(options) {
|
|
|
352
435
|
messages: thread.messages,
|
|
353
436
|
};
|
|
354
437
|
}
|
|
438
|
+
function normalizeThreadPayload(thread) {
|
|
439
|
+
if ('filePath' in thread && 'position' in thread) {
|
|
440
|
+
return thread;
|
|
441
|
+
}
|
|
442
|
+
const threadId = typeof thread.id === 'string' && thread.id.length > 0
|
|
443
|
+
? thread.id
|
|
444
|
+
: createHash('sha256').update(JSON.stringify(thread)).digest('hex').slice(0, 12);
|
|
445
|
+
const now = new Date().toISOString();
|
|
446
|
+
const messages = Array.isArray(thread.messages) && thread.messages.length > 0
|
|
447
|
+
? thread.messages.map((message, index) => ({
|
|
448
|
+
id: typeof message.id === 'string' && message.id.length > 0
|
|
449
|
+
? message.id
|
|
450
|
+
: `${threadId}:${index}`,
|
|
451
|
+
body: message.body,
|
|
452
|
+
author: message.author,
|
|
453
|
+
createdAt: message.createdAt || thread.createdAt || now,
|
|
454
|
+
updatedAt: message.updatedAt || message.createdAt || thread.updatedAt || now,
|
|
455
|
+
}))
|
|
456
|
+
: [
|
|
457
|
+
{
|
|
458
|
+
id: threadId,
|
|
459
|
+
body: '',
|
|
460
|
+
createdAt: thread.createdAt || now,
|
|
461
|
+
updatedAt: thread.updatedAt || thread.createdAt || now,
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
const firstMessage = messages[0];
|
|
465
|
+
const lastMessage = messages[messages.length - 1];
|
|
466
|
+
return {
|
|
467
|
+
id: threadId,
|
|
468
|
+
filePath: typeof thread.file === 'string' && thread.file.length > 0 ? thread.file : '<unknown file>',
|
|
469
|
+
createdAt: thread.createdAt || firstMessage?.createdAt || now,
|
|
470
|
+
updatedAt: thread.updatedAt || lastMessage?.updatedAt || thread.createdAt || now,
|
|
471
|
+
position: {
|
|
472
|
+
side: thread.side ?? 'new',
|
|
473
|
+
line: normalizeLineValue(thread.line),
|
|
474
|
+
},
|
|
475
|
+
codeSnapshot: typeof thread.codeContent === 'string'
|
|
476
|
+
? {
|
|
477
|
+
content: thread.codeContent,
|
|
478
|
+
}
|
|
479
|
+
: undefined,
|
|
480
|
+
messages,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
355
483
|
function parseCommentsPayload(body) {
|
|
356
484
|
const payload = typeof body === 'string'
|
|
357
485
|
? JSON.parse(body)
|
|
@@ -364,9 +492,33 @@ export async function startServer(options) {
|
|
|
364
492
|
}
|
|
365
493
|
return [];
|
|
366
494
|
}
|
|
495
|
+
function parseCommentImportsPayload(body) {
|
|
496
|
+
if (typeof body === 'string') {
|
|
497
|
+
return normalizeCommentImports(JSON.parse(body));
|
|
498
|
+
}
|
|
499
|
+
return normalizeCommentImports(body);
|
|
500
|
+
}
|
|
501
|
+
function updateCommentSession(selection, nextThreads) {
|
|
502
|
+
const session = getOrCreateCommentSession(selection);
|
|
503
|
+
const previous = JSON.stringify(session.threads);
|
|
504
|
+
const next = JSON.stringify(nextThreads);
|
|
505
|
+
session.threads = nextThreads;
|
|
506
|
+
if (previous === next) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
session.version += 1;
|
|
510
|
+
fileWatcher.broadcast({
|
|
511
|
+
type: 'commentsChanged',
|
|
512
|
+
version: session.version,
|
|
513
|
+
timestamp: new Date().toISOString(),
|
|
514
|
+
});
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
367
517
|
app.post('/api/comments', (req, res) => {
|
|
368
518
|
try {
|
|
369
|
-
|
|
519
|
+
const selection = getCommentSelectionFromQuery(req.query);
|
|
520
|
+
const nextThreads = parseCommentsPayload(req.body);
|
|
521
|
+
updateCommentSession(selection, nextThreads);
|
|
370
522
|
res.json({ success: true });
|
|
371
523
|
}
|
|
372
524
|
catch (error) {
|
|
@@ -374,10 +526,43 @@ export async function startServer(options) {
|
|
|
374
526
|
res.status(400).json({ error: 'Invalid comment data' });
|
|
375
527
|
}
|
|
376
528
|
});
|
|
377
|
-
app.
|
|
529
|
+
app.post('/api/comment-imports', (req, res) => {
|
|
530
|
+
try {
|
|
531
|
+
const selection = getCommentSelectionFromQuery(req.query);
|
|
532
|
+
const session = getOrCreateCommentSession(selection);
|
|
533
|
+
const commentImports = parseCommentImportsPayload(req.body);
|
|
534
|
+
const importId = createHash('sha256')
|
|
535
|
+
.update(serializeCommentImports(commentImports))
|
|
536
|
+
.digest('hex');
|
|
537
|
+
const merged = mergeCommentImports(session.threads, commentImports);
|
|
538
|
+
const changed = updateCommentSession(selection, merged.threads);
|
|
539
|
+
res.json({
|
|
540
|
+
success: true,
|
|
541
|
+
changed,
|
|
542
|
+
count: commentImports.length,
|
|
543
|
+
importId,
|
|
544
|
+
warnings: merged.warnings,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
console.error('Error parsing comment imports:', error);
|
|
549
|
+
res.status(400).json({ error: 'Invalid comment import data' });
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
app.get('/api/comments-json', (req, res) => {
|
|
553
|
+
const selection = getCommentSelectionFromQuery(req.query);
|
|
554
|
+
const session = getOrCreateCommentSession(selection);
|
|
555
|
+
res.json({
|
|
556
|
+
version: session.version,
|
|
557
|
+
threads: session.threads,
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
app.get('/api/comments-output', (req, res) => {
|
|
561
|
+
const selection = getCommentSelectionFromQuery(req.query);
|
|
562
|
+
const session = getOrCreateCommentSession(selection);
|
|
378
563
|
res.type('text/plain');
|
|
379
|
-
if (
|
|
380
|
-
const output = formatCommentsOutput(
|
|
564
|
+
if (session.threads.length > 0) {
|
|
565
|
+
const output = formatCommentsOutput(session.threads.map(toCommentThread));
|
|
381
566
|
res.send(output);
|
|
382
567
|
}
|
|
383
568
|
else {
|
|
@@ -400,67 +585,79 @@ export async function startServer(options) {
|
|
|
400
585
|
return;
|
|
401
586
|
}
|
|
402
587
|
const resolvedPath = resolve(repositoryPath, filepathResult.path);
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
588
|
+
const editorRequest = parseEditorRequest(editor);
|
|
589
|
+
const editorId = editorRequest.id ?? process.env.DIFIT_EDITOR ?? process.env.EDITOR ?? undefined;
|
|
590
|
+
if (editorId?.toLowerCase() === NONE_EDITOR_ID) {
|
|
406
591
|
res.status(400).json({ error: 'Open in editor is disabled' });
|
|
407
592
|
return;
|
|
408
593
|
}
|
|
594
|
+
// The browser always sends command + argsTemplate in the body, so we use
|
|
595
|
+
// those directly. We only fall back to the preset table when neither is
|
|
596
|
+
// provided (for example, when DIFIT_EDITOR is set and there's no body).
|
|
597
|
+
let command;
|
|
598
|
+
let argsTemplate;
|
|
599
|
+
if (editorRequest.command !== undefined || editorRequest.argsTemplate !== undefined) {
|
|
600
|
+
command = (editorRequest.command ?? '').trim();
|
|
601
|
+
argsTemplate = (editorRequest.argsTemplate ?? '').trim();
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
const preset = resolveEditorOption(editorId);
|
|
605
|
+
command = preset.command;
|
|
606
|
+
argsTemplate = preset.argsTemplate;
|
|
607
|
+
}
|
|
608
|
+
if (!command || !argsTemplate) {
|
|
609
|
+
const isCustom = editorId?.toLowerCase() === CUSTOM_EDITOR_ID;
|
|
610
|
+
res.status(400).json({
|
|
611
|
+
error: isCustom
|
|
612
|
+
? 'Custom editor is not configured. Set a command and arguments in Settings > System.'
|
|
613
|
+
: 'Open in editor is not configured',
|
|
614
|
+
});
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
409
617
|
const lineNumber = (() => {
|
|
410
618
|
const parsed = Number.parseInt(String(line ?? ''), 10);
|
|
411
619
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
412
620
|
})();
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
621
|
+
const spawnSpec = buildEditorSpawnSpec({
|
|
622
|
+
command,
|
|
623
|
+
argsTemplate,
|
|
624
|
+
filePath: resolvedPath,
|
|
625
|
+
lineNumber,
|
|
626
|
+
});
|
|
627
|
+
if (!spawnSpec) {
|
|
628
|
+
res.status(500).json({ error: 'Invalid editor configuration' });
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const launched = await new Promise((resolvePromise) => {
|
|
632
|
+
const child = spawn(spawnSpec.command, [...spawnSpec.args], {
|
|
633
|
+
stdio: 'ignore',
|
|
634
|
+
detached: true,
|
|
635
|
+
});
|
|
636
|
+
child.once('error', (error) => {
|
|
637
|
+
const code = error.code;
|
|
638
|
+
if (code && code !== 'ENOENT') {
|
|
639
|
+
console.error('Failed to launch editor CLI:', error);
|
|
424
640
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
console.error('Failed to launch editor CLI:', error);
|
|
436
|
-
}
|
|
437
|
-
resolvePromise(false);
|
|
438
|
-
});
|
|
439
|
-
child.once('spawn', () => {
|
|
440
|
-
child.unref();
|
|
441
|
-
resolvePromise(true);
|
|
442
|
-
});
|
|
641
|
+
resolvePromise(false);
|
|
642
|
+
});
|
|
643
|
+
child.once('spawn', () => {
|
|
644
|
+
child.unref();
|
|
645
|
+
resolvePromise(true);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
if (!launched) {
|
|
649
|
+
res.status(500).json({
|
|
650
|
+
error: `Failed to launch editor: command "${spawnSpec.command}" is not available on PATH`,
|
|
443
651
|
});
|
|
444
|
-
};
|
|
445
|
-
if (await tryOpenWithCli()) {
|
|
446
|
-
res.json({ success: true });
|
|
447
652
|
return;
|
|
448
653
|
}
|
|
449
|
-
|
|
450
|
-
const fileUri = `${resolvedEditor.protocol}://file${encodeURI(resolvedPath)}${lineSuffix}`;
|
|
451
|
-
try {
|
|
452
|
-
await open(fileUri);
|
|
453
|
-
res.json({ success: true });
|
|
454
|
-
}
|
|
455
|
-
catch (error) {
|
|
456
|
-
console.error('Failed to open file in editor:', error);
|
|
457
|
-
res.status(500).json({ error: 'Failed to open file in editor' });
|
|
458
|
-
}
|
|
654
|
+
res.json({ success: true });
|
|
459
655
|
});
|
|
460
656
|
// Function to output comments when server shuts down
|
|
461
657
|
function outputFinalComments() {
|
|
462
|
-
|
|
463
|
-
|
|
658
|
+
const session = getOrCreateCommentSession(currentCommentSelection);
|
|
659
|
+
if (session.threads.length > 0) {
|
|
660
|
+
console.log(formatCommentsOutput(session.threads.map(toCommentThread)));
|
|
464
661
|
}
|
|
465
662
|
}
|
|
466
663
|
// SSE endpoint for file watching
|