diffstalker 0.1.6 → 0.2.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.
Files changed (79) hide show
  1. package/.github/workflows/release.yml +5 -3
  2. package/CHANGELOG.md +36 -0
  3. package/bun.lock +378 -0
  4. package/dist/App.js +1162 -1
  5. package/dist/config.js +83 -2
  6. package/dist/core/ExplorerStateManager.js +266 -0
  7. package/dist/core/FilePathWatcher.js +133 -0
  8. package/dist/core/GitOperationQueue.js +109 -1
  9. package/dist/core/GitStateManager.js +525 -1
  10. package/dist/git/diff.js +471 -10
  11. package/dist/git/ignoreUtils.js +30 -0
  12. package/dist/git/status.js +237 -5
  13. package/dist/index.js +70 -16
  14. package/dist/ipc/CommandClient.js +165 -0
  15. package/dist/ipc/CommandServer.js +152 -0
  16. package/dist/services/commitService.js +22 -1
  17. package/dist/state/CommitFlowState.js +86 -0
  18. package/dist/state/UIState.js +182 -0
  19. package/dist/themes.js +127 -1
  20. package/dist/types/tabs.js +4 -0
  21. package/dist/ui/Layout.js +252 -0
  22. package/dist/ui/modals/BaseBranchPicker.js +110 -0
  23. package/dist/ui/modals/DiscardConfirm.js +77 -0
  24. package/dist/ui/modals/HotkeysModal.js +209 -0
  25. package/dist/ui/modals/ThemePicker.js +107 -0
  26. package/dist/ui/widgets/CommitPanel.js +58 -0
  27. package/dist/ui/widgets/CompareListView.js +216 -0
  28. package/dist/ui/widgets/DiffView.js +279 -0
  29. package/dist/ui/widgets/ExplorerContent.js +102 -0
  30. package/dist/ui/widgets/ExplorerView.js +95 -0
  31. package/dist/ui/widgets/FileList.js +185 -0
  32. package/dist/ui/widgets/Footer.js +46 -0
  33. package/dist/ui/widgets/Header.js +111 -0
  34. package/dist/ui/widgets/HistoryView.js +69 -0
  35. package/dist/utils/ansiToBlessed.js +125 -0
  36. package/dist/utils/ansiTruncate.js +108 -0
  37. package/dist/utils/baseBranchCache.js +44 -2
  38. package/dist/utils/commitFormat.js +38 -1
  39. package/dist/utils/diffFilters.js +21 -1
  40. package/dist/utils/diffRowCalculations.js +113 -1
  41. package/dist/utils/displayRows.js +351 -2
  42. package/dist/utils/explorerDisplayRows.js +169 -0
  43. package/dist/utils/fileCategories.js +26 -1
  44. package/dist/utils/formatDate.js +39 -1
  45. package/dist/utils/formatPath.js +58 -1
  46. package/dist/utils/languageDetection.js +236 -0
  47. package/dist/utils/layoutCalculations.js +98 -1
  48. package/dist/utils/lineBreaking.js +88 -5
  49. package/dist/utils/mouseCoordinates.js +165 -1
  50. package/dist/utils/pathUtils.js +27 -0
  51. package/dist/utils/rowCalculations.js +246 -4
  52. package/dist/utils/wordDiff.js +50 -0
  53. package/package.json +15 -19
  54. package/dist/components/BaseBranchPicker.js +0 -1
  55. package/dist/components/BottomPane.js +0 -1
  56. package/dist/components/CommitPanel.js +0 -1
  57. package/dist/components/CompareListView.js +0 -1
  58. package/dist/components/ExplorerContentView.js +0 -3
  59. package/dist/components/ExplorerView.js +0 -1
  60. package/dist/components/FileList.js +0 -1
  61. package/dist/components/Footer.js +0 -1
  62. package/dist/components/Header.js +0 -1
  63. package/dist/components/HistoryView.js +0 -1
  64. package/dist/components/HotkeysModal.js +0 -1
  65. package/dist/components/Modal.js +0 -1
  66. package/dist/components/ScrollableList.js +0 -1
  67. package/dist/components/ThemePicker.js +0 -1
  68. package/dist/components/TopPane.js +0 -1
  69. package/dist/components/UnifiedDiffView.js +0 -1
  70. package/dist/hooks/useCommitFlow.js +0 -1
  71. package/dist/hooks/useCompareState.js +0 -1
  72. package/dist/hooks/useExplorerState.js +0 -9
  73. package/dist/hooks/useGit.js +0 -1
  74. package/dist/hooks/useHistoryState.js +0 -1
  75. package/dist/hooks/useKeymap.js +0 -1
  76. package/dist/hooks/useLayout.js +0 -1
  77. package/dist/hooks/useMouse.js +0 -1
  78. package/dist/hooks/useTerminalSize.js +0 -1
  79. package/dist/hooks/useWatcher.js +0 -11
@@ -1 +0,0 @@
1
- import{jsx as t,jsxs as c}from"react/jsx-runtime";import{Box as i,Text as s,useInput as C}from"ink";import{Modal as b,centerModal as M}from"./Modal.js";const r=[{title:"Navigation",entries:[{key:"\u2191/k",description:"Move up"},{key:"\u2193/j",description:"Move down"},{key:"Tab",description:"Toggle pane focus"}]},{title:"Staging",entries:[{key:"^S",description:"Stage file"},{key:"^U",description:"Unstage file"},{key:"^A",description:"Stage all"},{key:"^Z",description:"Unstage all"},{key:"Space/Enter",description:"Toggle stage"}]},{title:"Actions",entries:[{key:"c",description:"Open commit panel"},{key:"r",description:"Refresh"},{key:"q",description:"Quit"}]},{title:"Pane Resize",entries:[{key:"[",description:"Shrink top pane"},{key:"]",description:"Grow top pane"}]},{title:"Tabs",entries:[{key:"1",description:"Diff view"},{key:"2",description:"Commit panel"},{key:"3",description:"History view"},{key:"4",description:"Compare view"},{key:"a",description:"Toggle auto-tab mode"}]},{title:"Other",entries:[{key:"m",description:"Toggle scroll/select mode"},{key:"f",description:"Toggle follow mode"},{key:"w",description:"Toggle wrap mode"},{key:"t",description:"Theme picker"},{key:"b",description:"Base branch picker"},{key:"u",description:"Toggle uncommitted"},{key:"?",description:"This help"}]}];export function HotkeysModal({onClose:T,width:h,height:y}){C((e,o)=>{(o.escape||o.return||e==="?")&&T()});const m=h>=90,l=m?38:30,d=Math.min(m?82:40,h-4);let p;if(m){const e=Math.ceil(r.length/2),o=r.slice(0,e),n=r.slice(e),a=o.reduce((g,k)=>g+k.entries.length+2,0),w=n.reduce((g,k)=>g+k.entries.length+2,0);p=Math.min(Math.max(a,w)+5,y-4)}else{const e=r.reduce((o,n)=>o+n.entries.length+2,0)+4;p=Math.min(e,y-4)}const{x:f,y:x}=M(d,p,h,y),u=(e,o)=>c(i,{flexDirection:"column",marginBottom:1,children:[t(s,{bold:!0,dimColor:!0,children:e.title}),e.entries.map(n=>c(i,{children:[t(i,{width:13,children:t(s,{color:"cyan",children:n.key})}),t(i,{width:o-13,children:t(s,{children:n.description})})]},n.key))]},e.title);if(m){const e=Math.ceil(r.length/2),o=r.slice(0,e),n=r.slice(e);return t(b,{x:f,y:x,width:d,height:p,children:c(i,{borderStyle:"round",borderColor:"cyan",flexDirection:"column",width:d,children:[t(i,{justifyContent:"center",marginBottom:1,children:c(s,{bold:!0,color:"cyan",children:[" ","Keyboard Shortcuts"," "]})}),c(i,{children:[t(i,{flexDirection:"column",width:l,marginRight:2,children:o.map(a=>u(a,l))}),t(i,{flexDirection:"column",width:l,children:n.map(a=>u(a,l))})]}),t(i,{marginTop:1,justifyContent:"center",children:t(s,{dimColor:!0,children:"Press Esc, Enter, or ? to close"})})]})})}return t(b,{x:f,y:x,width:d,height:p,children:c(i,{borderStyle:"round",borderColor:"cyan",flexDirection:"column",width:d,children:[t(i,{justifyContent:"center",marginBottom:1,children:c(s,{bold:!0,color:"cyan",children:[" ","Keyboard Shortcuts"," "]})}),r.map(e=>u(e,l)),t(i,{marginTop:1,justifyContent:"center",children:t(s,{dimColor:!0,children:"Press Esc, Enter, or ? to close"})})]})})}
@@ -1 +0,0 @@
1
- import{jsx as e,jsxs as c}from"react/jsx-runtime";import{Box as i,Text as x}from"ink";export function Modal({x:o,y:r,width:n,height:t,children:l}){const a=" ".repeat(n);return c(i,{position:"absolute",marginLeft:o,marginTop:r,flexDirection:"column",children:[Array.from({length:t}).map((f,s)=>e(x,{children:a},`blank-${s}`)),e(i,{position:"absolute",flexDirection:"column",children:l})]})}export function centerModal(o,r,n,t){return{x:Math.floor((n-o)/2),y:Math.floor((t-r)/2)}}
@@ -1 +0,0 @@
1
- import{jsxs as s,jsx as f}from"react/jsx-runtime";import{useMemo as C}from"react";import{Box as R,Text as w}from"ink";export function ScrollableList({items:e,renderItem:u,maxHeight:i,scrollOffset:o,getKey:n,header:r,showIndicators:m=!0,getItemHeight:x}){const d=!!x,{itemRowStarts:S,totalRows:g}=C(()=>{if(!d)return{itemRowStarts:[],totalRows:e.length};const a=[];let t=0;for(let l=0;l<e.length;l++)a.push(t),t+=x(e[l],l);return{itemRowStarts:a,totalRows:t}},[e,x,d]);let h=i;r&&h--;const j=o>0,p=(d?g:e.length)>i;m&&p&&(h-=2),h=Math.max(1,h);const v=[];let c=0,M=0,b=0;if(d){let a=0;for(let t=0;t<e.length;t++){const l=x(e[t],t);if(S[t]+l>o){a=t;break}}for(let t=a;t<e.length&&c<h;t++){const l=x(e[t],t);v.push({item:e[t],index:t}),c+=l}M=o,b=Math.max(0,g-o-c)}else{const a=Math.min(o+h,e.length);for(let t=o;t<a;t++)v.push({item:e[t],index:t}),c++;M=o,b=Math.max(0,e.length-o-c)}return s(R,{flexDirection:"column",overflowX:"hidden",height:i,overflow:"hidden",children:[r,m&&p&&(j?s(w,{dimColor:!0,children:["\u2191 ",M," more above"]}):f(w,{children:" "})),v.map(({item:a,index:t})=>f(R,{children:u(a,t)},`${o}-${t}-${n(a,t)}`)),m&&p&&(b>0?s(w,{dimColor:!0,children:["\u2193 ",b," more below"]}):f(w,{children:" "}))]})}export function getMaxScrollOffset(e,u,i=!1,o=!0){let n=u;return i&&n--,o&&e>n&&(n-=2),n=Math.max(1,n),Math.max(0,e-n)}export function getVisibleItemCount(e,u,i,o=!1,n=!0){let r=u;return o&&r--,n&&(i>0&&r--,e>i+r&&r--),r=Math.max(1,r),Math.min(r,e-i)}
@@ -1 +0,0 @@
1
- import{jsx as o,jsxs as c}from"react/jsx-runtime";import{useState as y}from"react";import{Box as l,Text as r,useInput as w}from"ink";import{themes as j,themeOrder as t,getTheme as S}from"../themes.js";import{Modal as T,centerModal as v}from"./Modal.js";function M({theme:h}){const{colors:e}=h;return c(l,{flexDirection:"column",marginLeft:2,children:[c(l,{children:[o(r,{backgroundColor:e.delBg,color:e.delLineNum,children:" 5 "}),o(r,{backgroundColor:e.delBg,color:e.delSymbol,bold:!0,children:"- "}),o(r,{backgroundColor:e.delBg,color:e.text,children:"const "}),o(r,{backgroundColor:e.delHighlight,color:e.text,children:"old"}),o(r,{backgroundColor:e.delBg,color:e.text,children:" = value;"})]}),c(l,{children:[o(r,{backgroundColor:e.addBg,color:e.addLineNum,children:" 5 "}),o(r,{backgroundColor:e.addBg,color:e.addSymbol,bold:!0,children:"+ "}),o(r,{backgroundColor:e.addBg,color:e.text,children:"const "}),o(r,{backgroundColor:e.addHighlight,color:e.text,children:"new"}),o(r,{backgroundColor:e.addBg,color:e.text,children:" = value;"})]})]})}export function ThemePicker({currentTheme:h,onSelect:e,onCancel:f,width:m,height:g}){const[a,x]=y(()=>{const n=t.indexOf(h);return n>=0?n:0}),C=S(t[a]);w((n,d)=>{d.escape?f():d.return?e(t[a]):d.upArrow||n==="k"?x(i=>Math.max(0,i-1)):(d.downArrow||n==="j")&&x(i=>Math.min(t.length-1,i+1))});const s=Math.min(50,m-4),b=Math.min(t.length+10,g-4),{x:p,y:B}=v(s,b,m,g);return o(T,{x:p,y:B,width:s,height:b,children:c(l,{borderStyle:"round",borderColor:"cyan",flexDirection:"column",width:s,children:[o(l,{justifyContent:"center",marginBottom:1,children:c(r,{bold:!0,color:"cyan",children:[" ","Select Theme"," "]})}),t.map((n,d)=>{const i=j[n],u=d===a,k=n===h;return c(l,{children:[o(r,{color:u?"cyan":void 0,children:u?"\u25B8 ":" "}),o(r,{bold:u,color:u?"cyan":void 0,children:i.displayName}),k&&o(r,{dimColor:!0,children:" (current)"})]},n)}),c(l,{marginTop:1,flexDirection:"column",children:[o(r,{dimColor:!0,children:"Preview:"}),o(M,{theme:C})]}),o(l,{marginTop:1,justifyContent:"center",children:o(r,{dimColor:!0,children:"\u2191\u2193 navigate \u2022 Enter select \u2022 Esc cancel"})})]})})}
@@ -1 +0,0 @@
1
- import{jsx as e,jsxs as o,Fragment as s}from"react/jsx-runtime";import G from"react";import{Box as n,Text as i}from"ink";import{FileList as X}from"./FileList.js";import{HistoryView as b}from"./HistoryView.js";import{CompareListView as z}from"./CompareListView.js";import{ExplorerView as H,buildBreadcrumbs as N}from"./ExplorerView.js";import{categorizeFiles as Y}from"../utils/fileCategories.js";export function TopPane({bottomTab:d,currentPane:l,terminalWidth:c,topPaneHeight:t,files:h,selectedIndex:g,fileListScrollOffset:C,stagedCount:w,onStage:y,onUnstage:a,commits:u,historySelectedIndex:A,historyScrollOffset:O,onSelectHistoryCommit:p,compareDiff:r,compareListSelection:v,compareScrollOffset:F,includeUncommitted:f,explorerCurrentPath:m="",explorerItems:I=[],explorerSelectedIndex:E=0,explorerScrollOffset:R=0,explorerIsLoading:j=!1,explorerError:L=null}){const{modified:S,untracked:k}=Y(h),B=S.length,M=k.length;return o(n,{flexDirection:"column",height:t,width:c,overflowX:"hidden",overflowY:"hidden",children:[(d==="diff"||d==="commit")&&o(s,{children:[o(n,{children:[e(i,{bold:!0,color:l==="files"?"cyan":void 0,children:"STAGING AREA"}),o(i,{dimColor:!0,children:[" ","(",B," modified, ",M," untracked, ",w," staged)"]})]}),e(X,{files:h,selectedIndex:g,isFocused:l==="files",scrollOffset:C,maxHeight:t-1,width:c,onStage:y,onUnstage:a})]}),d==="history"&&o(s,{children:[o(n,{children:[e(i,{bold:!0,color:l==="history"?"cyan":void 0,children:"COMMITS"}),o(i,{dimColor:!0,children:[" (",u.length," commits)"]})]}),e(b,{commits:u,selectedIndex:A,scrollOffset:O,maxHeight:t-1,isActive:l==="history",width:c,onSelectCommit:p})]}),d==="compare"&&o(s,{children:[o(n,{children:[e(i,{bold:!0,color:l==="compare"?"cyan":void 0,children:"COMPARE"}),e(i,{dimColor:!0,children:" (vs "}),e(i,{color:"cyan",children:r?.baseBranch??"..."}),o(i,{dimColor:!0,children:[": ",r?.commits.length??0," commits, ",r?.files.length??0," files) (b)"]}),r&&r.uncommittedCount>0&&o(s,{children:[e(i,{dimColor:!0,children:" | "}),o(i,{color:f?"magenta":"yellow",children:["[",f?"x":" ","] uncommitted"]}),e(i,{dimColor:!0,children:" (u)"})]})]}),e(z,{commits:r?.commits??[],files:r?.files??[],selectedItem:v,scrollOffset:F,maxHeight:t-1,isActive:l==="compare",width:c})]}),d==="explorer"&&o(s,{children:[o(n,{children:[e(i,{bold:!0,color:l==="explorer"?"cyan":void 0,children:"EXPLORER"}),e(i,{dimColor:!0,children:" "}),N(m).map((V,x,_)=>o(G.Fragment,{children:[e(i,{color:"blue",children:V}),x<_.length-1&&e(i,{dimColor:!0,children:" / "})]},x)),m&&e(i,{dimColor:!0,children:" /"}),!m&&e(i,{dimColor:!0,children:"(root)"})]}),e(H,{currentPath:m,items:I,selectedIndex:E,scrollOffset:R,maxHeight:t-1,isActive:l==="explorer",width:c,isLoading:j,error:L})]})]})}
@@ -1 +0,0 @@
1
- import{jsxs as f,jsx as e}from"react/jsx-runtime";import{useMemo as N}from"react";import{Box as m,Text as o}from"ink";import{getTheme as D}from"../themes.js";import{ScrollableList as I}from"./ScrollableList.js";import{getDisplayRowsLineNumWidth as j}from"../utils/displayRows.js";function l(n,r){return r<=0||n.length<=r?n:r<=1?"\u2026":n.slice(0,r-1)+"\u2026"}function b(n,r){return n===void 0?" ".repeat(r):String(n).padStart(r," ")}function T({row:n,lineNumWidth:r,width:g,theme:C,wrapMode:u}){const{colors:c}=C,h=g-r-5,s=g-2;switch(n.type){case"diff-header":{const t=n.content;if(t.startsWith("diff --git")){const i=t.match(/diff --git a\/.+ b\/(.+)$/);if(i){const a=s-6,d=l(i[1],a);return f(o,{color:"cyan",bold:!0,children:["\u2500\u2500 ",d," \u2500\u2500"]})}}return e(o,{dimColor:!0,children:l(t,s)})}case"diff-hunk":{const t=n.content.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/);if(t){const i=parseInt(t[1],10),a=t[2]?parseInt(t[2],10):1,d=parseInt(t[3],10),p=t[4]?parseInt(t[4],10):1,x=t[5].trim(),k=i+a-1,B=d+p-1,S=a===1?`${i}`:`${i}-${k}`,R=p===1?`${d}`:`${d}-${B}`,y=`Lines ${S} \u2192 ${R}`,L=s-y.length-1,$=x&&L>3?" "+l(x,L):"";return f(m,{children:[e(o,{color:"cyan",dimColor:!0,children:y}),$&&e(o,{color:"gray",children:$})]})}return e(o,{color:"cyan",dimColor:!0,children:l(n.content,s)})}case"diff-add":{const t=n.isContinuation,i=t?"\xBB":"+",d=" "+(u?n.content||"":l(n.content,h)||"")||" ";return f(m,{children:[e(o,{backgroundColor:c.addBg,color:c.addLineNum,children:b(n.lineNum,r)+" "}),e(o,{backgroundColor:c.addBg,color:t?c.addLineNum:c.addSymbol,bold:!t,children:i}),e(o,{backgroundColor:c.addBg,color:c.text,children:d})]})}case"diff-del":{const t=n.isContinuation,i=t?"\xBB":"-",d=" "+(u?n.content||"":l(n.content,h)||"")||" ";return f(m,{children:[e(o,{backgroundColor:c.delBg,color:c.delLineNum,children:b(n.lineNum,r)+" "}),e(o,{backgroundColor:c.delBg,color:t?c.delLineNum:c.delSymbol,bold:!t,children:i}),e(o,{backgroundColor:c.delBg,color:c.text,children:d})]})}case"diff-context":{const i=n.isContinuation?"\xBB ":" ",a=u?n.content:l(n.content,h);return f(m,{children:[f(o,{color:c.contextLineNum,children:[b(n.lineNum,r)," "]}),e(o,{dimColor:!0,children:i}),e(o,{children:a})]})}case"commit-header":return e(o,{color:"yellow",children:l(n.content,s)});case"commit-message":return e(o,{children:l(n.content,s)});case"spacer":return e(o,{children:" "})}}export function UnifiedDiffView({rows:n,maxHeight:r,scrollOffset:g,theme:C,width:u,wrapMode:c=!1}){const h=N(()=>D(C),[C]),s=N(()=>j(n),[n]);return n.length===0?e(m,{paddingX:1,children:e(o,{dimColor:!0,children:"No diff to display"})}):e(m,{flexDirection:"column",paddingX:1,width:u,children:e(I,{items:n,maxHeight:r,scrollOffset:g,getKey:(t,i)=>`row-${i}`,renderItem:t=>e(T,{row:t,lineNumWidth:s,width:u,theme:h,wrapMode:c})})})}export function getUnifiedDiffTotalRows(n){return n.length}
@@ -1 +0,0 @@
1
- import{useState as o,useCallback as i,useEffect as M}from"react";import{validateCommit as S,formatCommitMessage as b}from"../services/commitService.js";export function useCommitFlow(C){const{stagedCount:l,onCommit:m,onSuccess:c,getHeadMessage:f}=C,[s,n]=o(""),[t,r]=o(!1),[p,u]=o(!1),[h,a]=o(null),[v,d]=o(!1);M(()=>{t&&f().then(e=>{e&&!s&&n(e)})},[t,f]);const y=i(()=>{r(e=>!e)},[]),E=i(async()=>{const e=S(s,l,t);if(!e.valid){a(e.error);return}u(!0),a(null);try{await m(b(s),t),n(""),r(!1),c()}catch(g){a(g instanceof Error?g.message:"Commit failed")}finally{u(!1)}},[s,l,t,m,c]),F=i(()=>{n(""),r(!1),a(null),d(!1)},[]);return{message:s,amend:t,isCommitting:p,error:h,inputFocused:v,setMessage:n,toggleAmend:y,setInputFocused:d,handleSubmit:E,reset:F}}
@@ -1 +0,0 @@
1
- import{useState as a,useEffect as h,useCallback as o,useMemo as L,useRef as Z}from"react";import{getCompareItemIndexFromRow as _,getFileScrollOffset as $}from"../utils/rowCalculations.js";import{buildCompareDisplayRows as H,getDisplayRowsLineNumWidth as S,getWrappedRowCount as p}from"../utils/displayRows.js";export function useCompareState({repoPath:m,isActive:s,compareDiff:t,refreshCompareDiff:R,getCandidateBaseBranches:k,setCompareBaseBranch:y,selectCompareCommit:C,topPaneHeight:b,compareScrollOffset:i,setCompareScrollOffset:g,setDiffScrollOffset:l,status:T,wrapMode:U,terminalWidth:F}){const[d,W]=a(!0),[z,I]=a(null),[u,w]=a(0),r=Z(!1),[E,M]=a([]),[N,x]=a(!1);h(()=>{m&&s&&R(d)},[m,s,T,R,d]),h(()=>{m&&s&&k().then(M)},[m,s,k]),h(()=>{s&&(r.current=!1,I(null),l(0))},[s,l]),h(()=>{if(s&&t&&r.current){const n=t.commits.length,e=t.files.length;if(u<n)I({type:"commit",index:u}),C(u),l(0);else if(u<n+e){const c=u-n;I({type:"file",index:c});const Y=$(t,c);l(Y)}}},[s,t,u,C,l]);const B=L(()=>t?t.commits.length+t.files.length:0,[t]),j=L(()=>{const n=H(t);if(!U)return n.length;const e=S(n),c=F-e-5;return p(n,c,!0)},[t,U,F]),q=o(()=>{W(n=>!n)},[]),G=o(()=>{x(!0)},[]),J=o(()=>{x(!1)},[]),K=o(n=>{x(!1),y(n,d)},[y,d]),P=o(()=>{r.current=!0},[]),Q=o(()=>{r.current=!0,w(n=>{const e=Math.max(0,n-1);return e<i&&g(e),e})},[i,g]),V=o(()=>{r.current=!0,w(n=>{const e=Math.min(B-1,n+1),c=i+b-2;return e>=c&&g(i+1),e})},[B,i,b,g]),X=o(n=>t?_(n,t.commits.length,t.files.length):-1,[t]);return{includeUncommitted:d,compareListSelection:z,compareSelectedIndex:u,baseBranchCandidates:E,showBaseBranchPicker:N,compareTotalItems:B,compareDiffTotalRows:j,setCompareSelectedIndex:w,toggleIncludeUncommitted:q,openBaseBranchPicker:G,closeBaseBranchPicker:J,selectBaseBranch:K,navigateCompareUp:Q,navigateCompareDown:V,markSelectionInitialized:P,getItemIndexFromRow:X}}
@@ -1,9 +0,0 @@
1
- import{useState as p,useEffect as k,useCallback as L,useMemo as v}from"react";import*as z from"node:fs";import*as h from"node:path";import{simpleGit as G}from"simple-git";const K=1024*1024,N=100*1024;function W(r){const s=Math.min(r.length,8192);for(let m=0;m<s;m++)if(r[m]===0)return!0;return!1}async function X(r,s){if(s.length===0)return new Set;const m=G(r),d=new Set,c=100;for(let g=0;g<s.length;g+=c){const w=s.slice(g,g+c);try{const F=(await m.raw(["check-ignore",...w])).trim().split(`
2
- `).filter(i=>i.length>0);for(const i of F)d.add(i)}catch{}}return d}export function useExplorerState({repoPath:r,isActive:s,topPaneHeight:m,explorerScrollOffset:d,setExplorerScrollOffset:c,fileScrollOffset:g,setFileScrollOffset:w}){const[t,F]=p(""),[i,E]=p([]),[D,I]=p(0),[x,f]=p(null),[_,B]=p(!1),[$,C]=p(null);k(()=>{if(!s||!r)return;(async()=>{B(!0),C(null);try{const a=h.join(r,t),o=await z.promises.readdir(a,{withFileTypes:!0}),y=o.map(e=>t?h.join(t,e.name):e.name),M=await X(r,y),l=o.filter(e=>{const u=t?h.join(t,e.name):e.name;return!M.has(u)}).map(e=>({name:e.name,path:t?h.join(t,e.name):e.name,isDirectory:e.isDirectory()}));l.sort((e,u)=>e.isDirectory&&!u.isDirectory?-1:!e.isDirectory&&u.isDirectory?1:e.name.localeCompare(u.name)),t&&l.unshift({name:"..",path:h.dirname(t)||"",isDirectory:!0}),E(l),I(0),c(0)}catch(a){C(a instanceof Error?a.message:"Failed to read directory"),E([])}finally{B(!1)}})()},[r,t,s,c]),k(()=>{if(!s||!r||i.length===0){f(null);return}const n=i[D];if(!n||n.isDirectory){f(null);return}(async()=>{try{const o=h.join(r,n.path),y=await z.promises.stat(o);if(y.size>K){f({path:n.path,content:`File too large to display (${(y.size/1024/1024).toFixed(2)} MB).
3
- Maximum size: 1 MB`,truncated:!0});return}const M=await z.promises.readFile(o);if(W(M)){f({path:n.path,content:"Binary file - cannot display"});return}let l=M.toString("utf-8"),e=!1;y.size>N&&(l=`\u26A0 Large file (${(y.size/1024).toFixed(1)} KB)
4
-
5
- `+l);const u=5e3,j=l.split(`
6
- `);j.length>u&&(l=j.slice(0,u).join(`
7
- `)+`
8
-
9
- ... (truncated, ${j.length-u} more lines)`,e=!0),f({path:n.path,content:l,truncated:e}),w(0)}catch(o){f({path:n.path,content:o instanceof Error?`Error: ${o.message}`:"Failed to read file"})}})()},[r,i,D,s,w]);const T=v(()=>i.length,[i]),b=L(()=>{I(n=>{const a=Math.max(0,n-1);return a<d&&c(a),a})},[d,c]),R=L(()=>{I(n=>{const a=Math.min(i.length-1,n+1),o=d+m-2;return a>=o&&c(d+1),a})},[i.length,d,m,c]),U=L(()=>{const n=i[D];n&&n.isDirectory&&(n.name===".."?F(h.dirname(t)||""):F(n.path))},[i,D,t]),Z=L(()=>{t&&F(h.dirname(t)||"")},[t]);return{currentPath:t,items:i,selectedIndex:D,setSelectedIndex:I,selectedFile:x,fileScrollOffset:g,setFileScrollOffset:w,navigateUp:b,navigateDown:R,enterDirectory:U,goUp:Z,isLoading:_,error:$,explorerTotalRows:T}}
@@ -1 +0,0 @@
1
- import{useState as i,useEffect as k,useCallback as a,useRef as v}from"react";import{getManagerForRepo as I,removeManagerForRepo as T}from"../core/GitStateManager.js";export function useGit(c){const[n,m]=i({status:null,diff:null,stagedDiff:"",selectedFile:null,isLoading:!1,error:null}),[o,p]=i({compareDiff:null,compareBaseBranch:null,compareLoading:!1,compareError:null}),[f,h]=i({selectedCommit:null,commitDiff:null}),[l,C]=i({type:null,index:0,diff:null}),t=v(null);k(()=>{if(!c){m({status:null,diff:null,stagedDiff:"",selectedFile:null,isLoading:!1,error:null});return}const e=I(c);t.current=e;const s=r=>{m(r)},u=r=>{p(r)},g=r=>{h(r)},d=r=>{C(r)};return e.on("state-change",s),e.on("compare-state-change",u),e.on("history-state-change",g),e.on("compare-selection-change",d),e.startWatching(),e.refresh(),()=>{e.off("state-change",s),e.off("compare-state-change",u),e.off("history-state-change",g),e.off("compare-selection-change",d),T(c),t.current=null}},[c]);const y=a(e=>{t.current?.selectFile(e)},[]),S=a(async e=>{await t.current?.stage(e)},[]),B=a(async e=>{await t.current?.unstage(e)},[]),D=a(async e=>{await t.current?.discard(e)},[]),w=a(async()=>{await t.current?.stageAll()},[]),F=a(async()=>{await t.current?.unstageAll()},[]),L=a(async(e,s=!1)=>{await t.current?.commit(e,s)},[]),H=a(async()=>{await t.current?.refresh()},[]),x=a(async()=>t.current?.getHeadCommitMessage()??"",[]),A=a(async(e=!1)=>{await t.current?.refreshCompareDiff(e)},[]),E=a(async()=>t.current?.getCandidateBaseBranches()??[],[]),M=a(async(e,s=!1)=>{await t.current?.setCompareBaseBranch(e,s)},[]),R=a(async e=>{await t.current?.selectHistoryCommit(e)},[]),G=a(async e=>{await t.current?.selectCompareCommit(e)},[]),b=a(e=>{t.current?.selectCompareFile(e)},[]);return{status:n.status,diff:n.diff,stagedDiff:n.stagedDiff,selectedFile:n.selectedFile,isLoading:n.isLoading,error:n.error,selectFile:y,stage:S,unstage:B,discard:D,stageAll:w,unstageAll:F,commit:L,refresh:H,getHeadCommitMessage:x,compareDiff:o.compareDiff,compareBaseBranch:o.compareBaseBranch,compareLoading:o.compareLoading,compareError:o.compareError,refreshCompareDiff:A,getCandidateBaseBranches:E,setCompareBaseBranch:M,historySelectedCommit:f.selectedCommit,historyCommitDiff:f.commitDiff,selectHistoryCommit:R,compareSelectionType:l.type,compareSelectionIndex:l.index,compareSelectionDiff:l.diff,selectCompareCommit:G,selectCompareFile:b}}
@@ -1 +0,0 @@
1
- import{useState as h,useEffect as I,useCallback as b,useMemo as W}from"react";import{getCommitHistory as k}from"../git/status.js";import{buildHistoryDisplayRows as C,getDisplayRowsLineNumWidth as H,getWrappedRowCount as L}from"../utils/displayRows.js";export function useHistoryState({repoPath:s,isActive:u,selectHistoryCommit:g,historyCommitDiff:c,historySelectedCommit:w,topPaneHeight:p,historyScrollOffset:e,setHistoryScrollOffset:i,setDiffScrollOffset:r,status:y,wrapMode:x,terminalWidth:R}){const[t,D]=h([]),[a,m]=h(0);I(()=>{s&&u&&k(s,100).then(D)},[s,u,y]),I(()=>{if(u&&t.length>0){const n=t[a];n&&(g(n),r(0))}},[u,t,a,g,r]);const E=W(()=>{const n=C(w,c);if(!x)return n.length;const o=H(n),d=R-o-5;return L(n,d,!0)},[w,c,x,R]),M=W(()=>t.length,[t]),N=b(()=>{m(n=>{const o=Math.max(0,n-1);return o<e&&i(o),o})},[e,i]),T=b(()=>{m(n=>{const o=Math.min(t.length-1,n+1),d=e+p-2;return o>=d&&i(e+1),o})},[t.length,e,p,i]);return{commits:t,historySelectedIndex:a,setHistorySelectedIndex:m,historyDiffTotalRows:E,navigateHistoryUp:N,navigateHistoryDown:T,historyTotalRows:M}}
@@ -1 +0,0 @@
1
- import{useInput as l}from"ink";export function useKeymap(r,n,f){l((e,o)=>{if(!f){if(o.ctrl&&e==="c"){r.onQuit();return}if(e==="q"){r.onQuit();return}if(e==="j"||o.downArrow){r.onNavigateDown();return}if(e==="k"||o.upArrow){r.onNavigateUp();return}if(o.tab){r.onTogglePane();return}if(e==="1"){r.onSwitchTab("diff");return}if(e==="2"){r.onSwitchTab("commit");return}if(e==="3"){r.onSwitchTab("history");return}if(e==="4"){r.onSwitchTab("compare");return}if(e==="5"){r.onSwitchTab("explorer");return}if(e==="u"&&r.onToggleIncludeUncommitted){r.onToggleIncludeUncommitted();return}if(e==="b"&&r.onCycleBaseBranch){r.onCycleBaseBranch();return}if(e==="t"&&r.onOpenThemePicker){r.onOpenThemePicker();return}if(e==="?"&&r.onOpenHotkeysModal){r.onOpenHotkeysModal();return}if(e==="["&&r.onShrinkTopPane){r.onShrinkTopPane();return}if(e==="]"&&r.onGrowTopPane){r.onGrowTopPane();return}if(e==="m"&&r.onToggleMouse){r.onToggleMouse();return}if(e==="f"&&r.onToggleFollow){r.onToggleFollow();return}if(e==="a"&&r.onToggleAutoTab){r.onToggleAutoTab();return}if(e==="w"&&r.onToggleWrap){r.onToggleWrap();return}if(o.ctrl&&e==="s"){r.onStage();return}if(o.ctrl&&e==="u"){r.onUnstage();return}if(o.ctrl&&e==="a"){r.onStageAll();return}if(o.ctrl&&e==="z"){r.onUnstageAll();return}if(e==="c"){r.onCommit();return}if(o.ctrl&&e==="r"){r.onRefresh();return}if(e==="r"){r.onRefresh();return}if(r.onExplorerEnter&&o.return){r.onExplorerEnter();return}if(r.onExplorerBack&&(o.backspace||o.delete||e==="h")){r.onExplorerBack();return}if(o.return||e===" "){r.onSelect();return}}})}
@@ -1 +0,0 @@
1
- import{useState as m,useEffect as g,useMemo as T,useCallback as a}from"react";import{getRowForFileIndex as q,calculateScrollOffset as z,getFileListSectionCounts as G,getFileListTotalRows as J}from"../utils/layoutCalculations.js";import{calculatePaneBoundaries as K}from"../utils/mouseCoordinates.js";export const LAYOUT_OVERHEAD=5;const b={diff:.4,commit:.4,history:.5,compare:.5,explorer:.4};export const SPLIT_RATIO_STEP=.05;export function useLayout(u,N,l,O,F,S="diff",Q,y,H=0){const r=u-LAYOUT_OVERHEAD-H,[p,R]=m(y??null),x=p??b[S],{topPaneHeight:i,bottomPaneHeight:f}=T(()=>{const t=r-5,o=Math.floor(r*x),s=Math.max(5,Math.min(o,t)),n=r-s;return{topPaneHeight:s,bottomPaneHeight:n}},[r,x]),A=a(e=>{const t=Math.max(.15,Math.min(.85,e));R(t)},[]),E=a(e=>{const t=p??b[S],o=Math.max(.15,Math.min(.85,t+e));R(o)},[p,S]),I=x,d=H+1,_=T(()=>K(i,f,u,d),[i,f,u,d]),[D,h]=m(0),[k,M]=m(0),[B,L]=m(0),[U,w]=m(0);g(()=>{h(0)},[l.length]),g(()=>{M(0)},[F]),g(()=>{const{modifiedCount:e,untrackedCount:t,stagedCount:o}=G(l),s=q(O,e,t,o),n=i-1;h(c=>z(s,c,n))},[O,l,i]);const V=a((e,t=3,o=0)=>{const s=f-1,c=o>s?s-2:s,C=Math.max(0,o-c);M(P=>e==="up"?Math.max(0,P-t):Math.min(C,P+t))},[f]),j=a((e,t=3)=>{const o=J(l),s=i-1,n=Math.max(0,o-s);h(c=>e==="up"?Math.max(0,c-t):Math.min(n,c+t))},[l,i]),W=a((e,t=0,o=3)=>{const s=Math.max(0,t-(i-1));L(n=>e==="up"?Math.max(0,n-o):Math.min(s,n+o))},[i]),Y=a((e,t,o=3)=>{const s=Math.max(0,t-(i-1));w(n=>e==="up"?Math.max(0,n-o):Math.min(s,n+o))},[i]);return{topPaneHeight:i,bottomPaneHeight:f,contentHeight:r,paneBoundaries:_,splitRatio:I,setSplitRatio:A,adjustSplitRatio:E,fileListScrollOffset:D,diffScrollOffset:k,historyScrollOffset:B,compareScrollOffset:U,setFileListScrollOffset:h,setDiffScrollOffset:M,setHistoryScrollOffset:L,setCompareScrollOffset:w,scrollDiff:V,scrollFileList:j,scrollHistory:W,scrollCompare:Y}}
@@ -1 +0,0 @@
1
- import{useEffect as d,useState as y,useCallback as C,useRef as x}from"react";import{useStdin as R}from"ink";export function useMouse(p,b=!1){const{stdin:u,setRawMode:h}=R(),[e,w]=y(!0),o=x(p);d(()=>{o.current=p});const M=C(()=>{w(l=>!l)},[]),a=x(e);return d(()=>{a.current=e},[e]),d(()=>{!b&&e?(process.stdout.write("\x1B[?1000h"),process.stdout.write("\x1B[?1006h")):(process.stdout.write("\x1B[?1006l"),process.stdout.write("\x1B[?1000l"))},[b,e]),d(()=>{if(!u||!h)return;const l=g=>{const m=g.toString(),s=m.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);if(s){const t=parseInt(s[1],10),n=parseInt(s[2],10),r=parseInt(s[3],10),c=s[4]==="m";if(t>=64&&t<96){if(a.current){const f=t===64?"scroll-up":"scroll-down";o.current({x:n,y:r,type:f,button:"none"})}}else if(c&&t>=0&&t<3){const f=t===0?"left":t===1?"middle":"right";o.current({x:n,y:r,type:"click",button:f})}return}const i=m.match(/\x1b\[M(.)(.)(.)/);if(i){const t=i[1].charCodeAt(0)-32,n=i[2].charCodeAt(0)-32,r=i[3].charCodeAt(0)-32;if(t>=64){if(a.current){const c=t===64?"scroll-up":"scroll-down";o.current({x:n,y:r,type:c,button:"none"})}}else if(t>=0&&t<3){const c=t===0?"left":t===1?"middle":"right";o.current({x:n,y:r,type:"click",button:c})}}};return u.on("data",l),()=>{u.off("data",l),process.stdout.write("\x1B[?1006l"),process.stdout.write("\x1B[?1000l")}},[u,h]),{mouseEnabled:e,toggleMouse:M}}
@@ -1 +0,0 @@
1
- import{useState as t,useEffect as r}from"react";export function useTerminalSize(){const[o,e]=t({rows:process.stdout.rows??24,columns:process.stdout.columns??80});return r(()=>{const s=()=>{e({rows:process.stdout.rows??24,columns:process.stdout.columns??80})};return process.stdout.on("resize",s),()=>{process.stdout.off("resize",s)}},[]),o}
@@ -1,11 +0,0 @@
1
- import{useState as h,useEffect as S,useRef as $}from"react";import*as f from"node:fs";import*as a from"node:path";import*as v from"node:os";import{watch as b}from"chokidar";import{ensureTargetDir as I}from"../config.js";function y(t){return t.startsWith("~/")?a.join(v.homedir(),t.slice(2)):t==="~"?v.homedir():t}function R(t){const e=t.split(`
2
- `);for(let s=e.length-1;s>=0;s--){const n=e[s].trim();if(n)return n}return""}export function useWatcher(t,e,s=!1){const[n,T]=h(t),[x,p]=h({path:null,lastUpdate:null,rawContent:null,sourceFile:t?e:null,enabled:t}),i=$(null),l=$(null);return S(()=>{p(d=>({...d,enabled:n,sourceFile:n?e:null}))},[n,e]),S(()=>{if(!n)return;I(e),f.existsSync(e)||f.writeFileSync(e,"");const d=()=>{i.current&&clearTimeout(i.current),i.current=setTimeout(()=>{try{const m=f.readFileSync(e,"utf-8"),r=R(m);if(r&&r!==l.current){const o=y(r),c=a.isAbsolute(o)?o:a.resolve(o),u=new Date;s&&(process.stderr.write(`[diffstalker ${u.toISOString()}] Path change detected
3
- `),process.stderr.write(` Source file: ${e}
4
- `),process.stderr.write(` Raw content: "${r}"
5
- `),process.stderr.write(` Previous: "${l.current??"(none)"}"
6
- `),process.stderr.write(` Resolved: "${c}"
7
- `)),l.current=c,p({path:c,lastUpdate:u,rawContent:r,sourceFile:e,enabled:!0})}}catch{}},100)};try{const m=f.readFileSync(e,"utf-8"),r=R(m);if(r){const o=y(r),c=a.isAbsolute(o)?o:a.resolve(o),u=new Date;s&&(process.stderr.write(`[diffstalker ${u.toISOString()}] Initial path read
8
- `),process.stderr.write(` Source file: ${e}
9
- `),process.stderr.write(` Raw content: "${r}"
10
- `),process.stderr.write(` Resolved: "${c}"
11
- `)),l.current=c,p({path:c,lastUpdate:u,rawContent:r,sourceFile:e,enabled:!0})}}catch{}const w=b(e,{persistent:!0,ignoreInitial:!0});return w.on("change",d),w.on("add",d),()=>{i.current&&clearTimeout(i.current),w.close()}},[n,e,s]),{state:x,setEnabled:T}}