statusbar-quick-actions 0.0.10

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.
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Visibility management for StatusBar Quick Actions
3
+ */
4
+
5
+ import {
6
+ VisibilityContext,
7
+ VisibilityConfig,
8
+ VisibilityCondition,
9
+ } from "./types";
10
+ import * as vscode from "vscode";
11
+ import * as path from "path";
12
+ import * as fs from "fs";
13
+ import { DebouncedVisibilityChecker } from "./utils/debounce";
14
+
15
+ export class VisibilityManager {
16
+ private debouncedChecker: DebouncedVisibilityChecker;
17
+ private visibilityCache: Map<string, boolean>;
18
+
19
+ constructor(defaultDebounceMs = 300) {
20
+ this.debouncedChecker = new DebouncedVisibilityChecker(defaultDebounceMs);
21
+ this.visibilityCache = new Map();
22
+ }
23
+ /**
24
+ * Check if a button should be visible based on current context
25
+ */
26
+ public isVisible(
27
+ buttonConfig: VisibilityConfig,
28
+ context: VisibilityContext,
29
+ ): boolean {
30
+ if (!buttonConfig.conditions || buttonConfig.conditions.length === 0) {
31
+ return true;
32
+ }
33
+
34
+ // All conditions must be met (AND logic)
35
+ return buttonConfig.conditions.every((condition) => {
36
+ const result = this.evaluateCondition(condition, context);
37
+ // Apply invert logic if specified
38
+ return condition.invert ? !result : result;
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Evaluate a single visibility condition
44
+ */
45
+ private evaluateCondition(
46
+ condition: VisibilityCondition,
47
+ context: VisibilityContext,
48
+ ): boolean {
49
+ switch (condition.type) {
50
+ case "fileType":
51
+ return this.checkFileTypeCondition(condition, context);
52
+ case "fileExists":
53
+ return this.checkFileExistsCondition(condition, context);
54
+ case "gitStatus":
55
+ return this.checkGitStatusCondition(condition, context);
56
+ case "workspaceFolder":
57
+ return this.checkWorkspaceFolderCondition(condition, context);
58
+ default:
59
+ return false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get current visibility context
65
+ */
66
+ public getCurrentContext(): VisibilityContext {
67
+ const activeTextEditor = vscode.window.activeTextEditor;
68
+ const activeFile = activeTextEditor?.document.uri;
69
+ const fileName = activeTextEditor?.document.fileName;
70
+ const extension = activeTextEditor?.document.languageId;
71
+
72
+ return {
73
+ activeFile,
74
+ activeFileName: fileName,
75
+ activeFileExtension: extension,
76
+ workspaceFolders: [...(vscode.workspace.workspaceFolders || [])],
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Check file type condition
82
+ */
83
+ private checkFileTypeCondition(
84
+ condition: VisibilityCondition,
85
+ context: VisibilityContext,
86
+ ): boolean {
87
+ if (!condition.patterns || !context.activeFileName) {
88
+ return false;
89
+ }
90
+
91
+ const fileName = path.basename(context.activeFileName);
92
+ const fileExt = context.activeFileExtension;
93
+
94
+ return condition.patterns.some((pattern: string) => {
95
+ // Check if pattern is a glob pattern
96
+ if (pattern.includes("*") || pattern.includes("?")) {
97
+ return this.simpleGlobMatch(fileName, pattern);
98
+ }
99
+ // Check if pattern is an extension
100
+ else if (pattern.startsWith(".")) {
101
+ return fileName.endsWith(pattern);
102
+ }
103
+ // Check if pattern matches language ID
104
+ else {
105
+ return fileExt === pattern;
106
+ }
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Check file exists condition
112
+ */
113
+ private checkFileExistsCondition(
114
+ condition: VisibilityCondition,
115
+ _context: VisibilityContext,
116
+ ): boolean {
117
+ if (!condition.path) {
118
+ return false;
119
+ }
120
+
121
+ const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
122
+ if (!workspaceFolder) {
123
+ return false;
124
+ }
125
+
126
+ const fullPath = path.isAbsolute(condition.path)
127
+ ? condition.path
128
+ : path.join(workspaceFolder, condition.path);
129
+
130
+ return fs.existsSync(fullPath);
131
+ }
132
+
133
+ /**
134
+ * Check git status condition
135
+ */
136
+ private checkGitStatusCondition(
137
+ condition: VisibilityCondition,
138
+ _context: VisibilityContext,
139
+ ): boolean {
140
+ if (!condition.status) {
141
+ return false;
142
+ }
143
+
144
+ const gitExtension = vscode.extensions.getExtension("vscode.git");
145
+ if (!gitExtension) {
146
+ return false;
147
+ }
148
+
149
+ const git = gitExtension.exports.getAPI(1);
150
+ const repo = git.repositories[0];
151
+
152
+ if (!repo) {
153
+ return condition.status === "repository" ? false : true;
154
+ }
155
+
156
+ switch (condition.status) {
157
+ case "repository":
158
+ return true;
159
+ case "clean":
160
+ return (
161
+ repo.state.workingTreeChanges.length === 0 &&
162
+ repo.state.indexChanges.length === 0
163
+ );
164
+ case "dirty":
165
+ return (
166
+ repo.state.workingTreeChanges.length > 0 ||
167
+ repo.state.indexChanges.length > 0
168
+ );
169
+ case "ahead":
170
+ return (repo.state.HEAD?.ahead || 0) > 0;
171
+ case "behind":
172
+ return (repo.state.HEAD?.behind || 0) > 0;
173
+ default:
174
+ return false;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Check workspace folder condition
180
+ */
181
+ private checkWorkspaceFolderCondition(
182
+ condition: VisibilityCondition,
183
+ context: VisibilityContext,
184
+ ): boolean {
185
+ if (!condition.folders || !context.workspaceFolders) {
186
+ return false;
187
+ }
188
+
189
+ return context.workspaceFolders.some((folder) =>
190
+ condition.folders?.includes(folder.name),
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Check visibility with debouncing
196
+ */
197
+ public checkVisibilityDebounced(
198
+ buttonId: string,
199
+ buttonConfig: VisibilityConfig,
200
+ customDebounce?: number,
201
+ callback?: (isVisible: boolean) => void,
202
+ ): void {
203
+ const checkFn = () => {
204
+ const context = this.getCurrentContext();
205
+ const isVisible = this.isVisible(buttonConfig, context);
206
+ this.visibilityCache.set(buttonId, isVisible);
207
+ if (callback) {
208
+ callback(isVisible);
209
+ }
210
+ };
211
+
212
+ // Use per-button custom debounce if specified, otherwise use global default
213
+ const delay = customDebounce ?? buttonConfig.debounceMs;
214
+
215
+ const debouncedCheck = this.debouncedChecker.getDebouncedCheck(
216
+ buttonId,
217
+ checkFn,
218
+ delay,
219
+ );
220
+
221
+ debouncedCheck();
222
+ }
223
+
224
+ /**
225
+ * Get cached visibility result for a button
226
+ */
227
+ public getCachedVisibility(buttonId: string): boolean | undefined {
228
+ return this.visibilityCache.get(buttonId);
229
+ }
230
+
231
+ /**
232
+ * Simple glob pattern matching (replaces minimatch for basic patterns)
233
+ */
234
+ private simpleGlobMatch(text: string, pattern: string): boolean {
235
+ // Convert glob pattern to regex
236
+ let regexPattern = pattern
237
+ .replace(/\./g, "\\.") // Escape dots
238
+ .replace(/\*/g, ".*") // Replace * with .*
239
+ .replace(/\?/g, ".") // Replace ? with .
240
+ .replace(/\+\//g, ".*\\/"); // Handle path separators
241
+
242
+ // Ensure full string match
243
+ regexPattern = `^${regexPattern}$`;
244
+
245
+ try {
246
+ const regex = new RegExp(regexPattern, "i"); // Case insensitive
247
+ return regex.test(text);
248
+ } catch (error) {
249
+ console.warn(`Invalid glob pattern: ${pattern}`, error);
250
+ return false;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Clear cached visibility for a button
256
+ */
257
+ public clearCachedVisibility(buttonId: string): void {
258
+ this.visibilityCache.delete(buttonId);
259
+ }
260
+
261
+ /**
262
+ * Clear all cached visibility results
263
+ */
264
+ public clearAllCachedVisibility(): void {
265
+ this.visibilityCache.clear();
266
+ }
267
+
268
+ /**
269
+ * Update visibility when editor changes
270
+ */
271
+ public onEditorChanged(): void {
272
+ // This would trigger re-evaluation of button visibility
273
+ // Implementation would depend on how the main extension handles this
274
+ }
275
+
276
+ /**
277
+ * Dispose of resources
278
+ */
279
+ public dispose(): void {
280
+ this.debouncedChecker.dispose();
281
+ this.visibilityCache.clear();
282
+ }
283
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "sourceMap": true,
5
+ "removeComments": false,
6
+ "inlineSourceMap": false,
7
+ "declaration": true,
8
+ "declarationMap": true
9
+ }
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2024",
5
+ "lib": ["ES2024"],
6
+ "outDir": "out",
7
+ "sourceMap": false,
8
+ "strict": true,
9
+ "rootDir": "src",
10
+ "resolveJsonModule": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "declaration": false,
14
+ "declarationMap": false,
15
+ "inlineSourceMap": false,
16
+ "removeComments": true
17
+ },
18
+ "exclude": ["node_modules", ".vscode-test"]
19
+ }