Skip to content

Commit 18a5ca7

Browse files
authored
Restructure extension. (#2)
1 parent b66606a commit 18a5ca7

File tree

10 files changed

+694
-682
lines changed

10 files changed

+694
-682
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ repos:
2020
files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx
2121
types: [file]
2222
additional_dependencies:
23-
- eslint
24-
- typescript
25-
- '@typescript-eslint/parser'
26-
- '@typescript-eslint/eslint-plugin'
23+
- eslint@8.56.0
24+
- '@typescript-eslint/parser@6.18.1'
25+
- '@typescript-eslint/eslint-plugin@6.18.1'
26+
- '@eslint/js@8.56.0'
2727
- repo: https://github.com/lyz-code/yamlfix
2828
rev: 1.17.0
2929
hooks:

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"printWidth": 100,
3+
"singleQuote": false
4+
}

eslint.config.mjs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
1-
import js from '@eslint/js';
2-
import typescript from '@typescript-eslint/eslint-plugin';
3-
import tsParser from '@typescript-eslint/parser';
1+
import js from "@eslint/js";
2+
import typescript from "@typescript-eslint/eslint-plugin";
3+
import tsParser from "@typescript-eslint/parser";
44

55
export default [
66
js.configs.recommended,
77
{
8-
files: ['**/*.ts'],
8+
files: ["**/*.ts"],
99
languageOptions: {
1010
parser: tsParser,
1111
parserOptions: {
12-
ecmaVersion: 'latest',
13-
sourceType: 'module',
12+
ecmaVersion: "latest",
13+
sourceType: "module",
1414
},
1515
globals: {
16-
console: 'readonly',
17-
process: 'readonly',
18-
__dirname: 'readonly',
19-
setTimeout: 'readonly',
16+
console: "readonly",
17+
process: "readonly",
18+
__dirname: "readonly",
19+
setTimeout: "readonly",
2020
},
2121
},
2222
plugins: {
23-
'@typescript-eslint': typescript,
23+
"@typescript-eslint": typescript,
2424
},
2525
rules: {
26-
...typescript.configs['recommended'].rules,
26+
...typescript.configs["recommended"].rules,
2727
},
2828
},
2929
{
30-
files: ['**/test/**/*.ts'],
30+
files: ["**/test/**/*.ts"],
3131
languageOptions: {
3232
globals: {
33-
suite: 'readonly',
34-
test: 'readonly',
35-
teardown: 'readonly',
36-
suiteSetup: 'readonly',
37-
suiteTeardown: 'readonly',
38-
setup: 'readonly',
33+
suite: "readonly",
34+
test: "readonly",
35+
teardown: "readonly",
36+
suiteSetup: "readonly",
37+
suiteTeardown: "readonly",
38+
setup: "readonly",
3939
},
4040
},
4141
rules: {
42-
'@typescript-eslint/no-unused-vars': 'off',
42+
"@typescript-eslint/no-unused-vars": "off",
4343
},
4444
},
4545
];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"vscode:prepublish": "npm run compile",
3737
"compile": "tsc -p ./",
3838
"watch": "tsc -watch -p ./",
39-
"pretest": "npm run compile && npm run lint && npm run format",
39+
"pretest": "rm -rf out && npm run compile && npm run lint && npm run format",
4040
"lint": "eslint src",
4141
"test": "node ./out/test/runTest.js",
4242
"format": "prettier --write \"src/**/*.{ts,js,json,md}\""

src/extension.ts

Lines changed: 3 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -1,280 +1,8 @@
1-
import * as vscode from 'vscode';
2-
import * as path from 'path';
3-
import * as fs from 'fs';
4-
5-
type TreeItemType = FolderItem | ModuleItem | TaskItem;
6-
7-
class FolderItem extends vscode.TreeItem {
8-
constructor(
9-
public readonly label: string,
10-
public readonly children: (FolderItem | ModuleItem)[] = [],
11-
public readonly folderPath: string
12-
) {
13-
super(label, vscode.TreeItemCollapsibleState.Expanded);
14-
this.contextValue = 'folder';
15-
this.iconPath = new vscode.ThemeIcon('folder');
16-
this.tooltip = folderPath;
17-
}
18-
}
19-
20-
class ModuleItem extends vscode.TreeItem {
21-
constructor(
22-
public readonly label: string,
23-
public readonly children: TaskItem[] = [],
24-
public readonly filePath: string
25-
) {
26-
super(label, vscode.TreeItemCollapsibleState.Collapsed);
27-
this.contextValue = 'module';
28-
this.iconPath = new vscode.ThemeIcon('symbol-file');
29-
this.tooltip = filePath;
30-
this.command = {
31-
command: 'vscode.open',
32-
title: 'Open Module File',
33-
arguments: [vscode.Uri.file(filePath)],
34-
};
35-
}
36-
}
37-
38-
class TaskItem extends vscode.TreeItem {
39-
constructor(
40-
public readonly label: string,
41-
public readonly filePath: string,
42-
public readonly lineNumber: number
43-
) {
44-
super(label, vscode.TreeItemCollapsibleState.None);
45-
this.contextValue = 'task';
46-
this.iconPath = new vscode.ThemeIcon('symbol-method');
47-
this.tooltip = `${this.label} - ${path.basename(this.filePath)}:${this.lineNumber}`;
48-
this.description = path.basename(this.filePath);
49-
this.command = {
50-
command: 'vscode.open',
51-
title: 'Open Task File',
52-
arguments: [
53-
vscode.Uri.file(this.filePath),
54-
{
55-
selection: new vscode.Range(
56-
new vscode.Position(this.lineNumber - 1, 0),
57-
new vscode.Position(this.lineNumber - 1, 0)
58-
),
59-
},
60-
],
61-
};
62-
}
63-
}
64-
65-
export class PyTaskProvider implements vscode.TreeDataProvider<TreeItemType> {
66-
private _onDidChangeTreeData: vscode.EventEmitter<TreeItemType | undefined | null | void> =
67-
new vscode.EventEmitter<TreeItemType | undefined | null | void>();
68-
readonly onDidChangeTreeData: vscode.Event<TreeItemType | undefined | null | void> =
69-
this._onDidChangeTreeData.event;
70-
private fileSystemWatcher: vscode.FileSystemWatcher;
71-
72-
constructor() {
73-
this.fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/task_*.py');
74-
75-
this.fileSystemWatcher.onDidCreate(() => {
76-
this.refresh();
77-
});
78-
79-
this.fileSystemWatcher.onDidChange(() => {
80-
this.refresh();
81-
});
82-
83-
this.fileSystemWatcher.onDidDelete(() => {
84-
this.refresh();
85-
});
86-
}
87-
88-
dispose() {
89-
this.fileSystemWatcher.dispose();
90-
}
91-
92-
refresh(): void {
93-
this._onDidChangeTreeData.fire();
94-
}
95-
96-
getTreeItem(element: TreeItemType): vscode.TreeItem {
97-
return element;
98-
}
99-
100-
async getChildren(element?: TreeItemType): Promise<TreeItemType[]> {
101-
if (!element) {
102-
return this.buildFileTree();
103-
}
104-
105-
if (element instanceof FolderItem) {
106-
return element.children;
107-
}
108-
109-
if (element instanceof ModuleItem) {
110-
return element.children;
111-
}
112-
113-
return [];
114-
}
115-
116-
private async buildFileTree(): Promise<TreeItemType[]> {
117-
const workspaceFolders = vscode.workspace.workspaceFolders;
118-
if (!workspaceFolders) {
119-
return [];
120-
}
121-
122-
const rootItems = new Map<string, TreeItemType>();
123-
124-
// Get all task modules across the workspace
125-
const taskFiles = await vscode.workspace.findFiles(
126-
'**/task_*.py',
127-
'{**/node_modules/**,**/.venv/**,**/.git/**,**/.pixi/**,**/venv/**,**/__pycache__/**}'
128-
);
129-
130-
// Process each task module
131-
for (const taskFile of taskFiles) {
132-
const relativePath = path.relative(workspaceFolders[0].uri.fsPath, taskFile.fsPath);
133-
const dirPath = path.dirname(relativePath);
134-
const fileName = path.basename(taskFile.fsPath);
135-
136-
// Create folder hierarchy
137-
let currentPath = '';
138-
let currentItems = rootItems;
139-
const pathParts = dirPath.split(path.sep);
140-
141-
// Skip if it's in the root
142-
if (dirPath !== '.') {
143-
for (const part of pathParts) {
144-
currentPath = currentPath ? path.join(currentPath, part) : part;
145-
const fullPath = path.join(workspaceFolders[0].uri.fsPath, currentPath);
146-
147-
if (!currentItems.has(currentPath)) {
148-
const newFolder = new FolderItem(part, [], fullPath);
149-
currentItems.set(currentPath, newFolder);
150-
}
151-
152-
const folderItem = currentItems.get(currentPath);
153-
if (folderItem instanceof FolderItem) {
154-
currentItems = new Map(
155-
folderItem.children
156-
.filter((child) => child instanceof FolderItem)
157-
.map((child) => [path.basename(child.label), child as FolderItem])
158-
);
159-
}
160-
}
161-
}
162-
163-
// Create module and its tasks
164-
const content = fs.readFileSync(taskFile.fsPath, 'utf8');
165-
const taskItems = this.findTaskFunctions(taskFile.fsPath, content);
166-
const moduleItem = new ModuleItem(fileName, taskItems, taskFile.fsPath);
167-
168-
// Add module to appropriate folder or root
169-
if (dirPath === '.') {
170-
rootItems.set(fileName, moduleItem);
171-
} else {
172-
const parentFolder = rootItems.get(dirPath);
173-
if (parentFolder instanceof FolderItem) {
174-
parentFolder.children.push(moduleItem);
175-
}
176-
}
177-
}
178-
179-
// Sort everything
180-
const result = Array.from(rootItems.values());
181-
182-
// Sort folders and modules
183-
result.sort((a, b) => {
184-
// Folders come before modules
185-
if (a instanceof FolderItem && !(b instanceof FolderItem)) return -1;
186-
if (!(a instanceof FolderItem) && b instanceof FolderItem) return 1;
187-
// Alphabetical sort within same type
188-
return a.label.localeCompare(b.label);
189-
});
190-
191-
return result;
192-
}
193-
194-
findTaskFunctions(filePath: string, content: string): TaskItem[] {
195-
// Find out whether the task decorator is used in the file.
196-
197-
// Booleans to track if the task decorator is imported as `from pytask import task`
198-
// and used as `@task` or `import pytask` and used as `@pytask.task`.
199-
let hasTaskImport = false;
200-
let taskAlias = 'task'; // default name for 'from pytask import task'
201-
let pytaskAlias = 'pytask'; // default name for 'import pytask'
202-
let hasPytaskImport = false;
203-
204-
// Match the import statements
205-
// Handle various import patterns:
206-
// - from pytask import task
207-
// - from pytask import task as t
208-
// - from pytask import Product, task
209-
// - from pytask import (Product, task)
210-
const fromPytaskImport = content.match(
211-
/from\s+pytask\s+import\s+(?:\(?\s*(?:[\w]+\s*,\s*)*task(?:\s+as\s+(\w+))?(?:\s*,\s*[\w]+)*\s*\)?)/
212-
);
213-
const importPytask = content.match(/import\s+pytask(?:\s+as\s+(\w+))?\s*$/m);
214-
215-
if (fromPytaskImport) {
216-
hasTaskImport = true;
217-
if (fromPytaskImport[1]) {
218-
taskAlias = fromPytaskImport[1];
219-
}
220-
}
221-
222-
if (importPytask) {
223-
hasPytaskImport = true;
224-
// If there's an alias (import pytask as something), use it
225-
pytaskAlias = importPytask[1] || 'pytask';
226-
}
227-
228-
// Find the tasks.
229-
const tasks: TaskItem[] = [];
230-
const lines = content.split('\n');
231-
232-
let isDecorated = false;
233-
for (let i = 0; i < lines.length; i++) {
234-
const line = lines[i].trim();
235-
236-
// Check for decorators
237-
if (line.startsWith('@')) {
238-
// Handle both @task and @pytask.task(...) patterns
239-
isDecorated =
240-
(hasTaskImport && line === `@${taskAlias}`) ||
241-
(hasPytaskImport && line.startsWith(`@${pytaskAlias}.task`));
242-
continue;
243-
}
244-
245-
// Check for function definitions
246-
const funcMatch = line.match(/^def\s+(\w+)\s*\(/);
247-
if (funcMatch) {
248-
const funcName = funcMatch[1];
249-
// Add if it's a task_* function or has a task decorator
250-
if (funcName.startsWith('task_') || isDecorated) {
251-
tasks.push(new TaskItem(funcName, filePath, i + 1));
252-
}
253-
isDecorated = false; // Reset decorator flag
254-
}
255-
}
256-
257-
// Sort the tasks by name.
258-
tasks.sort((a, b) => a.label.localeCompare(b.label));
259-
260-
return tasks;
261-
}
262-
}
1+
import * as vscode from "vscode";
2+
import { activate as activateTaskProvider } from "./providers/taskProvider";
2633

2644
export function activate(context: vscode.ExtensionContext) {
265-
const pytaskProvider = new PyTaskProvider();
266-
const treeView = vscode.window.createTreeView('pytaskExplorer', {
267-
treeDataProvider: pytaskProvider,
268-
showCollapseAll: true,
269-
});
270-
271-
context.subscriptions.push(treeView);
272-
273-
const refreshCommand = vscode.commands.registerCommand('pytask.refresh', () => {
274-
pytaskProvider.refresh();
275-
});
276-
277-
context.subscriptions.push(refreshCommand, pytaskProvider);
5+
activateTaskProvider(context);
2786
}
2797

2808
export function deactivate() {}

0 commit comments

Comments
 (0)