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.
@@ -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, readdirSync, statSync, readFileSync } from 'node:fs';
9
- import { resolve, join, relative, 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
- * Finds all .gitignore files in a directory tree
25
- *
26
- * @param {string} rootPath - Root directory path
27
- * @returns {Map<string, string>} Map of directory paths to their .gitignore content
28
- */
29
- function findGitignoreFiles(rootPath) {
30
- const gitignoreMap = new Map();
31
-
32
- /**
33
- * Recursively scan for .gitignore files
34
- * @param {string} dirPath - Current directory to scan
35
- */
36
- function scanDirectory(dirPath) {
37
- try {
38
- const entries = readdirSync(dirPath, { withFileTypes: true });
39
-
40
- // Check if this directory has a .gitignore file
41
- const gitignorePath = join(dirPath, '.gitignore');
42
- if (existsSync(gitignorePath)) {
43
- // We'll read these lazily when needed to avoid excessive I/O
44
- gitignoreMap.set(dirPath, gitignorePath);
45
- }
46
-
47
- // Recurse into subdirectories
48
- for (const entry of entries) {
49
- if (entry.isDirectory() && entry.name !== '.git') {
50
- const subPath = join(dirPath, entry.name);
51
- scanDirectory(subPath);
52
- }
53
- }
54
- } catch (error) {
55
- // Skip directories we can't read (permission errors, etc.)
56
- }
57
- }
58
-
59
- scanDirectory(rootPath);
60
- return gitignoreMap;
61
- }
62
-
63
- /**
64
- * Creates an ignore filter function based on .gitignore files
65
- *
66
- * @param {string} rootPath - Root directory path
67
- * @param {object} options - Configuration options
68
- * @param {boolean} [options.showIgnored=false] - If true, don't filter anything (--no-ignore)
69
- * @param {string[]} [options.extraIgnorePatterns] - Additional patterns to ignore
70
- * @returns {object} Object with shouldIgnore method and addPattern method
71
- */
72
- export function createIgnoreFilter(rootPath, options = {}) {
73
- const { showIgnored = false, extraIgnorePatterns = [] } = options;
74
-
75
- // If showIgnored is true, return a pass-through filter
76
- if (showIgnored) {
77
- return {
78
- shouldIgnore: (relativePath) => false,
79
- addPattern: () => {},
80
- filter: (paths) => paths,
81
- };
82
- }
83
-
84
- // Initialize the ignore instance
85
- const ig = ignore();
86
-
87
- // Add default patterns (unless showIgnored is true)
88
- for (const pattern of DEFAULT_PATTERNS) {
89
- ig.add(pattern);
90
- }
91
-
92
- // Add extra patterns from options
93
- if (extraIgnorePatterns.length > 0) {
94
- ig.add(extraIgnorePatterns);
95
- }
96
-
97
- // Find all .gitignore files
98
- const gitignoreMap = findGitignoreFiles(rootPath);
99
-
100
- // Track which .gitignore files we've already loaded
101
- const loadedIgnores = new Set();
102
-
103
- /**
104
- * Ensures .gitignore patterns for a given path are loaded
105
- * This implements lazy loading - we only load patterns when we encounter a directory
106
- *
107
- * @param {string} dirAbsPath - Absolute path to directory
108
- */
109
- function loadPatternsForDirectory(dirAbsPath) {
110
- if (loadedIgnores.has(dirAbsPath)) {
111
- return; // Already loaded
112
- }
113
-
114
- const gitignorePath = gitignoreMap.get(dirAbsPath);
115
- if (gitignorePath && existsSync(gitignorePath)) {
116
- try {
117
- // Read and parse the .gitignore file
118
- const content = readFileSync(gitignorePath, 'utf-8');
119
- ig.add(content);
120
- loadedIgnores.add(dirAbsPath);
121
- } catch (error) {
122
- // Silently ignore read errors
123
- }
124
- }
125
- }
126
-
127
- /**
128
- * Checks if a relative path should be ignored
129
- *
130
- * @param {string} relativePath - Path relative to root (forward slashes)
131
- * @returns {boolean} True if the path should be ignored
132
- */
133
- function shouldIgnore(relativePath) {
134
- // Empty string (root directory) is never ignored
135
- if (relativePath === '') {
136
- return false;
137
- }
138
-
139
- // Normalize path to use forward slashes
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
- node.expanded = !node.expanded;
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