Skip to content

Commit e6082ee

Browse files
authored
Significant speedups of Trajectory and Structure viewers (#96)
* fix missing theme colors in vscode extension + cleanup extension code * fix apply_theme_to_dom not setting color-scheme attribute, causing light form inputs in dark mode - Update tests to ensure error handling for unknown themes and verify color-scheme settings * big performance improvements to StructureScene.svelte (most noticeable in Trajectory.svelte) - new event handlers for playback, pause, step change, and error handling in Trajectory.svelte - new playwright/trajectory-performance.test.ts to guard against FPS, load time and mem usage regressions in Trajectory.svelte - new `performance_mode` prop in `Structure.svelte` to optimize rendering based on structure size - playwright tests for new performance mode prop of Structure.svelte - StructureScene.svelte used InstancedMesh for more efficient rendering of same-species atoms * Trajectory.svelte rename `frame_rate_fps` to `fps` - trajectory-performance.test.ts wait for spinner visibility during loading - trajectory.test.ts consolidate setup code and fix outdated class selectors - structure.test.ts consolidate performance mode tests into parallel running Promise.all() - refactor index.ts simplify rgb_scheme_to_hex function using Object.fromEntries - trajectory.test.ts remove failing fps_range * breaking: rename `Trajectory` to `TrajectoryType` and Svelte component `TrajectoryViewer` to `Trajectory` - update all imports and type annotations * fix Trajectory.svelte not passing x-axis label to Histogram.svelte - remove CSS in HistogramControls.svelte and ScatterPlotControls.svelte, all handled by DraggablePanel.svelte - fix Histogram.svelte dark text in dark mode
1 parent 6dddb65 commit e6082ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2068
-1661
lines changed

extensions/vscode/.vscodeignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Source files
22
src/**
3-
webview/**
43
*.ts
54

65
# Build files

extensions/vscode/readme.md

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# MatterViz VS Code Extension
1+
# [MatterViz VSCode Extension]
2+
3+
[matterviz vscode extension]: https://marketplace.visualstudio.com/items?itemName=janosh.matterviz
24

35
**MatterViz** offers a VSCode extension for rendering crystal structures and molecular dynamics (MD) or geometry optimization trajectories directly in the editor to speed up typical materials science/computational chemistry workflows.
46

@@ -61,9 +63,7 @@ MatterViz automatically registers as a custom editor for trajectory files such a
6163

6264
## ⌨️ Keyboard Shortcuts
6365

64-
| Shortcut | Action |
65-
| ------------------------------ | ------------------------------- |
66-
| `Ctrl+Shift+V` / `Cmd+Shift+V` | Render structure with MatterViz |
66+
- `Ctrl+Shift+V` / `Cmd+Shift+V` → Render structure/trajectory with MatterViz
6767

6868
## 📄 License
6969

@@ -72,25 +72,17 @@ This extension is [MIT-Licensed](./license).
7272
## 🔗 Related Projects
7373

7474
- **✅ MatterViz Web**: [matterviz.janosh.dev](https://matterviz.janosh.dev)
75-
- **✅ pymatviz**: [Jupyter](https://jupyter.org)/[Marimo](https://marimo.io) extension for Python notebooks. See [`pymatviz` readme](https://github.com/janosh/pymatviz/blob/widgets/readme.md#interactive-widgets) for details.
75+
- **✅ pymatviz**: [Jupyter](https://jupyter.org)/[Marimo](https://marimo.io) extension for Python notebooks. Read about widgets in [`pymatviz` readme](https://github.com/janosh/pymatviz/blob/main/readme.md#interactive-widgets) for details.
7676

7777
## 🤝 Contributing
7878

7979
We welcome contributions! Please see our [Contributing Guide](../../contributing.md) for details.
8080

8181
## 🛠️ Development
8282

83-
This extension is built with:
84-
85-
- **TypeScript** - Main extension logic
86-
- **Svelte 5** - Modern reactive webview components
87-
- **Vite** - Fast build tooling
88-
- **Three.js** - 3D visualization engine
89-
90-
### Building from Source
91-
9283
```bash
93-
cd extensions/vscode
84+
git clone https://github.com/janosh/matterviz
85+
cd matterviz/extensions/vscode
9486
pnpm install
9587
pnpm build
9688
vsce package

extensions/vscode/src/extension.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { ThemeName } from '$lib/theme/index'
2-
import { AUTO_THEME, COLOR_THEMES, is_valid_theme_mode } from '$lib/theme/index'
1+
import type { ThemeName } from '$lib/theme'
2+
import { AUTO_THEME, COLOR_THEMES, is_valid_theme_mode } from '$lib/theme'
33
import { is_trajectory_file } from '$lib/trajectory/parse'
44
import * as fs from 'fs'
55
import * as path from 'path'
66
import * as vscode from 'vscode'
7+
78
interface FileData {
89
filename: string
910
content: string
@@ -33,34 +34,27 @@ export const read_file = (file_path: string): FileData => {
3334
// Binary files that should be read as base64
3435
const is_binary = /\.(gz|traj|h5|hdf5)$/.test(filename)
3536

36-
return {
37-
filename,
38-
content: is_binary
39-
? fs.readFileSync(file_path).toString(`base64`)
40-
: fs.readFileSync(file_path, `utf8`),
41-
isCompressed: is_binary,
42-
}
37+
const content = is_binary
38+
? fs.readFileSync(file_path).toString(`base64`)
39+
: fs.readFileSync(file_path, `utf8`)
40+
return { filename, content, isCompressed: is_binary }
4341
}
4442

4543
// Get file data from URI or active editor
4644
export const get_file = (uri?: vscode.Uri): FileData => {
4745
if (uri) return read_file(uri.fsPath)
4846

4947
if (vscode.window.activeTextEditor) {
50-
return {
51-
filename: path.basename(vscode.window.activeTextEditor.document.fileName),
52-
content: vscode.window.activeTextEditor.document.getText(),
53-
isCompressed: false,
54-
}
48+
const filename = path.basename(vscode.window.activeTextEditor.document.fileName)
49+
const content = vscode.window.activeTextEditor.document.getText()
50+
return { filename, content, isCompressed: false }
5551
}
5652

5753
const active_tab = vscode.window.tabGroups.activeTabGroup.activeTab
5854
if (
5955
active_tab?.input && typeof active_tab.input === `object` &&
6056
active_tab.input !== null && `uri` in active_tab.input
61-
) {
62-
return read_file((active_tab.input as { uri: vscode.Uri }).uri.fsPath)
63-
}
57+
) return read_file(active_tab.input.uri.fsPath)
6458

6559
throw new Error(
6660
`No file selected. MatterViz needs an active editor to know what to render.`,
@@ -80,13 +74,9 @@ export const get_theme = (): ThemeName => {
8074
return get_system_theme()
8175
}
8276

83-
// Handle manual theme selection
84-
if (theme_setting !== AUTO_THEME) {
85-
return theme_setting as ThemeName
86-
}
77+
if (theme_setting !== AUTO_THEME) return theme_setting // Handle manual theme selection
8778

88-
// Auto-detect from VSCode color theme
89-
return get_system_theme()
79+
return get_system_theme() // Auto-detect from VSCode color theme
9080
}
9181

9282
// Get system theme based on VSCode's current color theme
@@ -113,17 +103,12 @@ export const create_html = (
113103
const js_uri = webview.asWebviewUri(
114104
vscode.Uri.joinPath(context.extensionUri, `dist`, `webview.js`),
115105
)
116-
const themes_uri = webview.asWebviewUri(
117-
vscode.Uri.joinPath(context.extensionUri, `../../static`, `themes.js`),
118-
)
119106

120107
return `<!DOCTYPE html>
121108
<html>
122109
<head>
123-
<meta charset="UTF-8">
124110
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}' 'unsafe-eval' ${webview.cspSource}; style-src 'unsafe-inline' ${webview.cspSource}; img-src ${webview.cspSource} data:; connect-src ${webview.cspSource}; worker-src blob:;">
125111
<meta name="viewport" content="width=device-width, initial-scale=1.0">
126-
<script nonce="${nonce}" src="${themes_uri}"></script>
127112
<script nonce="${nonce}">window.mattervizData=${JSON.stringify(data)}</script>
128113
</head>
129114
<body>

extensions/vscode/webview/src/main.ts renamed to extensions/vscode/src/webview/main.ts

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import '$lib/app.css'
33
import { parse_structure_file } from '$lib/io/parse'
44
import Structure from '$lib/structure/Structure.svelte'
55
import { is_valid_theme_name, type ThemeName } from '$lib/theme/index'
6+
import '$lib/theme/themes'
67
import { is_trajectory_file, parse_trajectory_data } from '$lib/trajectory/parse'
78
import Trajectory from '$lib/trajectory/Trajectory.svelte'
89
import { mount } from 'svelte'
@@ -103,24 +104,20 @@ const handle_file_change = async (message: FileChangeMessage): Promise<void> =>
103104
const container = document.getElementById(`matterviz-app`)
104105
if (container && current_app) {
105106
current_app.destroy()
106-
current_app = create_display(container, result, result.filename) as MatterVizApp
107+
current_app = create_display(container, result, result.filename)
107108
}
108109

109110
const vscode = get_vscode_api()
110111
if (vscode) {
111-
vscode.postMessage({
112-
command: `info`,
113-
text: `File reloaded successfully`,
114-
})
112+
const text = `File reloaded successfully`
113+
vscode.postMessage({ command: `info`, text })
115114
}
116115
} catch (error) {
117116
console.error(`Failed to reload file:`, error)
118117
const vscode = get_vscode_api()
119118
if (vscode) {
120-
vscode.postMessage({
121-
command: `error`,
122-
text: `Failed to reload file: ${error}`,
123-
})
119+
const text = `Failed to reload file: ${error}`
120+
vscode.postMessage({ command: `error`, text })
124121
}
125122
}
126123
}
@@ -139,22 +136,19 @@ export function base64_to_array_buffer(base64: string): ArrayBuffer {
139136
// Apply theme to the webview DOM
140137
const apply_theme = (theme: ThemeName): void => {
141138
const root = document.documentElement
142-
143139
root.setAttribute(`data-theme`, theme)
144140

145-
// Apply MatterViz theme if available
146-
if (typeof globalThis !== `undefined` && globalThis.MATTERVIZ_THEMES) {
147-
const matterviz_theme = globalThis.MATTERVIZ_THEMES[theme]
148-
const css_map = globalThis.MATTERVIZ_CSS_MAP || {}
141+
if (!globalThis.MATTERVIZ_THEMES) throw new Error(`No themes found`)
149142

150-
if (matterviz_theme) {
151-
for (const [key, value] of Object.entries(matterviz_theme)) {
152-
const css_var = css_map[key as keyof typeof css_map]
153-
if (css_var && value) {
154-
root.style.setProperty(css_var, value as string)
155-
}
156-
}
157-
}
143+
const matterviz_theme = globalThis.MATTERVIZ_THEMES[theme]
144+
if (!matterviz_theme) throw new Error(`Theme ${theme} not found`)
145+
146+
const css_map = globalThis.MATTERVIZ_CSS_MAP || {}
147+
148+
// Apply MatterViz theme CSS variables to root element
149+
for (const [key, value] of Object.entries(matterviz_theme)) {
150+
const css_var = css_map[key]
151+
if (css_var && value) root.style.setProperty(css_var, value)
158152
}
159153
}
160154

@@ -260,16 +254,10 @@ const create_display = (
260254
const is_trajectory = result.type === `trajectory`
261255
const Component = is_trajectory ? Trajectory : Structure
262256
const props = is_trajectory
263-
? {
264-
trajectory: result.data,
265-
show_fullscreen_button: false,
266-
layout: `horizontal`,
267-
}
257+
? { trajectory: result.data, show_fullscreen_button: false, layout: `horizontal` }
268258
: { structure: result.data, fullscreen_toggle: false }
269259

270-
if (!is_trajectory) {
271-
container.style.setProperty(`--struct-height`, `100vh`)
272-
}
260+
if (!is_trajectory) container.style.setProperty(`--struct-height`, `100vh`)
273261

274262
const component = mount(Component, { target: container, props })
275263

@@ -318,9 +306,8 @@ const initialize_app = async (): Promise<MatterVizApp> => {
318306
if (vscode) {
319307
// Listen for file change messages from extension
320308
globalThis.addEventListener(`message`, (event) => {
321-
const message = event.data as FileChangeMessage
322-
if (message.command === `fileUpdated` || message.command === `fileDeleted`) {
323-
handle_file_change(message)
309+
if ([`fileUpdated`, `fileDeleted`].includes(event.data.command)) {
310+
handle_file_change(event.data)
324311
}
325312
})
326313
} else {

0 commit comments

Comments
 (0)