ftreeview 0.1.1 → 0.1.3
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 +17 -17
- package/dist/cli.js +311 -153
- package/package.json +2 -2
- package/src/App.jsx +28 -12
- package/src/cli.js +10 -10
- package/src/components/StatusBar.jsx +2 -2
- package/src/hooks/useChangedFiles.js +50 -27
- package/src/hooks/useGitStatus.js +47 -0
- package/src/hooks/useIgnore.js +139 -182
- package/src/hooks/useNavigation.js +12 -4
- package/src/hooks/useTree.js +178 -61
- package/src/hooks/useWatcher.js +11 -2
package/src/hooks/useIgnore.js
CHANGED
|
@@ -1,182 +1,139 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useIgnore - Gitignore Filtering Hook
|
|
3
|
-
*
|
|
4
|
-
* Provides gitignore-style filtering functionality using the 'ignore' package.
|
|
5
|
-
* Supports loading .gitignore files from the root and nested directories.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync,
|
|
9
|
-
import {
|
|
10
|
-
import ignore from 'ignore';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Default patterns that are always applied (unless --no-ignore is set)
|
|
14
|
-
*/
|
|
15
|
-
const DEFAULT_PATTERNS = [
|
|
16
|
-
'.git',
|
|
17
|
-
'.git/',
|
|
18
|
-
'.git/**',
|
|
19
|
-
'node_modules',
|
|
20
|
-
'node_modules/',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @param {string} rootPath - Root directory path
|
|
27
|
-
* @
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const normalizedPath = relativePath.split(sep).join('/');
|
|
141
|
-
|
|
142
|
-
return ig.ignores(normalizedPath);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Filters an array of paths, keeping only non-ignored ones
|
|
147
|
-
*
|
|
148
|
-
* @param {string[]} paths - Array of relative paths
|
|
149
|
-
* @returns {string[]} Filtered array of paths
|
|
150
|
-
*/
|
|
151
|
-
function filter(paths) {
|
|
152
|
-
return ig.filter(paths);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Adds a pattern to the ignore filter
|
|
157
|
-
*
|
|
158
|
-
* @param {string|string[]} pattern - Pattern or array of patterns to add
|
|
159
|
-
*/
|
|
160
|
-
function addPattern(pattern) {
|
|
161
|
-
ig.add(pattern);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
shouldIgnore,
|
|
166
|
-
filter,
|
|
167
|
-
addPattern,
|
|
168
|
-
loadPatternsForDirectory,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* React hook for creating an ignore filter
|
|
174
|
-
* This is a convenience wrapper around createIgnoreFilter
|
|
175
|
-
*
|
|
176
|
-
* @param {string} rootPath - Root directory path
|
|
177
|
-
* @param {object} options - Configuration options
|
|
178
|
-
* @returns {object} Filter object with shouldIgnore method
|
|
179
|
-
*/
|
|
180
|
-
export function useIgnore(rootPath, options = {}) {
|
|
181
|
-
return createIgnoreFilter(rootPath, options);
|
|
182
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useIgnore - Gitignore Filtering Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides gitignore-style filtering functionality using the 'ignore' package.
|
|
5
|
+
* Supports loading .gitignore files from the root and nested directories.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join, sep } from 'node:path';
|
|
10
|
+
import ignore from 'ignore';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default patterns that are always applied (unless --no-ignore is set)
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_PATTERNS = [
|
|
16
|
+
'.git',
|
|
17
|
+
'.git/',
|
|
18
|
+
'.git/**',
|
|
19
|
+
'node_modules',
|
|
20
|
+
'node_modules/',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates an ignore filter function based on .gitignore files
|
|
25
|
+
*
|
|
26
|
+
* @param {string} rootPath - Root directory path
|
|
27
|
+
* @param {object} options - Configuration options
|
|
28
|
+
* @param {boolean} [options.showIgnored=false] - If true, don't filter anything (--no-ignore)
|
|
29
|
+
* @param {string[]} [options.extraIgnorePatterns] - Additional patterns to ignore
|
|
30
|
+
* @returns {object} Object with shouldIgnore method and addPattern method
|
|
31
|
+
*/
|
|
32
|
+
export function createIgnoreFilter(rootPath, options = {}) {
|
|
33
|
+
const { showIgnored = false, extraIgnorePatterns = [] } = options;
|
|
34
|
+
|
|
35
|
+
// If showIgnored is true, return a pass-through filter
|
|
36
|
+
if (showIgnored) {
|
|
37
|
+
return {
|
|
38
|
+
shouldIgnore: (relativePath) => false,
|
|
39
|
+
addPattern: () => {},
|
|
40
|
+
filter: (paths) => paths,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Initialize the ignore instance
|
|
45
|
+
const ig = ignore();
|
|
46
|
+
|
|
47
|
+
// Add default patterns (unless showIgnored is true)
|
|
48
|
+
for (const pattern of DEFAULT_PATTERNS) {
|
|
49
|
+
ig.add(pattern);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add extra patterns from options
|
|
53
|
+
if (extraIgnorePatterns.length > 0) {
|
|
54
|
+
ig.add(extraIgnorePatterns);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Track which .gitignore files we've already loaded
|
|
58
|
+
const loadedIgnores = new Set();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ensures .gitignore patterns for a given path are loaded
|
|
62
|
+
* This implements lazy loading - we only load patterns when we encounter a directory
|
|
63
|
+
*
|
|
64
|
+
* @param {string} dirAbsPath - Absolute path to directory
|
|
65
|
+
*/
|
|
66
|
+
function loadPatternsForDirectory(dirAbsPath) {
|
|
67
|
+
if (loadedIgnores.has(dirAbsPath)) {
|
|
68
|
+
return; // Already loaded
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const gitignorePath = join(dirAbsPath, '.gitignore');
|
|
72
|
+
if (existsSync(gitignorePath)) {
|
|
73
|
+
try {
|
|
74
|
+
// Read and parse the .gitignore file
|
|
75
|
+
const content = readFileSync(gitignorePath, 'utf-8');
|
|
76
|
+
ig.add(content);
|
|
77
|
+
loadedIgnores.add(dirAbsPath);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// Silently ignore read errors
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Checks if a relative path should be ignored
|
|
86
|
+
*
|
|
87
|
+
* @param {string} relativePath - Path relative to root (forward slashes)
|
|
88
|
+
* @returns {boolean} True if the path should be ignored
|
|
89
|
+
*/
|
|
90
|
+
function shouldIgnore(relativePath) {
|
|
91
|
+
// Empty string (root directory) is never ignored
|
|
92
|
+
if (relativePath === '') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Normalize path to use forward slashes
|
|
97
|
+
const normalizedPath = relativePath.split(sep).join('/');
|
|
98
|
+
|
|
99
|
+
return ig.ignores(normalizedPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Filters an array of paths, keeping only non-ignored ones
|
|
104
|
+
*
|
|
105
|
+
* @param {string[]} paths - Array of relative paths
|
|
106
|
+
* @returns {string[]} Filtered array of paths
|
|
107
|
+
*/
|
|
108
|
+
function filter(paths) {
|
|
109
|
+
return ig.filter(paths);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Adds a pattern to the ignore filter
|
|
114
|
+
*
|
|
115
|
+
* @param {string|string[]} pattern - Pattern or array of patterns to add
|
|
116
|
+
*/
|
|
117
|
+
function addPattern(pattern) {
|
|
118
|
+
ig.add(pattern);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
shouldIgnore,
|
|
123
|
+
filter,
|
|
124
|
+
addPattern,
|
|
125
|
+
loadPatternsForDirectory,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* React hook for creating an ignore filter
|
|
131
|
+
* This is a convenience wrapper around createIgnoreFilter
|
|
132
|
+
*
|
|
133
|
+
* @param {string} rootPath - Root directory path
|
|
134
|
+
* @param {object} options - Configuration options
|
|
135
|
+
* @returns {object} Filter object with shouldIgnore method
|
|
136
|
+
*/
|
|
137
|
+
export function useIgnore(rootPath, options = {}) {
|
|
138
|
+
return createIgnoreFilter(rootPath, options);
|
|
139
|
+
}
|
|
@@ -19,9 +19,10 @@ const VIEWPORT_MARGIN = 3; // Keep cursor 3 lines from viewport edges
|
|
|
19
19
|
* @param {FileNode[]} flatList - Flattened list of visible nodes from useTree
|
|
20
20
|
* @param {Function} rebuildTree - Callback to rebuild tree with modified nodes
|
|
21
21
|
* @param {Function|null} refreshFlatList - Callback to refresh visible list without full rebuild
|
|
22
|
+
* @param {Function|null} ensureChildrenLoaded - Callback to lazy-load directory children when expanding
|
|
22
23
|
* @returns {{cursor: number, viewportStart: number, exit: Function, setCursorByPath: Function, setCursor: Function}} Navigation state
|
|
23
24
|
*/
|
|
24
|
-
export function useNavigation(flatList = [], rebuildTree, refreshFlatList = null) {
|
|
25
|
+
export function useNavigation(flatList = [], rebuildTree, refreshFlatList = null, ensureChildrenLoaded = null) {
|
|
25
26
|
const { exit } = useApp();
|
|
26
27
|
|
|
27
28
|
// Calculate viewport height (terminal height minus header and status bar)
|
|
@@ -108,10 +109,13 @@ export function useNavigation(flatList = [], rebuildTree, refreshFlatList = null
|
|
|
108
109
|
|
|
109
110
|
// Only directories can be expanded
|
|
110
111
|
if (node.isDir && !node.expanded) {
|
|
112
|
+
if (typeof ensureChildrenLoaded === 'function') {
|
|
113
|
+
ensureChildrenLoaded(node);
|
|
114
|
+
}
|
|
111
115
|
node.expanded = true;
|
|
112
116
|
refreshVisibleTree();
|
|
113
117
|
}
|
|
114
|
-
}, [cursor, flatList, refreshVisibleTree]);
|
|
118
|
+
}, [cursor, flatList, refreshVisibleTree, ensureChildrenLoaded]);
|
|
115
119
|
|
|
116
120
|
/**
|
|
117
121
|
* Smart collapse: collapse if expanded, jump to parent if collapsed
|
|
@@ -144,9 +148,13 @@ export function useNavigation(flatList = [], rebuildTree, refreshFlatList = null
|
|
|
144
148
|
const node = flatList[cursor];
|
|
145
149
|
if (!node || !node.isDir) return;
|
|
146
150
|
|
|
147
|
-
|
|
151
|
+
const nextExpanded = !node.expanded;
|
|
152
|
+
if (nextExpanded && typeof ensureChildrenLoaded === 'function') {
|
|
153
|
+
ensureChildrenLoaded(node);
|
|
154
|
+
}
|
|
155
|
+
node.expanded = nextExpanded;
|
|
148
156
|
refreshVisibleTree();
|
|
149
|
-
}, [cursor, flatList, refreshVisibleTree]);
|
|
157
|
+
}, [cursor, flatList, refreshVisibleTree, ensureChildrenLoaded]);
|
|
150
158
|
|
|
151
159
|
/**
|
|
152
160
|
* Jump to the first item in the list
|