Continue markdown preview implementation
The markdown preview feature is now complete. Here's a summary of what was implemented in this session:
Summary of Changes
Phase 4: Scroll Synchronization
- Added
sync_preview_scroll()method toAppModel(src/model/mod.rs) - Hooked scroll sync into
ensure_cursor_visible()andensure_cursor_visible_directional()methods - Added scroll sync call in
EditorMsg::Scrollhandler - Implemented
PreviewMsg::ScrollToLine,PreviewMsg::SyncFromPreview, andPreviewMsg::ToggleSynchandlers (src/update/preview.rs)
Phase 5: Polish
- Added
close_preview_if_not_markdown()helper to close previews when switching to non-markdown files (src/update/layout.rs) - Hooked preview close logic into
NextTab,PrevTab, andSwitchToTabhandlers - Preview auto-refreshes on document edit (reads directly from document buffer each render)
Documentation
- Updated docs/feature/markdown-preview.md to mark all phases as completed
Feature Usage
- Toggle markdown preview:
Cmd+Shift+V(opens a side-by-side preview pane) - Scroll sync is enabled by default (preview follows source scroll)
- Preview automatically closes when switching to a non-markdown file
The markdown preview command is now available in the command palette. Users can:
- Press
Cmd+Shift+Ato open the command palette - Type "markdown" or "preview" to find "Markdown: Toggle Preview"
- Press Enter to toggle the preview
The keybinding ⇧⌘V is also shown in the command palette entry.
I can see the issue. While wry is in Cargo.toml, the current implementation uses basic native text rendering instead of a real webview. The markdown_to_html function is unused (line 670-671 explicitly suppresses the warning).
Let me investigate what would be needed for proper webview integration with wry:
Now I understand. The issue is that wry with winit requires:
- Creating a separate
WebViewwidget that renders into the window - Calling
build_as_child(&window)to attach the webview to the window - Handling resize events to update webview bounds
- On Linux, calling
gtk::main_iteration_doin the event loop
The problem is that Token uses softbuffer for rendering (CPU-based pixel buffer rendering), not a compositor that can overlay a webview. Wry's webview is a native OS widget that needs to be composited on top of the window content.
Let me check the current architecture:
Now I understand the architectural challenge. Token uses:
winitfor window managementsoftbufferfor CPU-based pixel rendering (the entire window is a pixel buffer)
To integrate wry webview, we need to either:
- Create a child webview - The webview would overlay part of the window
- Manage bounds - Keep the webview's position/size in sync with the preview pane's rect
The key insight is that wry::WebViewBuilder::build_as_child(&window) creates a native child window that overlays the parent. This means we need to:
- Create the webview when opening preview
- Position it to match the preview pane's rect
- Update its bounds whenever the layout changes
- Update its HTML content when the document changes
- Destroy it when closing preview
Let me create a detailed analysis and implementation plan:
Let me create the webview manager structure. First, let me check the runtime app structure more:
Now let me create the webview manager module. This will manage wry WebViews for markdown previews:
Now update the runtime module to include the webview manager:
Now let me add the WebviewManager to the App struct:
Now I need to initialize the WebviewManager in App::new(). Let me find where the struct is returned:
Now let's build to check for errors:
Good, it compiles with just warnings about unused code. Now I need to integrate the webview manager into the rendering/update flow. The key integration points are:
- When preview opens - Create webview with rendered HTML
- When document edits - Update webview content
- When layout changes - Update webview bounds
- When preview closes - Destroy webview
Let me look at where the render happens and find where to hook the webview sync:
I see the render function and process_cmd. The best approach is to sync webviews after rendering. Let me add a sync method that will:
- Create webviews for new previews
- Update content/bounds for existing webviews
- Remove webviews for closed previews
Now let's build to check for errors:
It compiles. However, there's a subtle issue: the last_revision on the preview pane is never updated, so needs_refresh always returns true. Let me also update the preview's last_revision when we update content. But first, I realize we need to track this properly.
Also, the native rendering in view/mod.rs is still happening alongside the webview. When webview is active, we should skip the native rendering for that preview pane.
Let me update the view to skip native rendering when webview is being used:
Let me think about this more carefully. The webview is a native OS widget that overlays the softbuffer-rendered content. This means:
- The softbuffer still renders the whole window (including preview area background)
- The webview overlays on top of it
So the native rendering code should stay as a fallback for when webview fails or isn't available. However, we should still render a background for the preview area.
Actually, the current approach should work - the webview will overlay and hide whatever we render underneath. Let me update the last_revision tracking so we don't update HTML on every frame. I need to update the preview pane's last_revision after updating content:
The edit was the same - I need to think about this differently. We need to update preview.last_revision after we update the content, but since we're iterating over &self.model.editor_area.previews, we can't mutate. Let me restructure:
It builds. Now let's run the tests:
All tests pass. Now let me update the todo and run lint:
Let me fix these lint errors:
Now let me update the design doc with the webview implementation details:
The webview integration is now complete. Here's a summary of what was implemented:
Summary: Webview Integration for Markdown Preview
New Files Created
- src/runtime/webview.rs -
WebviewManagerstruct that manageswry::WebViewinstances
Key Changes
WebviewManager (webview.rs):
create_webview()- Creates a wry child webview with HTML contentupdate_content()- Updates webview HTML via JavaScript injectionupdate_bounds()- Repositions webview when layout changesclose_webview()- Removes webview when preview closesscroll_to_line()- For scroll synchronization (prepared for future use)
App Integration (app.rs):
- Added
webview_manager: WebviewManagerfield toAppstruct - Added
sync_webviews()method called after each render - Syncs webviews with model's preview panes:
- Creates webviews for new previews
- Updates bounds on resize
- Updates HTML content when document revision changes
- Destroys webviews when previews close
How It Works
- User presses
Cmd+Shift+V→PreviewMsg::Toggle→ Preview pane created in model - On next render →
sync_webviews()detects new preview → Createswry::WebViewwith rendered HTML - User edits document → Document revision increments →
sync_webviews()updates webview content - User closes preview → Preview removed from model →
sync_webviews()destroys webview
Architecture
- Native rendering still happens via softbuffer (fallback/background)
- Webview overlays on top as a native OS child window
- Uses
wry::WebViewBuilder::build_as_child()to embed in winit window