use wsb::prelude::*;
use axum::{
body::{boxed, Full},
extract::{Path, Query, State},
http::{header, StatusCode},
response::Response,
Json,
};
use super::sessions::ErrorResponse;
use wsb::extensions::runtime::{
DiagnosticEntry, ExtensionMessageEntry, ShowDocumentEntry, StatusBarItemEntry, TreeViewData,
};
use wsb::extensions::{ExtensionHost, VsCodeWorkbenchModel, WorkbenchExecutionContext};
use crate::web::state::WebState;
#[derive(Debug, serde::Serialize)]
pub struct WorkspaceWorkbenchModelResponse {
pub model: VsCodeWorkbenchModel,
pub disabled_extension_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ExecuteExtensionCommandRequest {
pub command: String,
#[serde(default)]
pub context: Option<WorkbenchExecutionContext>,
}
#[derive(Debug, serde::Serialize)]
pub struct ExecuteExtensionCommandResponse {
pub command: String,
pub panels: Vec<ExecutedWebviewPanel>,
pub diagnostics: Vec<DiagnosticEntry>,
pub status_bar_items: Vec<StatusBarItemEntry>,
pub tree_views: Vec<TreeViewData>,
pub show_documents: Vec<ShowDocumentEntry>,
pub messages: Vec<ExtensionMessageEntry>,
}
#[derive(Debug, serde::Serialize)]
pub struct ExecutedWebviewPanel {
pub panel_id: String,
pub view_type: String,
pub title: String,
pub html: String,
pub messages: Vec<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub struct ExtensionAssetQuery {
pub path: String,
}
pub async fn get_workbench_model(
State(state): State<WebState>,
) -> Result<Json<WorkspaceWorkbenchModelResponse>, (StatusCode, Json<ErrorResponse>)> {
tokio::task::spawn_blocking(move || {
let host = ExtensionHost::new(state.workspace_root.clone());
let disabled = host.disabled_ids();
let mut disabled_ids: Vec<String> = disabled.iter().cloned().collect();
disabled_ids.sort();
let (mut model, warnings) = host.workbench_model().map_err(internal_error)?;
let (all, _) = host.discover().map_err(internal_error)?;
for ext in &all {
if disabled.contains(&ext.id) {
model
.extensions
.push(wsb::extensions::WorkbenchExtension::from_installed(ext));
}
}
model.extensions.sort_by(|a, b| a.id.cmp(&b.id));
Ok(Json(WorkspaceWorkbenchModelResponse {
model,
disabled_extension_ids: disabled_ids,
warnings,
}))
})
.await
.map_err(|e| internal_error(&format!("spawn_blocking panicked: {e:?}")))?
}
pub async fn execute_extension_command(
State(state): State<WebState>,
Json(request): Json<ExecuteExtensionCommandRequest>,
) -> Result<Json<ExecuteExtensionCommandResponse>, (StatusCode, Json<ErrorResponse>)> {
let host = ExtensionHost::new(state.workspace_root.clone());
let server_base_url = format!("http://127.0.0.1:{}", state.port);
let context = request.context.unwrap_or(WorkbenchExecutionContext {
groups: Vec::new(),
active_group_id: None,
document: None,
});
let execution = host
.execute_command(&request.command, &server_base_url, context)
.map_err(internal_error)?;
Ok(Json(ExecuteExtensionCommandResponse {
command: execution.command,
panels: execution
.panels
.into_iter()
.map(|panel| ExecutedWebviewPanel {
panel_id: panel.panel_id,
view_type: panel.view_type,
title: panel.title,
html: panel.html,
messages: panel.messages,
})
.collect(),
diagnostics: execution.diagnostics,
status_bar_items: execution.status_bar_items,
tree_views: execution.tree_views,
show_documents: execution.show_documents,
messages: execution.messages,
}))
}
pub async fn get_extension_asset(
State(state): State<WebState>,
Path(extension_id): Path<String>,
Query(query): Query<ExtensionAssetQuery>,
) -> Result<Response, (StatusCode, Json<ErrorResponse>)> {
let host = ExtensionHost::new(state.workspace_root.clone());
let asset = host
.read_asset(&extension_id, &query.path)
.map_err(internal_error)?;
let mime = mime_guess::from_path(&asset.path).first_or_octet_stream();
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.as_ref())
.body(boxed(Full::from(asset.bytes)))
.map_err(|error| internal_error(error.to_string()))
}
pub async fn get_extension_readme(
State(state): State<WebState>,
Path(extension_id): Path<String>,
) -> Result<Response, (StatusCode, Json<ErrorResponse>)> {
let host = ExtensionHost::new(state.workspace_root.clone());
let readme = host.read_readme(&extension_id).map_err(internal_error)?;
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/markdown; charset=utf-8")
.body(boxed(Full::from(readme)))
.map_err(|error| internal_error(error.to_string()))
}
fn internal_error(error: impl std::fmt::Display) -> (StatusCode, Json<ErrorResponse>) {
let message = format!("{error:#}");
tracing::error!("{message}");
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse { error: message }),
)
}