Skip to content
1 change: 0 additions & 1 deletion .claude/skills
Submodule skills deleted from a3d61b
1 change: 1 addition & 0 deletions __mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ export const workspace = {
.mockName('createFileSystemWatcher')
.mockReturnValue({
onDidCreate: vi.fn().mockName('onDidCreate'),
onDidChange: vi.fn().mockName('onDidChange'),
onDidDelete: vi.fn().mockName('onDidDelete'),
}),
fs: {
Expand Down
54 changes: 54 additions & 0 deletions docs/python-remote-file-sourcing.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,57 @@ def dashboard_content(table):
2. In your VS Code workspace, use the Deephaven extension to run `main.py`. The imports will resolve because the `stock_ticker` folder is registered as a remote file source.

![Run main.py](assets/run-main-py.gif)

## Controller Import Prefix Support (Enterprise)

When using **Deephaven Enterprise** with controller-scoped imports, you can configure the extension to automatically send prefixed module names to the server. This is useful when your server environment expects modules to be imported under a controller prefix (e.g., `controller.mymodule` in addition to `mymodule`).

### Configuration

Add the following to any Python file in your workspace to enable the default `controller` prefix:

```python
import deephaven_enterprise.controller_import
deephaven_enterprise.controller_import.meta_import()
```

To use a custom prefix instead, pass it as an argument:

```python
import deephaven_enterprise.controller_import
deephaven_enterprise.controller_import.meta_import("myprefix")
```

### Behavior

When controller import prefix support is configured:

- Both the unprefixed and prefixed module names are sent to the Deephaven server.
- Example: If you mark a folder called `mymodule` and configure with prefix `controller`, the server will receive both `mymodule` and `controller.mymodule`.
- Without any configuration, only the unprefixed name (`mymodule`) is sent — this is the default behavior.
- The configuration is **auto-detected** whenever `.py` files are saved in your workspace; no manual setup is required beyond adding the `meta_import()` call.

### Supported Import Patterns

The extension detects the following patterns:

1. **Direct import and call:**

```python
import deephaven_enterprise.controller_import
deephaven_enterprise.controller_import.meta_import()
```

2. **From import and call:**

```python
from deephaven_enterprise.controller_import import meta_import
meta_import("custom")
```

### Limitations

- **Import aliases are not supported.** Patterns such as `import deephaven_enterprise.controller_import as ci` or `from deephaven_enterprise.controller_import import meta_import as m` will not be detected.
- **Only one configuration per workspace is supported.** If multiple `.py` files contain a `meta_import()` call with different prefixes, the first match found will be used.

If your use case requires support for additional patterns, please open an issue.
9 changes: 8 additions & 1 deletion src/controllers/ExtensionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
DheServiceCache,
FilteredWorkspace,
RemoteFileSourceService,
PythonControllerImportScanner,
PanelService,
ParsedDocumentCache,
PYTHON_FILE_PATTERN,
Expand Down Expand Up @@ -406,9 +407,15 @@ export class ExtensionController implements IDisposable {
);
this._context.subscriptions.push(this._pythonWorkspace);

const controllerImportScanner = new PythonControllerImportScanner(
this._pythonWorkspace
);
this._context.subscriptions.push(controllerImportScanner);

this._remoteFileSourceService = new RemoteFileSourceService(
this._groovyWorkspace,
this._pythonWorkspace
this._pythonWorkspace,
controllerImportScanner
);
this._context.subscriptions.push(this._remoteFileSourceService);
};
Expand Down
43 changes: 40 additions & 3 deletions src/services/FilteredWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,24 @@ export class FilteredWorkspace<
this.disposables.add(vscode.window.registerFileDecorationProvider(this));

const watcher = vscode.workspace.createFileSystemWatcher(filePattern);
this.disposables.add(watcher.onDidCreate(() => this.refresh()));
this.disposables.add(
watcher.onDidChange(uri => this._handleFileContentChange(uri))
watcher.onDidCreate(uri => {
this._onDidChangeFile.fire({ type: 'create', uri });
this.refresh();
})
);
this.disposables.add(
watcher.onDidChange(uri => {
this._onDidChangeFile.fire({ type: 'change', uri });
this._handleFileContentChange(uri);
})
);
this.disposables.add(
watcher.onDidDelete(uri => {
this._onDidChangeFile.fire({ type: 'delete', uri });
this.refresh();
})
);
this.disposables.add(watcher.onDidDelete(() => this.refresh()));
this.disposables.add(watcher);

// TODO: Load marked folders from storage DH-20573
Expand All @@ -96,6 +109,12 @@ export class FilteredWorkspace<
private _onDidUpdate = new vscode.EventEmitter<void>();
readonly onDidUpdate = this._onDidUpdate.event;

private _onDidChangeFile = new vscode.EventEmitter<{
type: 'create' | 'change' | 'delete';
uri: vscode.Uri;
}>();
readonly onDidChangeFile = this._onDidChangeFile.event;

private readonly _childNodeMap = new URIMap<URIMap<FilteredWorkspaceNode>>();
private readonly _parentUriMap = new URIMap<vscode.Uri | null>();
private readonly _nodeMap = new URIMap<FilteredWorkspaceNode>();
Expand Down Expand Up @@ -299,6 +318,18 @@ export class FilteredWorkspace<
return this._wsFileUriMap;
}

/**
* Get all file URIs from all workspace folders.
* @returns An array of all file URIs.
*/
getAllFileUris(): vscode.Uri[] {
const allFiles: vscode.Uri[] = [];
for (const fileUris of this._wsFileUriMap.values()) {
allFiles.push(...fileUris);
}
return allFiles;
}

/**
* Check if a given parent URI has child nodes.
* @param parentUri The parent URI to check.
Expand Down Expand Up @@ -554,4 +585,10 @@ export class FilteredWorkspace<
childMap.set(node.uri, node as FilteredWorkspaceNode);
}
}

protected override async onDisposing(): Promise<void> {
this._onDidChangeFileDecorations.dispose();
this._onDidUpdate.dispose();
this._onDidChangeFile.dispose();
}
}
Loading
Loading