feat(story-193): clickable code references in frontend
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||
use crate::store::StoreOps;
|
||||
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
|
||||
use poem_openapi::{Object, OpenApi, Tags, param::Query, payload::Json};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
@@ -22,6 +22,11 @@ struct EditorCommandResponse {
|
||||
editor_command: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Object, Serialize)]
|
||||
struct OpenFileResponse {
|
||||
success: bool,
|
||||
}
|
||||
|
||||
pub struct SettingsApi {
|
||||
pub ctx: Arc<AppContext>,
|
||||
}
|
||||
@@ -39,6 +44,32 @@ impl SettingsApi {
|
||||
Ok(Json(EditorCommandResponse { editor_command }))
|
||||
}
|
||||
|
||||
/// Open a file in the configured editor at the given line number.
|
||||
///
|
||||
/// Invokes the stored editor CLI (e.g. "zed", "code") with `path:line` as the argument.
|
||||
/// Returns an error if no editor is configured or if the process fails to spawn.
|
||||
#[oai(path = "/settings/open-file", method = "post")]
|
||||
async fn open_file(
|
||||
&self,
|
||||
path: Query<String>,
|
||||
line: Query<Option<u32>>,
|
||||
) -> OpenApiResult<Json<OpenFileResponse>> {
|
||||
let editor_command = get_editor_command_from_store(&self.ctx)
|
||||
.ok_or_else(|| bad_request("No editor configured".to_string()))?;
|
||||
|
||||
let file_ref = match line.0 {
|
||||
Some(l) => format!("{}:{}", path.0, l),
|
||||
None => path.0.clone(),
|
||||
};
|
||||
|
||||
std::process::Command::new(&editor_command)
|
||||
.arg(&file_ref)
|
||||
.spawn()
|
||||
.map_err(|e| bad_request(format!("Failed to open editor: {e}")))?;
|
||||
|
||||
Ok(Json(OpenFileResponse { success: true }))
|
||||
}
|
||||
|
||||
/// Set the preferred editor command (e.g. "zed", "code", "cursor").
|
||||
/// Pass null or empty string to clear the preference.
|
||||
#[oai(path = "/settings/editor", method = "put")]
|
||||
@@ -275,4 +306,64 @@ mod tests {
|
||||
.0;
|
||||
assert!(result.editor_command.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn open_file_returns_error_when_no_editor_configured() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let api = make_api(&dir);
|
||||
let result = api
|
||||
.open_file(Query("src/main.rs".to_string()), Query(Some(42)))
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(err.status(), poem::http::StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn open_file_spawns_editor_with_path_and_line() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let api = make_api(&dir);
|
||||
// Configure the editor to "echo" which is a safe no-op command
|
||||
api.set_editor(Json(EditorCommandPayload {
|
||||
editor_command: Some("echo".to_string()),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
let result = api
|
||||
.open_file(Query("src/main.rs".to_string()), Query(Some(42)))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.0.success);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn open_file_spawns_editor_with_path_only_when_no_line() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let api = make_api(&dir);
|
||||
api.set_editor(Json(EditorCommandPayload {
|
||||
editor_command: Some("echo".to_string()),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
let result = api
|
||||
.open_file(Query("src/lib.rs".to_string()), Query(None))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.0.success);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn open_file_returns_error_for_nonexistent_editor() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let api = make_api(&dir);
|
||||
api.set_editor(Json(EditorCommandPayload {
|
||||
editor_command: Some("this_editor_does_not_exist_xyz_abc".to_string()),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
let result = api
|
||||
.open_file(Query("src/main.rs".to_string()), Query(Some(1)))
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user