diffing 0.6.0 → 0.7.0
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 +138 -100
- package/dist/apply-suggestion-BAuyCYk6.mjs +54 -0
- package/dist/cli-agent-BkkRbKqq.mjs +513 -0
- package/dist/cli-agent-BlMtx1Tx.mjs +736 -0
- package/dist/cli-agent-C4Fb90GL.mjs +728 -0
- package/dist/cli-agent-ChALuNbw.mjs +841 -0
- package/dist/cli-agent-D71pq2ep.mjs +841 -0
- package/dist/cli-agent-qBT8clNM.mjs +841 -0
- package/dist/cli-gh-6qiblgj7.mjs +150 -0
- package/dist/cli-gh-D5OCnjNe.mjs +150 -0
- package/dist/cli-gh-DgqscIkg.mjs +147 -0
- package/dist/cli-gh-KjPnyST6.mjs +150 -0
- package/dist/cli.mjs +38 -66
- package/dist/client/assets/{angular-html-BotC7B4-.js → angular-html-Dh86jUvr.js} +1 -1
- package/dist/client/assets/{angular-ts-DQTHeTJm.js → angular-ts-Bh4wRJtT.js} +1 -1
- package/dist/client/assets/{apl-Bwz5Dpd_.js → apl-BSXBHduT.js} +1 -1
- package/dist/client/assets/{arc-D-TSgv-7.js → arc-CRQlhBrV.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DLe25t8t.js → architectureDiagram-3BPJPVTR-BXq3UvEv.js} +1 -1
- package/dist/client/assets/{astro-DjkoD2fR.js → astro-Bh00klUw.js} +1 -1
- package/dist/client/assets/{blade-Cap2uJIA.js → blade-CisA3zhm.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B-bihLYO.js → blockDiagram-GPEHLZMM-Bw-PTAJr.js} +1 -1
- package/dist/client/assets/{c-C7JoAATL.js → c-D-pHVWrf.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CoCcjXEC.js → c4Diagram-AAUBKEIU-C0XyiqC_.js} +1 -1
- package/dist/client/assets/channel-CDxgOu-3.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Sm_uQrGq.js → chunk-2J33WTMH-DekjZXNj.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-Ct83Sp4P.js → chunk-3OPIFGDE-BtROpcL8.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DrqChk1a.js → chunk-4BX2VUAB-C92XLTlx.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CodeFb9E.js → chunk-55IACEB6-CceUgh8N.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-CpEx4dmS.js → chunk-5ZQYHXKU-DSbYPAe7.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D3U6TUmk.js → chunk-727SXJPM-DVzUJa-9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Dr-_DyBy.js → chunk-AQP2D5EJ-6n3Z_IFa.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-Bnyml8mU.js → chunk-BSJP7CBP-BgVLnkxr.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-BJSFGTlk.js → chunk-CSCIHK7Q-CkGMXntp.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BqiwDIwx.js → chunk-FMBD7UC4-BM8lrLBo.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-D-H-HBRn.js → chunk-KSCS5N6A-Bm2pjMQg.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-CVMSAF7h.js → chunk-L5ZTLDWV-VL17ikXM.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-BQsF6HF3.js → chunk-LZXEDZCA-B629g67M.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-eoM20ySO.js → chunk-ND2GUHAM-CjZGwhKA.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-B1iU0i4a.js → chunk-NZK2D7GU-DGsLjESY.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-CcIPuHUC.js → chunk-O5CBEL6O-EyUrGY3l.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-DJCeMpP8.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-BbuOkMaH.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-jSj3E63-.js → chunk-XPW4576I-C2TeAQmQ.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-D5oJbiED.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-5MJt5ZxQ.js +1 -0
- package/dist/client/assets/{cobol-DkiK_msx.js → cobol-DsRZMP16.js} +1 -1
- package/dist/client/assets/{coffee-f7z4wIqP.js → coffee-DP0-MMsE.js} +1 -1
- package/dist/client/assets/{cose-bilkent-S5V4N54A-Dikk-RXu.js → cose-bilkent-S5V4N54A-DMmK0b0W.js} +1 -1
- package/dist/client/assets/{cpp-DB_FV8ps.js → cpp-B6XYbbze.js} +1 -1
- package/dist/client/assets/{crystal-BRBnyZFz.js → crystal-dBLPFLMp.js} +1 -1
- package/dist/client/assets/{css-GpNBLqRF.js → css-tgwuSZL0.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-tErJNBcm.js → dagre-BM42HDAG-CgNkf0TR.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-D0-DgitC.js → diagram-2AECGRRQ-ocAqn0LV.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DqMjQTtW.js → diagram-5GNKFQAL-CB1Yo4Gc.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CP7V9tqS.js → diagram-KO2AKTUF-B2vpMBRT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-tCGdsz4p.js → diagram-LMA3HP47-DuxZOaQS.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DtDnaqso.js → diagram-OG6HWLK6-CkShDmcv.js} +1 -1
- package/dist/client/assets/{dist-B5Eb-OKZ.js → dist-BgZIvk5w.js} +1 -1
- package/dist/client/assets/{edge-DqnWvViE.js → edge-Bokubn8E.js} +1 -1
- package/dist/client/assets/{elixir-BSuxOIIa.js → elixir-BEzcf3Xg.js} +1 -1
- package/dist/client/assets/{elm-B6U1Is0L.js → elm-vJyLTAER.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-C5S0U2x3.js → erDiagram-TEJ5UH35-BFV1NQAH.js} +1 -1
- package/dist/client/assets/{erb-kK3QM18H.js → erb-DDkAmjBS.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-NNqmBYk2.js → flowDiagram-I6XJVG4X-DU8xbFdO.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-8ng09X8S.js → ganttDiagram-6RSMTGT7-DCb7Qh0c.js} +1 -1
- package/dist/client/assets/{git-rebase-BDKTWtxr.js → git-rebase-DWcifAdK.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-D_HBvwVl.js → gitGraphDiagram-PVQCEYII-C_Tw-piE.js} +1 -1
- package/dist/client/assets/{glimmer-js-BthGlRra.js → glimmer-js--HDru5ys.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CS8Z9z2h.js → glimmer-ts-DAdXisHh.js} +1 -1
- package/dist/client/assets/{glsl-C5ERhU2v.js → glsl-CmIRlJht.js} +1 -1
- package/dist/client/assets/{graphql-sfgTqbIp.js → graphql-tx4-7I3U.js} +1 -1
- package/dist/client/assets/{hack-EeOmOf4N.js → hack-yA0XjhF7.js} +1 -1
- package/dist/client/assets/{haml-DVf5-Ndq.js → haml-BR5MUE1b.js} +1 -1
- package/dist/client/assets/{handlebars-BSLgW49o.js → handlebars-BPOF7iOf.js} +1 -1
- package/dist/client/assets/{html-DoOSf-hz.js → html-BbteRk96.js} +1 -1
- package/dist/client/assets/{html-derivative-BM0DLd1Z.js → html-derivative-59NNrcMm.js} +1 -1
- package/dist/client/assets/{http-d2p18QI6.js → http-D3odGQI4.js} +1 -1
- package/dist/client/assets/{hurl-CH2I2f66.js → hurl-BLTj8fPj.js} +1 -1
- package/dist/client/assets/index-BZcKS8Sb.css +1 -0
- package/dist/client/assets/{index-CJI1E0bV.js → index-DHXpO7eS.js} +137 -104
- package/dist/client/assets/{infoDiagram-5YYISTIA-Cgg2FsCN.js → infoDiagram-5YYISTIA-CI6hJyRl.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-pYRQFecr.js → ishikawaDiagram-YF4QCWOH-gMS1P1LQ.js} +1 -1
- package/dist/client/assets/{java-BFiqhlWm.js → java-CsViuzs_.js} +1 -1
- package/dist/client/assets/{javascript-YWa7zRPZ.js → javascript-DQgsl2Dh.js} +1 -1
- package/dist/client/assets/{jinja-h_UdmDAn.js → jinja-DQNp2NUA.js} +1 -1
- package/dist/client/assets/{jison-cOvcjPYz.js → jison-CDDOZQdc.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-D3EBlW7m.js → journeyDiagram-JHISSGLW-BTQ_Ws_h.js} +1 -1
- package/dist/client/assets/{json-DT0GjoRi.js → json-DP4TqCuE.js} +1 -1
- package/dist/client/assets/{jsx-BaYjgZVH.js → jsx-Df5hn0Tf.js} +1 -1
- package/dist/client/assets/{julia-Cg36ySxC.js → julia--u33yGLi.js} +1 -1
- package/dist/client/assets/{just-KYC4U1y4.js → just-BCpMmSOD.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DA42mt4M.js → kanban-definition-UN3LZRKU-CvqGbLR_.js} +1 -1
- package/dist/client/assets/{latex-DpuXxlsH.js → latex-D8D4jZGw.js} +1 -1
- package/dist/client/assets/{line-D91vf_Yl.js → line-DBgaS_RW.js} +1 -1
- package/dist/client/assets/{linear-CL7WzrL5.js → linear-RQVzKNVU.js} +1 -1
- package/dist/client/assets/{liquid-Dua3sjBU.js → liquid-CkH85f12.js} +1 -1
- package/dist/client/assets/{lua-CpDMEbOh.js → lua-iKoV7WE3.js} +1 -1
- package/dist/client/assets/{marko-rM0rxrJ5.js → marko-N8ScQSqT.js} +1 -1
- package/dist/client/assets/{mdc-D-E4W6ne.js → mdc-BvHTOxsp.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BLQDdrsf.js → mermaid-parser.core-CwDRwMR7.js} +1 -1
- package/dist/client/assets/{mermaid.core-BBR2Otc_.js → mermaid.core-D2LDJgXO.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DMrApv-_.js → mindmap-definition-RKZ34NQL-FRnPh642.js} +1 -1
- package/dist/client/assets/{nginx-wZWgcQLM.js → nginx-DGgvASuI.js} +1 -1
- package/dist/client/assets/{nim-Bh7OZsZh.js → nim-D-v9hzvR.js} +1 -1
- package/dist/client/assets/{perl-DtEqNC_V.js → perl-CG8leJ8K.js} +1 -1
- package/dist/client/assets/{php-DX7bHce8.js → php-DvkeSP10.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-B2f9_PO6.js → pieDiagram-4H26LBE5-CepOg38Y.js} +1 -1
- package/dist/client/assets/{pug-r67inScB.js → pug-B4T7-xaL.js} +1 -1
- package/dist/client/assets/{qml-f1bOqUwX.js → qml-BE6D5Bxn.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DgdKno5G.js → quadrantDiagram-W4KKPZXB-DOFAsP60.js} +1 -1
- package/dist/client/assets/{r-Cpm-_vkr.js → r-BKZg9Mcq.js} +1 -1
- package/dist/client/assets/{razor-CEymfLfo.js → razor-D-02oaxL.js} +1 -1
- package/dist/client/assets/{regexp-DCnAJwuE.js → regexp-BDFvAR1k.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BIRl56-J.js → requirementDiagram-4Y6WPE33-BBVNH-2W.js} +1 -1
- package/dist/client/assets/review-export-D5iIl46_.js +7 -0
- package/dist/client/assets/{rst-Cm1M2uc8.js → rst-BcT26OSj.js} +1 -1
- package/dist/client/assets/{ruby-B49kkGT0.js → ruby-Cq_YQvFT.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-7XZZmV9N.js → sankeyDiagram-5OEKKPKP-BQfCTbBX.js} +1 -1
- package/dist/client/assets/{sas-BOXC9Iq4.js → sas-DN9oTxKY.js} +1 -1
- package/dist/client/assets/{scss-DU2HGWZP.js → scss-BK_sp3z2.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CtIjo3i9.js → sequenceDiagram-3UESZ5HK-CKAW2eyx.js} +1 -1
- package/dist/client/assets/{shellscript-YFjx1NBH.js → shellscript-CvNpOu0J.js} +1 -1
- package/dist/client/assets/{shellsession-4zfyiKm-.js → shellsession-8-yj2cJj.js} +1 -1
- package/dist/client/assets/{soy-CRExh-ql.js → soy-C6Q9atPF.js} +1 -1
- package/dist/client/assets/{sql-2Iw6Ta1F.js → sql-CBL3BuHb.js} +1 -1
- package/dist/client/assets/{src-Dn3_YOHM.js → src-D-VUWIK5.js} +1 -1
- package/dist/client/assets/{stata-DflAcpzB.js → stata-B2Q7Vqzu.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-D_w9F0BN.js → stateDiagram-AJRCARHV-BGJiv0qO.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdUgMghp.js +1 -0
- package/dist/client/assets/{surrealql-CMprrxj1.js → surrealql-C-A2N8vk.js} +1 -1
- package/dist/client/assets/{svelte-Bb_Bxt4a.js → svelte-VAut_-uy.js} +1 -1
- package/dist/client/assets/{templ-MJ_5gYAf.js → templ-mUetM--K.js} +1 -1
- package/dist/client/assets/{tex-BQ33E56K.js → tex-BT53mWGp.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BzgsTjf8.js → timeline-definition-PNZ67QCA-lVwMt1rk.js} +1 -1
- package/dist/client/assets/{ts-tags-Cddttr6f.js → ts-tags-VkIOkgLN.js} +1 -1
- package/dist/client/assets/{tsx-BFchi_eS.js → tsx-D_aaGHTf.js} +1 -1
- package/dist/client/assets/{twig-CA8ssIYI.js → twig-CufwqKkk.js} +1 -1
- package/dist/client/assets/{typescript-tdiTR9sY.js → typescript-osFD2CEV.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CMjPo4Qf.js → vennDiagram-CIIHVFJN-_K9NtpA5.js} +1 -1
- package/dist/client/assets/{vue-BbZNP0Rd.js → vue-Bub1rXmL.js} +1 -1
- package/dist/client/assets/{vue-html-02g5ipYx.js → vue-html-CN3x2pYg.js} +1 -1
- package/dist/client/assets/{vue-vine-9lCbaeLR.js → vue-vine-C0csYkGm.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cy6qwCgU.js → wardleyDiagram-YWT4CUSO-B2jTMfra.js} +1 -1
- package/dist/client/assets/{xml-BPPCB2P8.js → xml-my6OjeGb.js} +1 -1
- package/dist/client/assets/{xsl-1-u1Beet.js → xsl-Bx0YVn26.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-q9ysdRiS.js → xychartDiagram-2RQKCTM6-DfZ4YYXU.js} +1 -1
- package/dist/client/assets/{yaml-79W-bav7.js → yaml-DanwOypk.js} +1 -1
- package/dist/client/favicon.svg +40 -0
- package/dist/client/index.html +7 -3
- package/dist/comment-format-B5AcGcR-.mjs +118 -0
- package/dist/comment-format-CKTt0fzi.mjs +113 -0
- package/dist/comment-format-DG_PAehC.mjs +117 -0
- package/dist/completions-ZSKYvW06.mjs +126 -0
- package/dist/diff-fingerprint-CWa-soYD.mjs +78 -0
- package/dist/doctor-q6oA64hm.mjs +195 -0
- package/dist/find-tui-binary-CRpbYXLC.mjs +52 -0
- package/dist/git-Czo96ymM.mjs +789 -0
- package/dist/git-DaP_1rVy.mjs +2 -0
- package/dist/github-C9GLNak7.mjs +2 -0
- package/dist/github-HfIlIS-J.mjs +525 -0
- package/dist/mcp-7eXATtez.mjs +1334 -0
- package/dist/mcp-BBk8AIO4.mjs +1334 -0
- package/dist/mcp-BJxrW9dS.mjs +1334 -0
- package/dist/mcp-BgZO-iXI.mjs +1047 -0
- package/dist/mcp-BhT5GgYF.mjs +1334 -0
- package/dist/mcp-BsGdNA88.mjs +1334 -0
- package/dist/mcp-BxBm3d5l.mjs +1047 -0
- package/dist/mcp-Ca9QAlJ0.mjs +1334 -0
- package/dist/mcp-CphIUJUg.mjs +1047 -0
- package/dist/mcp-eor39qmD.mjs +1352 -0
- package/dist/plan-format-BQyx--mp.mjs +144 -0
- package/dist/plan-format-Bi46QS0m.mjs +132 -0
- package/dist/review-export-BoV0884M.mjs +66 -0
- package/dist/review-export-CKTI5Od_.mjs +66 -0
- package/dist/review-export-DjxKBeIZ.mjs +66 -0
- package/dist/server-AisDKkpW.mjs +3227 -0
- package/dist/server-BUsRhL1B.mjs +3283 -0
- package/dist/server-BZBFquG8.mjs +3875 -0
- package/dist/server-Bc2m9R5-.mjs +3263 -0
- package/dist/server-Bmt57wV5.mjs +3875 -0
- package/dist/server-C105rwQw.mjs +3283 -0
- package/dist/server-D0Ni8PKP.mjs +3252 -0
- package/dist/server-D2xEbakj.mjs +3874 -0
- package/dist/server-TOElOuNP.mjs +3258 -0
- package/dist/server-lock-DaSx7DjW.mjs +1049 -0
- package/dist/server-lock-uqSdDYeR.mjs +153 -0
- package/dist/settings-BVnzGFL7.mjs +478 -0
- package/dist/update-check-fe0tR8tx.mjs +129 -0
- package/package.json +1 -1
- package/dist/client/assets/channel-h7e2aSOW.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-HzjMGBiA.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-D_yZ0nqc.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-eToeXGJV.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-D_EfzX89.js +0 -1
- package/dist/client/assets/index-Ct9zZxJ5.css +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Brvg0TWH.js +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { d as getProjectStorageDir, p as getRepoRoot } from "./git-Czo96ymM.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
//#region src/lib/server-lock.ts
|
|
5
|
+
const STARTUP_LEASE_STALE_MS = 3e4;
|
|
6
|
+
/**
|
|
7
|
+
* Produce a stable comparison key for the diff a server displays. Runtime-only
|
|
8
|
+
* web options are excluded so `--port`/`--no-open` do not create false scope
|
|
9
|
+
* mismatches between a user-started session and an MCP request.
|
|
10
|
+
*/
|
|
11
|
+
function diffScopeKey(options) {
|
|
12
|
+
const { port: _port, host: _host, noOpen: _noOpen, help: _help, version: _version, outputMode: _outputMode, tui: _tui, gpu: _gpu, noExtDiff: _noExtDiff, ...scope } = options;
|
|
13
|
+
return JSON.stringify(scope);
|
|
14
|
+
}
|
|
15
|
+
function lockPath(repoRoot) {
|
|
16
|
+
return join(getProjectStorageDir(repoRoot), "server.json");
|
|
17
|
+
}
|
|
18
|
+
function writeServerLock(lock) {
|
|
19
|
+
const path = lockPath(lock.repoRoot);
|
|
20
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
21
|
+
writeFileSync(path, JSON.stringify(lock, null, 2), "utf-8");
|
|
22
|
+
}
|
|
23
|
+
function readServerLock(repoRoot) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = readFileSync(lockPath(repoRoot), "utf-8");
|
|
26
|
+
const lock = JSON.parse(raw);
|
|
27
|
+
if (typeof lock.port !== "number" || typeof lock.pid !== "number") return null;
|
|
28
|
+
return lock;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True if the process named by the lock is still alive and the lock belongs to
|
|
35
|
+
* this repo. `process.kill(pid, 0)` sends no signal — it just probes existence.
|
|
36
|
+
*/
|
|
37
|
+
function isLockAlive(lock, expectedRepoRoot) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(lock.pid, 0);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return lock.repoRoot === (expectedRepoRoot ?? getRepoRoot());
|
|
45
|
+
} catch {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function removeServerLock(repoRoot) {
|
|
50
|
+
try {
|
|
51
|
+
rmSync(lockPath(repoRoot), { force: true });
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
/** Remove server.json only while holding the startup lease and only if exact ownership still matches. */
|
|
55
|
+
function removeServerLockIfOwned(repoRoot, pid, ownerId) {
|
|
56
|
+
const cleanupLease = acquireServerStartupLease(repoRoot, `cleanup-${ownerId}`);
|
|
57
|
+
if (!cleanupLease) return false;
|
|
58
|
+
try {
|
|
59
|
+
const lock = readServerLock(repoRoot);
|
|
60
|
+
if (!lock || lock.pid !== pid || lock.ownerId !== ownerId) return false;
|
|
61
|
+
removeServerLock(repoRoot);
|
|
62
|
+
return true;
|
|
63
|
+
} finally {
|
|
64
|
+
cleanupLease.release();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Atomically reserve server startup for one repository. The exclusive-create
|
|
69
|
+
* directory is the cross-process serialization point; callers must re-read
|
|
70
|
+
* server.json after acquiring it because another process may have completed
|
|
71
|
+
* startup between their first lock check and this lease acquisition.
|
|
72
|
+
*/
|
|
73
|
+
function acquireServerStartupLease(repoRoot, ownerId, now = Date.now()) {
|
|
74
|
+
const path = join(getProjectStorageDir(repoRoot), "server-startup.lock");
|
|
75
|
+
const recordPath = join(path, "lease.json");
|
|
76
|
+
const ownerMarkerPath = join(path, `owner-${encodeURIComponent(ownerId)}`);
|
|
77
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
78
|
+
const tryCreate = () => {
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(path, {
|
|
81
|
+
recursive: false,
|
|
82
|
+
mode: 448
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error?.code === "EEXIST") return false;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
writeFileSync(recordPath, JSON.stringify({
|
|
90
|
+
ownerId,
|
|
91
|
+
createdAt: now,
|
|
92
|
+
pid: process.pid
|
|
93
|
+
}), "utf-8");
|
|
94
|
+
writeFileSync(ownerMarkerPath, "", "utf-8");
|
|
95
|
+
} catch (error) {
|
|
96
|
+
rmSync(path, {
|
|
97
|
+
recursive: true,
|
|
98
|
+
force: true
|
|
99
|
+
});
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
};
|
|
104
|
+
if (!tryCreate()) {
|
|
105
|
+
let reclaim = false;
|
|
106
|
+
let acquiredAfterRace = false;
|
|
107
|
+
try {
|
|
108
|
+
const record = JSON.parse(readFileSync(recordPath, "utf-8"));
|
|
109
|
+
if (typeof record.createdAt !== "number" || typeof record.pid !== "number") throw new Error("Malformed startup lease");
|
|
110
|
+
let ownerIsAlive = true;
|
|
111
|
+
try {
|
|
112
|
+
process.kill(record.pid, 0);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
ownerIsAlive = error?.code !== "ESRCH";
|
|
115
|
+
}
|
|
116
|
+
reclaim = now - record.createdAt > STARTUP_LEASE_STALE_MS && !ownerIsAlive;
|
|
117
|
+
if (!reclaim) return null;
|
|
118
|
+
} catch {
|
|
119
|
+
try {
|
|
120
|
+
reclaim = now - statSync(path).mtimeMs > STARTUP_LEASE_STALE_MS;
|
|
121
|
+
} catch (statError) {
|
|
122
|
+
if (statError?.code !== "ENOENT") return null;
|
|
123
|
+
if (!tryCreate()) return null;
|
|
124
|
+
acquiredAfterRace = true;
|
|
125
|
+
}
|
|
126
|
+
if (!reclaim && !acquiredAfterRace) return null;
|
|
127
|
+
}
|
|
128
|
+
if (reclaim && !acquiredAfterRace) {
|
|
129
|
+
const stalePath = `${path}.stale-${process.pid}-${now}`;
|
|
130
|
+
renameSync(path, stalePath);
|
|
131
|
+
rmSync(stalePath, {
|
|
132
|
+
recursive: true,
|
|
133
|
+
force: true
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (!acquiredAfterRace && !tryCreate()) return null;
|
|
137
|
+
}
|
|
138
|
+
let released = false;
|
|
139
|
+
return {
|
|
140
|
+
ownerId,
|
|
141
|
+
release() {
|
|
142
|
+
if (released) return;
|
|
143
|
+
released = true;
|
|
144
|
+
try {
|
|
145
|
+
unlinkSync(ownerMarkerPath);
|
|
146
|
+
unlinkSync(recordPath);
|
|
147
|
+
rmdirSync(path);
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { removeServerLock as a, readServerLock as i, diffScopeKey as n, removeServerLockIfOwned as o, isLockAlive as r, writeServerLock as s, acquireServerStartupLease as t };
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { d as getProjectStorageDir, p as getRepoRoot } from "./git-Czo96ymM.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/lib/symbols.ts
|
|
6
|
+
const SYMBOL_PATTERNS = [
|
|
7
|
+
{
|
|
8
|
+
pattern: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w[\w$]*)\s*[<(]/i,
|
|
9
|
+
kind: "function",
|
|
10
|
+
nameGroup: 1
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
pattern: /^\s*(?:export\s+)?(?:const|let|var)\s+(\w[\w$]*)\s*=\s*(?:async\s*)?\(/i,
|
|
14
|
+
kind: "function",
|
|
15
|
+
nameGroup: 1
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pattern: /^\s*(?:export\s+)?(?:const|let|var)\s+(\w[\w$]*)\s*=\s*(?:async\s+)?function/i,
|
|
19
|
+
kind: "function",
|
|
20
|
+
nameGroup: 1
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
pattern: /^\s*(?:export\s+)?class\s+(\w[\w$]*)/i,
|
|
24
|
+
kind: "class",
|
|
25
|
+
nameGroup: 1
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /^\s*(?:export\s+)?interface\s+(\w[\w$]*)/i,
|
|
29
|
+
kind: "interface",
|
|
30
|
+
nameGroup: 1
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
pattern: /^\s*(?:export\s+)?type\s+(\w[\w$]*)\s*=/i,
|
|
34
|
+
kind: "type",
|
|
35
|
+
nameGroup: 1
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pattern: /^\s*(?:export\s+)?enum\s+(\w[\w$]*)/i,
|
|
39
|
+
kind: "enum",
|
|
40
|
+
nameGroup: 1
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /^\s*(?:export\s+)?(?:const|let|var)\s+(\w[\w$]*)\s*=/i,
|
|
44
|
+
kind: "variable",
|
|
45
|
+
nameGroup: 1
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /^\s*def\s+(\w[\w$]*)\s*\(/i,
|
|
49
|
+
kind: "function",
|
|
50
|
+
nameGroup: 1
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /^\s*(?:pub(?:\s*\(\w+\))?\s+)?fn\s+(\w[\w$]*)\s*[<(]/i,
|
|
54
|
+
kind: "function",
|
|
55
|
+
nameGroup: 1
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /^\s*(?:pub(?:\s*\(\w+\))?\s+)?struct\s+(\w[\w$]*)/i,
|
|
59
|
+
kind: "struct",
|
|
60
|
+
nameGroup: 1
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
pattern: /^\s*(?:pub(?:\s*\(\w+\))?\s+)?enum\s+(\w[\w$]*)/i,
|
|
64
|
+
kind: "enum",
|
|
65
|
+
nameGroup: 1
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
pattern: /^\s*(?:pub(?:\s*\(\w+\))?\s+)?impl\s+(\w[\w$]*)/i,
|
|
69
|
+
kind: "impl",
|
|
70
|
+
nameGroup: 1
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pattern: /^\s*(?:pub(?:\s*\(\w+\))?\s+)?trait\s+(\w[\w$]*)/i,
|
|
74
|
+
kind: "trait",
|
|
75
|
+
nameGroup: 1
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
pattern: /^\s*func\s+(\w[\w$]*)\s*\(/i,
|
|
79
|
+
kind: "function",
|
|
80
|
+
nameGroup: 1
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
pattern: /^\s*(?:func\s+)?\((\w[\w$]*)\s+\*?\w+\)\s+(\w[\w$]*)\s*\(/i,
|
|
84
|
+
kind: "method",
|
|
85
|
+
nameGroup: 2
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
/**
|
|
89
|
+
* Returns the symbol defined on this line (name + kind), or null if the line
|
|
90
|
+
* is not a recognizable definition. The first matching pattern wins.
|
|
91
|
+
*/
|
|
92
|
+
function classifySymbolLine(line) {
|
|
93
|
+
for (const { pattern, kind, nameGroup } of SYMBOL_PATTERNS) {
|
|
94
|
+
const match = pattern.exec(line);
|
|
95
|
+
if (match) {
|
|
96
|
+
const name = match[nameGroup];
|
|
97
|
+
if (name) return {
|
|
98
|
+
name,
|
|
99
|
+
kind
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/lib/search.ts
|
|
107
|
+
/**
|
|
108
|
+
* Server-side code search powered by `@ff-labs/fff-node` — a native (Rust)
|
|
109
|
+
* fuzzy file finder + live grep. Because it is a native Node addon it cannot
|
|
110
|
+
* run in the browser, so the React client calls the `/api/search` endpoint and
|
|
111
|
+
* this module owns the single long-lived `FileFinder` instance for the repo.
|
|
112
|
+
*
|
|
113
|
+
* Design notes:
|
|
114
|
+
* - The native binary is loaded lazily via dynamic import inside `init()` so a
|
|
115
|
+
* missing/incompatible platform binary degrades gracefully (search returns
|
|
116
|
+
* an error) instead of crashing the whole server at module load.
|
|
117
|
+
* - One finder per process, keyed implicitly to the repo root. Its frecency /
|
|
118
|
+
* history databases live under the project's `~/.diffing/<repo>` storage dir
|
|
119
|
+
* so we never write stray files into the user's repository.
|
|
120
|
+
* - fff runs its own file-system watcher, so the index stays fresh as the
|
|
121
|
+
* working tree changes during a review.
|
|
122
|
+
* - "Symbols" search is plain grep for the identifier, then each hit line is
|
|
123
|
+
* classified with the shared {@link classifySymbolLine} patterns — fff 0.8.x
|
|
124
|
+
* does not populate `isDefinition`, so we do the classification ourselves.
|
|
125
|
+
*/
|
|
126
|
+
const DEFAULT_LIMIT = 60;
|
|
127
|
+
const MAX_LIMIT = 200;
|
|
128
|
+
/** Internal page size when no path filter — generous so diff-first client
|
|
129
|
+
* ranking has enough to work with. */
|
|
130
|
+
const SCAN_PAGE = 200;
|
|
131
|
+
/** Larger page size when filtering to a path set, so the post-filter is
|
|
132
|
+
* effectively complete for normal-sized diffs/repos. */
|
|
133
|
+
const SCAN_PAGE_FILTERED = 1e3;
|
|
134
|
+
let finder = null;
|
|
135
|
+
let initPromise = null;
|
|
136
|
+
let initError = null;
|
|
137
|
+
let shutdownHooked = false;
|
|
138
|
+
function clampLimit(limit) {
|
|
139
|
+
if (!limit || limit < 1) return DEFAULT_LIMIT;
|
|
140
|
+
return Math.min(limit, MAX_LIMIT);
|
|
141
|
+
}
|
|
142
|
+
async function init() {
|
|
143
|
+
try {
|
|
144
|
+
const { FileFinder } = await import("@ff-labs/fff-node");
|
|
145
|
+
const root = getRepoRoot();
|
|
146
|
+
const dbDir = join(getProjectStorageDir(root), "fff");
|
|
147
|
+
mkdirSync(dbDir, { recursive: true });
|
|
148
|
+
const created = FileFinder.create({
|
|
149
|
+
basePath: root,
|
|
150
|
+
frecencyDbPath: join(dbDir, "frecency.db"),
|
|
151
|
+
historyDbPath: join(dbDir, "history.db"),
|
|
152
|
+
logLevel: "error"
|
|
153
|
+
});
|
|
154
|
+
if (!created.ok) {
|
|
155
|
+
initError = created.error;
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const f = created.value;
|
|
159
|
+
if (!shutdownHooked) {
|
|
160
|
+
shutdownHooked = true;
|
|
161
|
+
process.once("exit", () => {
|
|
162
|
+
try {
|
|
163
|
+
f.destroy();
|
|
164
|
+
} catch {}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
await f.waitForScan(8e3);
|
|
168
|
+
finder = f;
|
|
169
|
+
return f;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
initError = err?.message ?? String(err);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function getFinder() {
|
|
176
|
+
if (finder) return finder;
|
|
177
|
+
if (initError) return null;
|
|
178
|
+
if (!initPromise) initPromise = init();
|
|
179
|
+
return initPromise;
|
|
180
|
+
}
|
|
181
|
+
function isIndexing(f) {
|
|
182
|
+
try {
|
|
183
|
+
return f.isScanning();
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function searchFiles(query, opts = {}) {
|
|
189
|
+
const f = await getFinder();
|
|
190
|
+
if (!f) return {
|
|
191
|
+
scope: "files",
|
|
192
|
+
items: [],
|
|
193
|
+
total: 0,
|
|
194
|
+
indexing: false,
|
|
195
|
+
error: initError ?? "Search unavailable"
|
|
196
|
+
};
|
|
197
|
+
const limit = clampLimit(opts.limit);
|
|
198
|
+
const pathSet = opts.paths ? new Set(opts.paths) : null;
|
|
199
|
+
const pageSize = pathSet ? SCAN_PAGE_FILTERED : Math.max(SCAN_PAGE, limit);
|
|
200
|
+
const res = f.fileSearch(query, { pageSize });
|
|
201
|
+
if (!res.ok) return {
|
|
202
|
+
scope: "files",
|
|
203
|
+
items: [],
|
|
204
|
+
total: 0,
|
|
205
|
+
indexing: isIndexing(f),
|
|
206
|
+
error: res.error
|
|
207
|
+
};
|
|
208
|
+
const items = [];
|
|
209
|
+
const { items: raw, scores } = res.value;
|
|
210
|
+
for (let i = 0; i < raw.length && items.length < limit; i++) {
|
|
211
|
+
const it = raw[i];
|
|
212
|
+
if (pathSet && !pathSet.has(it.relativePath)) continue;
|
|
213
|
+
const score = scores[i];
|
|
214
|
+
items.push({
|
|
215
|
+
path: it.relativePath,
|
|
216
|
+
fileName: it.fileName,
|
|
217
|
+
gitStatus: it.gitStatus,
|
|
218
|
+
matchType: score?.matchType ?? "",
|
|
219
|
+
exact: !!score?.exactMatch
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
scope: "files",
|
|
224
|
+
items,
|
|
225
|
+
total: res.value.totalMatched,
|
|
226
|
+
indexing: isIndexing(f)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async function searchContent(query, opts = {}) {
|
|
230
|
+
const f = await getFinder();
|
|
231
|
+
if (!f) return {
|
|
232
|
+
scope: "text",
|
|
233
|
+
items: [],
|
|
234
|
+
total: 0,
|
|
235
|
+
indexing: false,
|
|
236
|
+
error: initError ?? "Search unavailable"
|
|
237
|
+
};
|
|
238
|
+
if (!query) return {
|
|
239
|
+
scope: "text",
|
|
240
|
+
items: [],
|
|
241
|
+
total: 0,
|
|
242
|
+
indexing: isIndexing(f)
|
|
243
|
+
};
|
|
244
|
+
const limit = clampLimit(opts.limit);
|
|
245
|
+
const pathSet = opts.paths ? new Set(opts.paths) : null;
|
|
246
|
+
const pageSize = pathSet ? SCAN_PAGE_FILTERED : Math.max(SCAN_PAGE, limit);
|
|
247
|
+
const res = f.grep(query, {
|
|
248
|
+
mode: opts.regex ? "regex" : "plain",
|
|
249
|
+
smartCase: true,
|
|
250
|
+
beforeContext: 0,
|
|
251
|
+
afterContext: 0,
|
|
252
|
+
pageSize
|
|
253
|
+
});
|
|
254
|
+
if (!res.ok) return {
|
|
255
|
+
scope: "text",
|
|
256
|
+
items: [],
|
|
257
|
+
total: 0,
|
|
258
|
+
indexing: isIndexing(f),
|
|
259
|
+
error: res.error
|
|
260
|
+
};
|
|
261
|
+
const items = [];
|
|
262
|
+
for (const m of res.value.items) {
|
|
263
|
+
if (items.length >= limit) break;
|
|
264
|
+
if (pathSet && !pathSet.has(m.relativePath)) continue;
|
|
265
|
+
items.push(toContentHit(m));
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
scope: "text",
|
|
269
|
+
items,
|
|
270
|
+
total: res.value.totalMatched,
|
|
271
|
+
indexing: isIndexing(f),
|
|
272
|
+
regexError: res.value.regexFallbackError || void 0
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function toContentHit(m) {
|
|
276
|
+
return {
|
|
277
|
+
path: m.relativePath,
|
|
278
|
+
fileName: m.fileName,
|
|
279
|
+
line: m.lineNumber,
|
|
280
|
+
col: m.col,
|
|
281
|
+
content: m.lineContent,
|
|
282
|
+
matchRanges: m.matchRanges ?? [],
|
|
283
|
+
gitStatus: m.gitStatus
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function searchSymbols(query, opts = {}) {
|
|
287
|
+
const f = await getFinder();
|
|
288
|
+
if (!f) return {
|
|
289
|
+
scope: "symbols",
|
|
290
|
+
items: [],
|
|
291
|
+
total: 0,
|
|
292
|
+
indexing: false,
|
|
293
|
+
error: initError ?? "Search unavailable"
|
|
294
|
+
};
|
|
295
|
+
if (!query) return {
|
|
296
|
+
scope: "symbols",
|
|
297
|
+
items: [],
|
|
298
|
+
total: 0,
|
|
299
|
+
indexing: isIndexing(f)
|
|
300
|
+
};
|
|
301
|
+
const limit = clampLimit(opts.limit);
|
|
302
|
+
const pathSet = opts.paths ? new Set(opts.paths) : null;
|
|
303
|
+
const pageSize = pathSet ? SCAN_PAGE_FILTERED : Math.max(SCAN_PAGE_FILTERED, limit * 8);
|
|
304
|
+
const res = f.grep(query, {
|
|
305
|
+
mode: "plain",
|
|
306
|
+
smartCase: true,
|
|
307
|
+
beforeContext: 0,
|
|
308
|
+
afterContext: 0,
|
|
309
|
+
pageSize
|
|
310
|
+
});
|
|
311
|
+
if (!res.ok) return {
|
|
312
|
+
scope: "symbols",
|
|
313
|
+
items: [],
|
|
314
|
+
total: 0,
|
|
315
|
+
indexing: isIndexing(f),
|
|
316
|
+
error: res.error
|
|
317
|
+
};
|
|
318
|
+
const items = [];
|
|
319
|
+
const ql = query.toLowerCase();
|
|
320
|
+
const seen = /* @__PURE__ */ new Set();
|
|
321
|
+
for (const m of res.value.items) {
|
|
322
|
+
if (items.length >= limit) break;
|
|
323
|
+
if (pathSet && !pathSet.has(m.relativePath)) continue;
|
|
324
|
+
const sym = classifySymbolLine(m.lineContent);
|
|
325
|
+
if (!sym) continue;
|
|
326
|
+
if (!sym.name.toLowerCase().includes(ql)) continue;
|
|
327
|
+
const key = `${m.relativePath}:${m.lineNumber}:${sym.name}`;
|
|
328
|
+
if (seen.has(key)) continue;
|
|
329
|
+
seen.add(key);
|
|
330
|
+
const nameIdx = m.lineContent.indexOf(sym.name);
|
|
331
|
+
const matchRanges = nameIdx >= 0 ? [[nameIdx, nameIdx + sym.name.length]] : m.matchRanges ?? [];
|
|
332
|
+
items.push({
|
|
333
|
+
name: sym.name,
|
|
334
|
+
kind: sym.kind,
|
|
335
|
+
path: m.relativePath,
|
|
336
|
+
fileName: m.fileName,
|
|
337
|
+
line: m.lineNumber,
|
|
338
|
+
content: m.lineContent,
|
|
339
|
+
matchRanges,
|
|
340
|
+
gitStatus: m.gitStatus
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
scope: "symbols",
|
|
345
|
+
items,
|
|
346
|
+
total: items.length,
|
|
347
|
+
indexing: isIndexing(f)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async function searchAll(query, opts = {}) {
|
|
351
|
+
const limit = clampLimit(opts.limit);
|
|
352
|
+
const [filesRes, contentRes, symbolsRes] = await Promise.all([
|
|
353
|
+
searchFiles(query, {
|
|
354
|
+
...opts,
|
|
355
|
+
limit
|
|
356
|
+
}),
|
|
357
|
+
searchContent(query, {
|
|
358
|
+
...opts,
|
|
359
|
+
limit
|
|
360
|
+
}),
|
|
361
|
+
searchSymbols(query, {
|
|
362
|
+
...opts,
|
|
363
|
+
limit
|
|
364
|
+
})
|
|
365
|
+
]);
|
|
366
|
+
const items = [];
|
|
367
|
+
for (const hit of filesRes.items) items.push({
|
|
368
|
+
kind: "file",
|
|
369
|
+
hit
|
|
370
|
+
});
|
|
371
|
+
for (const hit of symbolsRes.items) items.push({
|
|
372
|
+
kind: "symbol",
|
|
373
|
+
hit
|
|
374
|
+
});
|
|
375
|
+
for (const hit of contentRes.items) items.push({
|
|
376
|
+
kind: "text",
|
|
377
|
+
hit
|
|
378
|
+
});
|
|
379
|
+
const total = (filesRes.total || 0) + (contentRes.total || 0) + (symbolsRes.total || 0);
|
|
380
|
+
const indexing = filesRes.indexing || contentRes.indexing || symbolsRes.indexing;
|
|
381
|
+
const error = filesRes.error || contentRes.error || symbolsRes.error;
|
|
382
|
+
const regexError = contentRes.regexError;
|
|
383
|
+
return {
|
|
384
|
+
scope: "all",
|
|
385
|
+
items: items.slice(0, limit),
|
|
386
|
+
total,
|
|
387
|
+
indexing,
|
|
388
|
+
error,
|
|
389
|
+
regexError
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function getSearchStatus() {
|
|
393
|
+
const f = await getFinder();
|
|
394
|
+
if (!f) return {
|
|
395
|
+
available: false,
|
|
396
|
+
indexing: false,
|
|
397
|
+
indexedFiles: 0,
|
|
398
|
+
error: initError ?? "Search unavailable"
|
|
399
|
+
};
|
|
400
|
+
let indexedFiles = 0;
|
|
401
|
+
let indexing = false;
|
|
402
|
+
try {
|
|
403
|
+
const prog = f.getScanProgress();
|
|
404
|
+
if (prog.ok) {
|
|
405
|
+
indexedFiles = prog.value.scannedFilesCount;
|
|
406
|
+
indexing = prog.value.isScanning;
|
|
407
|
+
}
|
|
408
|
+
} catch {}
|
|
409
|
+
return {
|
|
410
|
+
available: true,
|
|
411
|
+
indexing,
|
|
412
|
+
indexedFiles
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/** Record that the user opened `path` from a query, improving fff's frecency
|
|
416
|
+
* ranking for future searches. Best-effort, never throws. */
|
|
417
|
+
async function trackSelection(query, path) {
|
|
418
|
+
const f = await getFinder();
|
|
419
|
+
if (!f) return;
|
|
420
|
+
try {
|
|
421
|
+
f.trackQuery(query, path);
|
|
422
|
+
} catch {}
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/lib/settings.ts
|
|
426
|
+
const CONFIG_DIR = join(homedir(), ".config", "diffing");
|
|
427
|
+
const SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
428
|
+
const DEFAULTS = {
|
|
429
|
+
staged: true,
|
|
430
|
+
untracked: true,
|
|
431
|
+
diffStyle: "split",
|
|
432
|
+
defaultTabSize: 4,
|
|
433
|
+
theme: "nord",
|
|
434
|
+
editorIDE: "default",
|
|
435
|
+
lineDiffType: "word",
|
|
436
|
+
lineWrap: false,
|
|
437
|
+
diffIndicators: "classic",
|
|
438
|
+
showLineNumbers: true,
|
|
439
|
+
hunkSeparators: "line-info",
|
|
440
|
+
lineHoverHighlight: "both",
|
|
441
|
+
fontSize: 13,
|
|
442
|
+
expandContextByDefault: false,
|
|
443
|
+
collapsedContextThreshold: 10,
|
|
444
|
+
expansionLineCount: 20,
|
|
445
|
+
haptics: true,
|
|
446
|
+
sounds: true,
|
|
447
|
+
uiFont: null,
|
|
448
|
+
monoFont: null,
|
|
449
|
+
density: "comfortable",
|
|
450
|
+
autoCollapseLineThreshold: 400,
|
|
451
|
+
requireViewAllBeforeSend: false,
|
|
452
|
+
showStatusBar: true,
|
|
453
|
+
savedReplies: [],
|
|
454
|
+
ignoreSpaceChange: false,
|
|
455
|
+
ignoreAllSpace: false
|
|
456
|
+
};
|
|
457
|
+
function loadSettings() {
|
|
458
|
+
try {
|
|
459
|
+
const data = readFileSync(SETTINGS_FILE, "utf-8");
|
|
460
|
+
return {
|
|
461
|
+
...DEFAULTS,
|
|
462
|
+
...JSON.parse(data)
|
|
463
|
+
};
|
|
464
|
+
} catch {
|
|
465
|
+
return { ...DEFAULTS };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function saveSettings(settings) {
|
|
469
|
+
const merged = {
|
|
470
|
+
...loadSettings(),
|
|
471
|
+
...settings
|
|
472
|
+
};
|
|
473
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
474
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2));
|
|
475
|
+
return merged;
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
478
|
+
export { searchContent as a, trackSelection as c, searchAll as i, saveSettings as n, searchFiles as o, getSearchStatus as r, searchSymbols as s, loadSettings as t };
|