diff --git a/client/src/server/protocol.ts b/client/src/server/protocol.ts index 579b225..1287e0c 100644 --- a/client/src/server/protocol.ts +++ b/client/src/server/protocol.ts @@ -60,6 +60,18 @@ export interface AutomapperDetail { configs?: string[] } +export interface Span { + line_start: number + col_start: number + line_end: number + col_end: number +} + +export interface AutomapperDiagnostic { + span: Span + msg: string +} + export type MapCreation = { version: 'ddnet06' | 'teeworlds07' access: 'public' | 'unlisted' @@ -264,7 +276,7 @@ export interface Resp { "create/group": undefined "create/layer": undefined "create/quad": undefined - "create/automapper": undefined + "create/automapper": AutomapperDiagnostic[] "edit/config": undefined "edit/info": undefined "edit/envelope": undefined diff --git a/client/src/ui/lib/editAutomapper.svelte b/client/src/ui/lib/editAutomapper.svelte index 607709c..d2439c9 100644 --- a/client/src/ui/lib/editAutomapper.svelte +++ b/client/src/ui/lib/editAutomapper.svelte @@ -17,6 +17,7 @@ import { basicSetup } from "codemirror" import { EditorState } from "@codemirror/state" import { EditorView, tooltips, keymap } from "@codemirror/view" + import { linter, setDiagnostics } from "@codemirror/lint" import { DDNetRules } from './lang-ddnet_rules/index' import { DDNetRulesLinter } from "./lang-ddnet_rules/lint" import { Rpp } from './lang-rpp/index' @@ -124,11 +125,20 @@ createAmOpen = true } + function defaultFile(kind: AutomapperKind) { + if (kind === AutomapperKind.DDNet) + return '# Automapper tutorial: https://forum.ddnet.org/viewtopic.php?t=2428\n[Sweeper]\nIndex 0' + else if (kind === AutomapperKind.RulesPP) + return '// Rules++ tutorial: https://github.com/Aerll/rpp/wiki/\nAutoMapper("Sweeper");\nNewRun();\nInsert(0);' + else + return '' + } + async function onCreate() { if (!isValidName(newAmName)) return const name = `${newAmName}.${newAmKind}` - const file = '' + const file = defaultFile(newAmKind) newAmName = '' @@ -144,7 +154,19 @@ const id = showInfo('Uploading...', 'none') try { const name = $automappers[selected].name - await $server.query('create/automapper', [name, file]) + const res = await $server.query('create/automapper', [name, file]) + + const diagnostics = res.map(d => { + const line1 = view.state.doc.line(d.span.line_start) + const line2 = view.state.doc.line(d.span.line_end) + return { + from: line1.from + d.span.col_start - 1, + to: line2.from + d.span.col_end - 1, + severity: 'error' as 'error', + message: d.msg + } + }) + view.dispatch(setDiagnostics(view.state, diagnostics)) } finally { clearDialog(id) diff --git a/client/src/ui/lib/lang-ddnet_rules/lint.ts b/client/src/ui/lib/lang-ddnet_rules/lint.ts index 91a65b1..93d6fe5 100644 --- a/client/src/ui/lib/lang-ddnet_rules/lint.ts +++ b/client/src/ui/lib/lang-ddnet_rules/lint.ts @@ -1,4 +1,4 @@ -import { linter} from "@codemirror/lint" +import { linter } from "@codemirror/lint" import { lint as lintAutomapper, @@ -22,4 +22,4 @@ export const DDNetRulesLinter = linter(view => { message, } }) -}) \ No newline at end of file +}) diff --git a/client/src/ui/lib/lang-rpp/lint.ts b/client/src/ui/lib/lang-rpp/lint.ts index 91a65b1..93d6fe5 100644 --- a/client/src/ui/lib/lang-rpp/lint.ts +++ b/client/src/ui/lib/lang-rpp/lint.ts @@ -1,4 +1,4 @@ -import { linter} from "@codemirror/lint" +import { linter } from "@codemirror/lint" import { lint as lintAutomapper, @@ -22,4 +22,4 @@ export const DDNetRulesLinter = linter(view => { message, } }) -}) \ No newline at end of file +}) diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 8264183..e4d31c9 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -52,7 +52,7 @@ async fn server_main() { static_dir: None, rpp_path: None, }; - let server = Arc::new(twwe_server::create_server(&cli).expect("failed to create server")); + let server = Arc::new(twwe_server::create_server(&cli).expect("failed to create the server")); let router = twwe_server::router::Router::new(server, &cli); diff --git a/server/src/cli.rs b/server/src/cli.rs index 73dfa4f..f103db3 100644 --- a/server/src/cli.rs +++ b/server/src/cli.rs @@ -21,8 +21,7 @@ pub struct Cli { pub key: Option, /// path to the maps directories (containing sub-directories containing map.map, config.json etc.) - /// must contain at least one. - #[clap(name = "maps", value_parser, long, default_value = "maps")] + #[clap(name = "maps", value_parser, long)] pub maps_dirs: Vec, /// path to ddnet data directories, if you want to read maps from there. diff --git a/server/src/protocol.rs b/server/src/protocol.rs index f6c35e7..c4ffb91 100644 --- a/server/src/protocol.rs +++ b/server/src/protocol.rs @@ -44,6 +44,8 @@ pub struct MapDetail { pub users: usize, } +// AUTOMAPPERS + #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum AutomapperKind { #[serde(rename = "rules")] @@ -62,6 +64,20 @@ pub struct AutomapperDetail { pub configs: Option>, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Span { + pub line_start: u32, + pub col_start: u32, + pub line_end: u32, + pub col_end: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AutomapperDiagnostic { + pub span: Span, + pub msg: String, +} + // TILES #[derive(Clone, Debug, Serialize, Deserialize)] @@ -452,6 +468,7 @@ pub enum Response { Tiles(Base64), Quad(#[serde_as(as = "Box")] Box), Automappers(Vec), + AutomapperDiagnostics(Vec), Automapper(String), Users(usize), Cursors(HashMap), diff --git a/server/src/server.rs b/server/src/server.rs index 58dd8c0..e2b96ff 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -9,6 +9,7 @@ use axum_tungstenite::Message as WebSocketMessage; use axum_tungstenite::WebSocket; use futures::{channel::mpsc::unbounded, StreamExt, TryStreamExt}; use image::ImageFormat; +use regex::Regex; use crate::{ base64::Base64, @@ -184,15 +185,22 @@ impl Server { }, Request::Create(req) => match req { CreateReq::Image(image_name, create) => { - self.put_image(map_name?, &image_name, create) + { self.put_image(map_name?, &image_name, create) }.map(|()| Response::Ok) } - CreateReq::Envelope(req) => self.put_envelope(map_name?, *req), - CreateReq::Group(req) => self.put_group(map_name?, *req), - CreateReq::Layer(g, req) => self.put_layer(map_name?, g, *req), - CreateReq::Quad(g, l, req) => self.put_quad(map_name?, g, l, *req), - CreateReq::Automapper(am, file) => self.put_automapper(peer, map_name?, &am, &file), - } - .map(|()| Response::Ok), + CreateReq::Envelope(req) => { + self.put_envelope(map_name?, *req).map(|()| Response::Ok) + } + CreateReq::Group(req) => self.put_group(map_name?, *req).map(|()| Response::Ok), + CreateReq::Layer(g, req) => { + self.put_layer(map_name?, g, *req).map(|()| Response::Ok) + } + CreateReq::Quad(g, l, req) => { + self.put_quad(map_name?, g, l, *req).map(|()| Response::Ok) + } + CreateReq::Automapper(am, file) => self + .put_automapper(map_name?, &am, &file) + .map(Response::AutomapperDiagnostics), + }, Request::Edit(req) => match req { EditReq::Config(req) => self.edit_config(map_name?, *req), EditReq::Info(req) => self.edit_info(map_name?, *req), @@ -1308,20 +1316,23 @@ impl Server { let rpp_base_r = rpp_path.join("base.r"); let rpp_base_p = rpp_path.join("base.p"); - let exec = std::process::Command::new(&rpp_exe) - .current_dir(root) - .args([ - "--output", - &out_fname, - "--memory", - "100", - "--include", - &rpp_base_r.to_string_lossy(), - "--include", - &rpp_base_p.to_string_lossy(), - "--no-pause", - &in_fname, - ]) + let mut cmd = std::process::Command::new(&rpp_exe); + cmd.current_dir(root).args([ + "--output", + &out_fname, + "--memory", + "100", + "--include", + &rpp_base_r.to_string_lossy(), + "--include", + &rpp_base_p.to_string_lossy(), + "--no-pause", + &in_fname, + ]); + + log::debug!("rpp: {cmd:?}"); + + let exec = cmd .output() .map_err(|e| Error::ServerError(e.to_string().into()))?; @@ -1342,11 +1353,10 @@ impl Server { pub fn put_automapper( &self, - peer: &Peer, map_name: &str, am: &str, file: &str, - ) -> Result<(), Error> { + ) -> Result, Error> { if !check_file_name(am) { return Err(Error::InvalidFileName); } @@ -1366,8 +1376,8 @@ impl Server { std::fs::write(&path, file).map_err(|e| Error::ServerError(e.to_string().into()))?; if kind == AutomapperKind::RulesPP { - self.compile_rpp(&path) - .and_then(|()| { + match self.compile_rpp(&path) { + Ok(()) => { let target = path.with_extension("rules"); let file = std::fs::read_to_string(&target) .map_err(|e| Error::AutomapperError(e.to_string()))?; @@ -1379,12 +1389,31 @@ impl Server { let message = Message::Request(Request::Create(CreateReq::Automapper(name, file))); self.broadcast_to_room(&room, message); - Ok(()) - }) - .unwrap_or_else(|err| Server::send(peer, None, Message::Response(Err(err)))); + } + Err(Error::AutomapperError(s)) => { + let reg = Regex::new(r"\[(\d+):(\d+)-(\d+):(\d+)\]\s*(.+)").unwrap(); + let diagnostics = s + .split('\n') + .filter_map(|s| { + let caps = reg.captures(s)?; + Some(AutomapperDiagnostic { + span: Span { + line_start: caps[1].parse().ok()?, + col_start: caps[2].parse().ok()?, + line_end: caps[3].parse().ok()?, + col_end: caps[4].parse().ok()?, + }, + msg: caps[5].to_string(), + }) + }) + .collect(); + return Ok(diagnostics); + } + Err(_) => {} + } } - Ok(()) + Ok(vec![]) } pub fn delete_automapper(&self, map_name: &str, am: &str) -> Result<(), Error> {