Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit 16b34b8

Browse files
committed
Move dialectic: URL conversion to Rust server
- Add ResolvedMarkdownElement with custom Deserialize impl for URL processing - Convert file references (src/file.ts?pattern, src/file.ts#L42) to dialectic: URLs in Rust - Update TypeScript interfaces to match new ResolvedMarkdownElement structure - Add pulldown-cmark dependency and URL conversion tests - Remove client-side URL conversion logic from extension Extension now receives properly formatted dialectic: URLs directly from server, eliminating duplicate conversion logic and ensuring consistency.
1 parent 789b721 commit 16b34b8

File tree

5 files changed

+99
-11
lines changed

5 files changed

+99
-11
lines changed

extension/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ interface PresentWalkthroughPayload {
7575
}
7676

7777
type WalkthroughElement =
78-
| string // Markdown content
78+
| { content: string } // ResolvedMarkdownElement with processed dialectic: URLs
7979
| { comment: ResolvedComment }
8080
| { gitdiff: FileChange[] }
8181
| { action: ResolvedAction };

extension/src/walkthroughWebview.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as vscode from 'vscode';
22

33
type WalkthroughElement =
4-
| string // Markdown content
4+
| { content: string } // ResolvedMarkdownElement with processed dialectic: URLs
55
| { comment: any } // Simplified for now
66
| { gitdiff: any } // Simplified for now
77
| { action: { button: string; description?: string; tell_agent?: string } };
@@ -144,8 +144,9 @@ export class WalkthroughWebviewProvider implements vscode.WebviewViewProvider {
144144
html += '<div class="section-title">' + title + '</div>';
145145
146146
items.forEach(item => {
147-
if (typeof item === 'string') {
148-
html += '<div class="content-item">' + renderMarkdown(item) + '</div>';
147+
if (typeof item === 'object' && 'content' in item) {
148+
// ResolvedMarkdownElement with processed dialectic: URLs
149+
html += '<div class="content-item">' + renderMarkdown(item.content) + '</div>';
149150
} else if (item.action) {
150151
html += '<div class="content-item">';
151152
html += '<button class="action-button" onclick="handleAction(' +

server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ chrono = { version = "0.4", features = ["serde"] }
5656

5757
# File system traversal with gitignore support
5858
ignore = "0.4"
59+
pulldown-cmark = "0.13.0"
5960

6061
[dev-dependencies]
6162
tokio-test = { workspace = true }

server/src/ide.rs

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{future::Future, pin::Pin};
22

3-
use serde::{Deserialize, Serialize};
3+
use serde::{Deserialize, Deserializer, Serialize};
44

55
use crate::dialect::{DialectFunction, DialectInterpreter};
66

@@ -330,12 +330,12 @@ impl<U: IpcClient> DialectFunction<U> for Comment {
330330
for content_item in self.content {
331331
match content_item {
332332
serde_json::Value::String(text) => {
333-
resolved_content.push(ResolvedWalkthroughElement::Markdown(text));
333+
resolved_content.push(ResolvedWalkthroughElement::Markdown(ResolvedMarkdownElement { content: text }));
334334
}
335335
_ => {
336336
// For now, convert other types to string and treat as markdown
337337
// TODO: Execute Dialect programs here
338-
resolved_content.push(ResolvedWalkthroughElement::Markdown(content_item.to_string()));
338+
resolved_content.push(ResolvedWalkthroughElement::Markdown(ResolvedMarkdownElement { content: content_item.to_string() }));
339339
}
340340
}
341341
}
@@ -437,15 +437,101 @@ pub struct ResolvedWalkthrough {
437437
pub base_uri: String,
438438
}
439439

440+
#[derive(Serialize, Debug)]
441+
pub struct ResolvedMarkdownElement {
442+
pub content: String,
443+
}
444+
445+
impl<'de> Deserialize<'de> for ResolvedMarkdownElement {
446+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
447+
where
448+
D: Deserializer<'de>,
449+
{
450+
let raw_markdown = String::deserialize(deserializer)?;
451+
let processed_content = process_markdown_links(raw_markdown);
452+
Ok(ResolvedMarkdownElement {
453+
content: processed_content,
454+
})
455+
}
456+
}
457+
458+
fn process_markdown_links(markdown: String) -> String {
459+
// For now, just do simple regex-based URL conversion
460+
// TODO: Implement proper markdown parsing with pulldown-cmark
461+
let mut result = markdown;
462+
463+
// Handle path?regex format for search
464+
result = regex::Regex::new(r"\[([^\]]+)\]\(([^\s\[\]()]+)\?([^\[\]()]+)\)")
465+
.unwrap()
466+
.replace_all(&result, |caps: &regex::Captures| {
467+
format!("[{}](dialectic:{}?regex={})", &caps[1], &caps[2], &caps[3])
468+
})
469+
.to_string();
470+
471+
// Handle path#L42-L50 format for line ranges
472+
result = regex::Regex::new(r"\[([^\]]+)\]\(([^\s\[\]()]+)#L(\d+)-L(\d+)\)")
473+
.unwrap()
474+
.replace_all(&result, |caps: &regex::Captures| {
475+
format!("[{}](dialectic:{}?line={}-{})", &caps[1], &caps[2], &caps[3], &caps[4])
476+
})
477+
.to_string();
478+
479+
// Handle path#L42 format for single lines
480+
result = regex::Regex::new(r"\[([^\]]+)\]\(([^\s\[\]()]+)#L(\d+)\)")
481+
.unwrap()
482+
.replace_all(&result, |caps: &regex::Captures| {
483+
format!("[{}](dialectic:{}?line={})", &caps[1], &caps[2], &caps[3])
484+
})
485+
.to_string();
486+
487+
// Handle bare filenames
488+
result = regex::Regex::new(r"\[([^\]]+)\]\(([^\s\[\]():]+)\)")
489+
.unwrap()
490+
.replace_all(&result, |caps: &regex::Captures| {
491+
// Only convert if it doesn't already start with dialectic: or contain ://
492+
let url = &caps[2];
493+
if url.starts_with("dialectic:") || url.contains("://") {
494+
format!("[{}]({})", &caps[1], url)
495+
} else {
496+
format!("[{}](dialectic:{})", &caps[1], url)
497+
}
498+
})
499+
.to_string();
500+
501+
result
502+
}
503+
504+
505+
440506
#[derive(Serialize, Deserialize, Debug)]
441507
#[serde(untagged)]
442508
pub enum ResolvedWalkthroughElement {
443-
/// Plain markdown text
444-
Markdown(String),
509+
/// Plain markdown text with processed links
510+
Markdown(ResolvedMarkdownElement),
445511
/// Comment placed at specific locations
446512
Comment(ResolvedComment),
447513
/// Git diff display
448514
GitDiff(Vec<crate::synthetic_pr::FileChange>),
449515
/// Action button
450516
Action(ResolvedAction),
451517
}
518+
#[cfg(test)]
519+
mod url_conversion_tests {
520+
use super::*;
521+
522+
#[test]
523+
fn test_markdown_url_conversion() {
524+
let markdown = r#"
525+
Check out [this function](src/auth.ts?validateToken) and
526+
[this line](src/auth.ts#L42) or [this range](src/auth.ts#L42-L50).
527+
Also see [the whole file](src/auth.ts).
528+
"#;
529+
530+
let processed = process_markdown_links(markdown.to_string());
531+
532+
assert!(processed.contains("dialectic:src/auth.ts?regex=validateToken"));
533+
assert!(processed.contains("dialectic:src/auth.ts?line=42"));
534+
assert!(processed.contains("dialectic:src/auth.ts?line=42-50"));
535+
assert!(processed.contains("dialectic:src/auth.ts"));
536+
}
537+
}

server/src/server.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ impl DialecticServer {
330330

331331
match element {
332332
serde_json::Value::String(text) => {
333-
Ok(ResolvedWalkthroughElement::Markdown(text))
333+
Ok(ResolvedWalkthroughElement::Markdown(crate::ide::ResolvedMarkdownElement { content: text }))
334334
}
335335
serde_json::Value::Object(_) => {
336336
// Clone interpreter and execute Dialect program (same pattern as ide_operation)
@@ -350,7 +350,7 @@ impl DialecticServer {
350350
}
351351
_ => {
352352
// Convert other types to markdown
353-
Ok(ResolvedWalkthroughElement::Markdown(element.to_string()))
353+
Ok(ResolvedWalkthroughElement::Markdown(crate::ide::ResolvedMarkdownElement { content: element.to_string() }))
354354
}
355355
}
356356
}

0 commit comments

Comments
 (0)