diff --git a/playwright.config.ts b/playwright.config.ts index 4b5c7c43d..b4e60f4e8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -32,17 +32,17 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ - { - name: 'Google Chrome', - use: { - ...devices['Desktop Chrome'], - channel: 'chrome', - contextOptions: { - /* Chromium is the only one with these permission types */ - permissions: ['clipboard-write', 'clipboard-read'], - }, - }, // or 'chrome-beta' - }, + // { + // name: 'Google Chrome', + // use: { + // ...devices['Desktop Chrome'], + // channel: 'chrome', + // contextOptions: { + // /* Chromium is the only one with these permission types */ + // permissions: ['clipboard-write', 'clipboard-read'], + // }, + // }, // or 'chrome-beta' + // }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, diff --git a/src/components/AstExplorer.tsx b/src/components/AstExplorer.tsx index 93665cd9f..548e26535 100644 --- a/src/components/AstExplorer.tsx +++ b/src/components/AstExplorer.tsx @@ -11,6 +11,7 @@ export function AstExplorer() { kclManager.ast, context.selectionRanges.codeBasedSelections?.[0]?.range ) + console.log('demo', pathToNode) const [filterKeys, setFilterKeys] = useState(['start', 'end']) const _node = getNodeFromPath(kclManager.ast, pathToNode) diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts index d03f3e6ec..ba34303d5 100644 --- a/src/lang/std/engineConnection.ts +++ b/src/lang/std/engineConnection.ts @@ -1986,9 +1986,21 @@ export class EngineCommandManager extends EventTarget { async sendModelingCommandFromWasm( id: string, rangeStr: string, + pathToNodeStr: string, commandStr: string, idToRangeStr: string ): Promise { + // console.log('yoo', id, + // rangeStr, + // pathToNodeStr, + // commandStr, + // idToRangeStr) + console.log( + 'pathToNodeStr', + pathToNodeStr, + JSON.parse(commandStr), + JSON.parse(idToRangeStr) + ) if (this.engineConnection === undefined) { return Promise.resolve() } diff --git a/src/wasm-lib/derive-docs/src/lib.rs b/src/wasm-lib/derive-docs/src/lib.rs index 790b367df..09225d715 100644 --- a/src/wasm-lib/derive-docs/src/lib.rs +++ b/src/wasm-lib/derive-docs/src/lib.rs @@ -456,7 +456,7 @@ fn do_stdlib_inner( if !errors.is_empty() { errors.insert(0, Error::new_spanned(&ast.sig, "")); } - + println!("{}", stream); Ok((stream, errors)) } @@ -802,6 +802,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr .send_modeling_cmd( uuid::Uuid::new_v4(), crate::executor::SourceRange::default(), + crate::executor::PathToNode::default(), kittycad::types::ModelingCmd::ZoomToFit { object_ids: Default::default(), padding: 0.1, @@ -815,6 +816,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr .send_modeling_cmd( uuid::Uuid::new_v4(), crate::executor::SourceRange::default(), + crate::executor::PathToNode::default(), kittycad::types::ModelingCmd::TakeSnapshot { format: kittycad::types::ImageFormat::Png, }, diff --git a/src/wasm-lib/kcl/src/ast/modify.rs b/src/wasm-lib/kcl/src/ast/modify.rs index acb41a38a..eeef26141 100644 --- a/src/wasm-lib/kcl/src/ast/modify.rs +++ b/src/wasm-lib/kcl/src/ast/modify.rs @@ -9,7 +9,7 @@ use crate::{ }, engine::EngineManager, errors::{KclError, KclErrorDetails}, - executor::{Point2d, SourceRange}, + executor::{PathToNode, Point2d, SourceRange}, }; #[derive(Debug)] @@ -77,6 +77,7 @@ pub async fn modify_ast_for_sketch( .send_modeling_cmd( uuid::Uuid::new_v4(), SourceRange::default(), + PathToNode::default(), ModelingCmd::PathGetInfo { path_id: sketch_id }, ) .await?; @@ -101,6 +102,7 @@ pub async fn modify_ast_for_sketch( let h = engine.send_modeling_cmd( uuid::Uuid::new_v4(), SourceRange::default(), + PathToNode::default(), ModelingCmd::CurveGetControlPoints { curve_id: *command_id }, ); diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index 8ea90b690..10befa39f 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -1307,6 +1307,7 @@ impl CallExpression { for arg in &self.arguments { let metadata = Metadata { source_range: SourceRange([arg.start(), arg.end()]), + path_to_node: Some(memory.path_to_node.clone()), }; let result = ctx .arg_into_mem_item(arg, memory, pipe_info, &metadata, StatementKind::Expression) @@ -1872,6 +1873,7 @@ impl From for MemoryItem { value: JValue::from(literal.value.clone()), meta: vec![Metadata { source_range: literal.into(), + path_to_node: None, }], }) } @@ -1883,6 +1885,7 @@ impl From<&Box> for MemoryItem { value: JValue::from(literal.value.clone()), meta: vec![Metadata { source_range: literal.into(), + path_to_node: None, }], }) } @@ -1979,6 +1982,7 @@ impl From<&TagDeclarator> for TagIdentifier { value: tag.name.clone(), meta: vec![Metadata { source_range: tag.into(), + path_to_node: None, }], } } @@ -2050,6 +2054,7 @@ impl TagDeclarator { value: self.name.clone(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })); @@ -2270,6 +2275,7 @@ impl ArrayExpression { value: results.into(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } @@ -2438,6 +2444,7 @@ impl ObjectExpression { value: object.into(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } @@ -2676,6 +2683,7 @@ impl MemberExpression { value: value.clone(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } else { @@ -2733,6 +2741,7 @@ impl MemberExpression { value: value.clone(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } else { @@ -2894,6 +2903,7 @@ impl BinaryExpression { value, meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })); } @@ -2915,6 +2925,7 @@ impl BinaryExpression { value, meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } @@ -3101,6 +3112,7 @@ impl UnaryExpression { value: (-(num)).into(), meta: vec![Metadata { source_range: self.into(), + path_to_node: None, }], })) } @@ -3298,6 +3310,7 @@ async fn execute_pipe_body( // of its own. let meta = Metadata { source_range: SourceRange([first.start(), first.end()]), + path_to_node: Some(memory.path_to_node.clone()), }; let output = ctx .arg_into_mem_item(first, memory, pipe_info, &meta, StatementKind::Expression) diff --git a/src/wasm-lib/kcl/src/engine/conn.rs b/src/wasm-lib/kcl/src/engine/conn.rs index e7038b486..2222132d3 100644 --- a/src/wasm-lib/kcl/src/engine/conn.rs +++ b/src/wasm-lib/kcl/src/engine/conn.rs @@ -33,8 +33,8 @@ pub struct EngineConnection { responses: Arc>, tcp_read_handle: Arc, socket_health: Arc>, - batch: Arc>>, - batch_end: Arc>>, + batch: Arc>>, + batch_end: Arc>>, /// The default planes for the scene. default_planes: Arc>>, @@ -248,15 +248,15 @@ impl EngineConnection { #[async_trait::async_trait] impl EngineManager for EngineConnection { - fn batch(&self) -> Arc>> { + fn batch(&self) -> Arc>> { self.batch.clone() } - fn batch_end(&self) -> Arc>> { + fn batch_end(&self) -> Arc>> { self.batch_end.clone() } - async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result { + async fn default_planes(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result { { let opt = self.default_planes.read().await.as_ref().cloned(); if let Some(planes) = opt { @@ -264,15 +264,15 @@ impl EngineManager for EngineConnection { } } // drop the read lock - let new_planes = self.new_default_planes(source_range).await?; + let new_planes = self.new_default_planes(source_range, path_to_node).await?; *self.default_planes.write().await = Some(new_planes.clone()); Ok(new_planes) } - async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> { + async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result<(), KclError> { // Remake the default planes, since they would have been removed after the scene was cleared. - let new_planes = self.new_default_planes(source_range).await?; + let new_planes = self.new_default_planes(source_range, path_to_node).await?; *self.default_planes.write().await = Some(new_planes); Ok(()) @@ -282,8 +282,9 @@ impl EngineManager for EngineConnection { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, cmd: kittycad::types::WebSocketRequest, - _id_to_source_range: std::collections::HashMap, + _id_to_source_range: std::collections::HashMap, ) -> Result { let (tx, rx) = oneshot::channel(); diff --git a/src/wasm-lib/kcl/src/engine/conn_mock.rs b/src/wasm-lib/kcl/src/engine/conn_mock.rs index 3ff2e494a..be8840584 100644 --- a/src/wasm-lib/kcl/src/engine/conn_mock.rs +++ b/src/wasm-lib/kcl/src/engine/conn_mock.rs @@ -13,8 +13,8 @@ use crate::{errors::KclError, executor::DefaultPlanes}; #[derive(Debug, Clone)] pub struct EngineConnection { - batch: Arc>>, - batch_end: Arc>>, + batch: Arc>>, + batch_end: Arc>>, } impl EngineConnection { @@ -28,19 +28,19 @@ impl EngineConnection { #[async_trait::async_trait] impl crate::engine::EngineManager for EngineConnection { - fn batch(&self) -> Arc>> { + fn batch(&self) -> Arc>> { self.batch.clone() } - fn batch_end(&self) -> Arc>> { + fn batch_end(&self) -> Arc>> { self.batch_end.clone() } - async fn default_planes(&self, _source_range: crate::executor::SourceRange) -> Result { + async fn default_planes(&self, _source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result { Ok(DefaultPlanes::default()) } - async fn clear_scene_post_hook(&self, _source_range: crate::executor::SourceRange) -> Result<(), KclError> { + async fn clear_scene_post_hook(&self, _source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result<(), KclError> { Ok(()) } @@ -48,8 +48,9 @@ impl crate::engine::EngineManager for EngineConnection { &self, id: uuid::Uuid, _source_range: crate::executor::SourceRange, + _path_to_node: crate::executor::PathToNode, cmd: kittycad::types::WebSocketRequest, - _id_to_source_range: std::collections::HashMap, + _id_to_source_range: std::collections::HashMap, ) -> Result { match cmd { WebSocketRequest::ModelingCmdBatchReq { diff --git a/src/wasm-lib/kcl/src/engine/conn_wasm.rs b/src/wasm-lib/kcl/src/engine/conn_wasm.rs index 24f79f01e..0ff9cfd50 100644 --- a/src/wasm-lib/kcl/src/engine/conn_wasm.rs +++ b/src/wasm-lib/kcl/src/engine/conn_wasm.rs @@ -24,6 +24,7 @@ extern "C" { this: &EngineCommandManager, id: String, rangeStr: String, + pathToNodeStr: String, cmdStr: String, idToRangeStr: String, ) -> Result; @@ -41,8 +42,8 @@ extern "C" { #[derive(Debug, Clone)] pub struct EngineConnection { manager: Arc, - batch: Arc>>, - batch_end: Arc>>, + batch: Arc>>, + batch_end: Arc>>, } // Safety: WebAssembly will only ever run in a single-threaded context. @@ -61,15 +62,15 @@ impl EngineConnection { #[async_trait::async_trait] impl crate::engine::EngineManager for EngineConnection { - fn batch(&self) -> Arc>> { + fn batch(&self) -> Arc>> { self.batch.clone() } - fn batch_end(&self) -> Arc>> { + fn batch_end(&self) -> Arc>> { self.batch_end.clone() } - async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result { + async fn default_planes(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result { // Get the default planes. let promise = self.manager.get_default_planes().map_err(|e| { KclError::Engine(KclErrorDetails { @@ -107,7 +108,7 @@ impl crate::engine::EngineManager for EngineConnection { Ok(default_planes) } - async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> { + async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result<(), KclError> { self.manager.clear_default_planes().map_err(|e| { KclError::Engine(KclErrorDetails { message: e.to_string().into(), @@ -137,8 +138,10 @@ impl crate::engine::EngineManager for EngineConnection { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + // path_to_node: Vec<(String, String)>, + path_to_node: crate::executor::PathToNode, cmd: kittycad::types::WebSocketRequest, - id_to_source_range: std::collections::HashMap, + id_to_source_range: std::collections::HashMap, ) -> Result { let source_range_str = serde_json::to_string(&source_range).map_err(|e| { KclError::Engine(KclErrorDetails { @@ -146,6 +149,12 @@ impl crate::engine::EngineManager for EngineConnection { source_ranges: vec![source_range], }) })?; + let path_to_node_str = serde_json::to_string(&path_to_node).map_err(|e| { + KclError::Engine(KclErrorDetails { + message: format!("Failed to serialize path to node: {:?}", e), + source_ranges: vec![source_range], + }) + })?; let cmd_str = serde_json::to_string(&cmd).map_err(|e| { KclError::Engine(KclErrorDetails { message: format!("Failed to serialize modeling command: {:?}", e), @@ -161,7 +170,7 @@ impl crate::engine::EngineManager for EngineConnection { let promise = self .manager - .send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str) + .send_modeling_cmd_from_wasm(id.to_string(), source_range_str, path_to_node_str, cmd_str, id_to_source_range_str) .map_err(|e| { KclError::Engine(KclErrorDetails { message: e.to_string().into(), diff --git a/src/wasm-lib/kcl/src/engine/mod.rs b/src/wasm-lib/kcl/src/engine/mod.rs index c592b5ac5..e937c710e 100644 --- a/src/wasm-lib/kcl/src/engine/mod.rs +++ b/src/wasm-lib/kcl/src/engine/mod.rs @@ -31,17 +31,18 @@ lazy_static::lazy_static! { #[async_trait::async_trait] pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { /// Get the batch of commands to be sent to the engine. - fn batch(&self) -> Arc>>; + fn batch(&self) -> Arc>>; /// Get the batch of end commands to be sent to the engine. fn batch_end( &self, - ) -> Arc>>; + ) -> Arc>>; /// Get the default planes. async fn default_planes( &self, _source_range: crate::executor::SourceRange, + _path_to_node: crate::executor::PathToNode, ) -> Result; /// Helpers to be called after clearing a scene. @@ -49,6 +50,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { async fn clear_scene_post_hook( &self, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, ) -> Result<(), crate::errors::KclError>; /// Send a modeling command and wait for the response message. @@ -56,24 +58,26 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, cmd: kittycad::types::WebSocketRequest, - id_to_source_range: std::collections::HashMap, + id_to_source_range: std::collections::HashMap, ) -> Result; - async fn clear_scene(&self, source_range: crate::executor::SourceRange) -> Result<(), crate::errors::KclError> { + async fn clear_scene(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result<(), crate::errors::KclError> { self.batch_modeling_cmd( uuid::Uuid::new_v4(), source_range, + path_to_node.clone(), &kittycad::types::ModelingCmd::SceneClearAll {}, ) .await?; // Flush the batch queue, so clear is run right away. // Otherwise the hooks below won't work. - self.flush_batch(false, source_range).await?; + self.flush_batch(false, source_range, path_to_node.clone()).await?; // Do the after clear scene hook. - self.clear_scene_post_hook(source_range).await?; + self.clear_scene_post_hook(source_range, path_to_node).await?; Ok(()) } @@ -83,6 +87,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, cmd: &kittycad::types::ModelingCmd, ) -> Result<(), crate::errors::KclError> { let req = WebSocketRequest::ModelingCmdReq { @@ -91,7 +96,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { }; // Add cmd to the batch. - self.batch().lock().unwrap().push((req, source_range)); + self.batch().lock().unwrap().push((req, (source_range, path_to_node))); Ok(()) } @@ -103,6 +108,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, cmd: &kittycad::types::ModelingCmd, ) -> Result<(), crate::errors::KclError> { let req = WebSocketRequest::ModelingCmdReq { @@ -111,7 +117,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { }; // Add cmd to the batch end. - self.batch_end().lock().unwrap().insert(id, (req, source_range)); + self.batch_end().lock().unwrap().insert(id, (req, (source_range, path_to_node))); Ok(()) } @@ -122,12 +128,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { &self, id: uuid::Uuid, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, cmd: kittycad::types::ModelingCmd, ) -> Result { - self.batch_modeling_cmd(id, source_range, &cmd).await?; + self.batch_modeling_cmd(id, source_range, path_to_node.clone(), &cmd).await?; // Flush the batch queue. - self.flush_batch(false, source_range).await + self.flush_batch(false, source_range, path_to_node).await } /// Force flush the batch queue. @@ -137,6 +144,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { // We only do this at the very end of the file. batch_end: bool, source_range: crate::executor::SourceRange, + path_to_node: crate::executor::PathToNode, ) -> Result { let all_requests = if batch_end { let mut requests = self.batch().lock().unwrap().clone(); @@ -183,12 +191,12 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { for (req, range) in all_requests.iter() { match req { WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => { - id_to_source_range.insert(*cmd_id, *range); + id_to_source_range.insert(*cmd_id, range.clone()); } _ => { return Err(KclError::Engine(KclErrorDetails { message: format!("The request is not a modeling command: {:?}", req), - source_ranges: vec![*range], + source_ranges: vec![range.clone().0], })); } } @@ -210,7 +218,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { // Get the last command ID. let last_id = requests.last().unwrap().cmd_id; let ws_resp = self - .inner_send_modeling_cmd(batch_id, source_range, final_req, id_to_source_range.clone()) + .inner_send_modeling_cmd(batch_id, source_range, path_to_node, final_req, id_to_source_range.clone()) .await?; let response = self.parse_websocket_response(ws_resp, source_range)?; @@ -240,9 +248,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { }) })?; let ws_resp = self - .inner_send_modeling_cmd(cmd_id, source_range, final_req, id_to_source_range) + .inner_send_modeling_cmd(cmd_id, source_range.0, path_to_node, final_req, id_to_source_range) .await?; - self.parse_websocket_response(ws_resp, source_range) + self.parse_websocket_response(ws_resp, source_range.0) } _ => Err(KclError::Engine(KclErrorDetails { message: format!("The final request is not a modeling command: {:?}", final_req), @@ -266,6 +274,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { self.batch_modeling_cmd( plane_id, source_range, + crate::executor::PathToNode::default(), &ModelingCmd::MakePlane { clobber: false, origin: default_origin, @@ -282,6 +291,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { self.batch_modeling_cmd( uuid::Uuid::new_v4(), source_range, + crate::executor::PathToNode::default(), &ModelingCmd::PlaneSetColor { color, plane_id }, ) .await?; @@ -290,7 +300,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { Ok(plane_id) } - async fn new_default_planes(&self, source_range: crate::executor::SourceRange) -> Result { + async fn new_default_planes(&self, source_range: crate::executor::SourceRange, path_to_node: crate::executor::PathToNode) -> Result { let plane_settings: HashMap)> = HashMap::from([ ( PlaneName::Xy, @@ -378,7 +388,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { } // Flush the batch queue, so these planes are created right away. - self.flush_batch(false, source_range).await?; + self.flush_batch(false, source_range, path_to_node).await?; Ok(DefaultPlanes { xy: planes[&PlaneName::Xy], @@ -416,7 +426,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { // The last response we are looking for. id: uuid::Uuid, // The mapping of source ranges to command IDs. - id_to_source_range: std::collections::HashMap, + id_to_source_range: std::collections::HashMap, // The response from the engine. responses: HashMap, ) -> Result { @@ -425,7 +435,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { let cmd_id = uuid::Uuid::parse_str(cmd_id).map_err(|e| { KclError::Engine(KclErrorDetails { message: format!("Failed to parse command ID: {:?}", e), - source_ranges: vec![id_to_source_range[&id]], + source_ranges: vec![id_to_source_range[&id].0], }) })?; @@ -439,7 +449,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { })?; return Err(KclError::Engine(KclErrorDetails { message: format!("Modeling command failed: {:?}", errors), - source_ranges: vec![source_range], + source_ranges: vec![source_range.0], })); } if let Some(response) = resp.response.as_ref() { @@ -468,6 +478,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { self.batch_modeling_cmd( uuid::Uuid::new_v4(), Default::default(), + Default::default(), &ModelingCmd::ObjectVisible { hidden, object_id: *GRID_OBJECT_ID, @@ -479,6 +490,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { self.batch_modeling_cmd( uuid::Uuid::new_v4(), Default::default(), + Default::default(), &ModelingCmd::ObjectVisible { hidden, object_id: *GRID_SCALE_TEXT_OBJECT_ID, @@ -486,7 +498,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { ) .await?; - self.flush_batch(false, Default::default()).await?; + self.flush_batch(false, Default::default(), Default::default()).await?; Ok(()) } diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 29af85328..0c142f8ea 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -26,6 +26,9 @@ pub struct ProgramMemory { pub root: HashMap, #[serde(rename = "return")] pub return_: Option, + #[serde(skip)] + pub path_to_node: PathToNode, + // pub path_to_node: Vec<(String, String)>, } impl ProgramMemory { @@ -62,6 +65,8 @@ impl ProgramMemory { ), ]), return_: None, + // path_to_node: Vec::new(), + path_to_node: PathToNode::new(), } } @@ -1004,6 +1009,24 @@ pub enum BodyType { #[ts(export)] pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]); +#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema, Hash, Eq)] +#[cfg_attr(feature = "pyo3", pyo3::pyclass)] +#[ts(export)] +pub struct PathToNode(#[ts(type = "Array<[string, string]>")] pub Vec<(String, String)>); + +impl PathToNode { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn push(&mut self, path: (String, String)) { + self.0.push(path); + } + pub fn pop(&mut self) -> Option<(String, String)> { + self.0.pop() + } +} + impl SourceRange { /// Create a new source range. pub fn new(start: usize, end: usize) -> Self { @@ -1132,11 +1155,16 @@ impl From for kittycad::types::Point3D { pub struct Metadata { /// The source range. pub source_range: SourceRange, + + // pathToNode + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path_to_node: Option, } -impl From for Metadata { - fn from(source_range: SourceRange) -> Self { - Self { source_range } +// Implement From trait for Metadata from a tuple of SourceRange and PathToNode +impl From<(SourceRange, Option)> for Metadata { + fn from((source_range, path_to_node): (SourceRange, Option)) -> Self { + Self { source_range, path_to_node } } } @@ -1349,6 +1377,8 @@ pub struct ExecutorContext { /// Mock mode is only for the modeling app when they just want to mock engine calls and not /// actually make them. pub is_mock: bool, + // an array of tuples (string| number, string)[] + // pub path_to_node: Vec<(String, String)>, } /// The executor settings. @@ -1438,6 +1468,7 @@ impl ExecutorContext { .batch_modeling_cmd( uuid::Uuid::new_v4(), SourceRange::default(), + PathToNode::default(), &kittycad::types::ModelingCmd::EdgeLinesVisible { hidden: !settings.highlight_edges, }, @@ -1450,6 +1481,7 @@ impl ExecutorContext { stdlib: Arc::new(StdLib::new()), settings, is_mock: false, + // path_to_node: Vec::new(), }) } @@ -1499,6 +1531,7 @@ impl ExecutorContext { .send_modeling_cmd( uuid::Uuid::new_v4(), SourceRange::default(), + PathToNode::default(), kittycad::types::ModelingCmd::SceneClearAll {}, ) .await?; @@ -1518,6 +1551,7 @@ impl ExecutorContext { .batch_modeling_cmd( uuid::Uuid::new_v4(), SourceRange::default(), + PathToNode::default(), &kittycad::types::ModelingCmd::SetSceneUnits { unit: self.settings.units.into(), }, @@ -1541,9 +1575,11 @@ impl ExecutorContext { body_type: BodyType, ) -> Result { let pipe_info = PipeInfo::default(); - + memory.path_to_node.push(("body".to_string(), "".to_string())); + // Iterate over the body of the program. - for statement in &program.body { + for (index, statement) in program.body.iter().enumerate() { + memory.path_to_node.push((index.to_string(), "index".to_string())); match statement { BodyItem::ExpressionStatement(expression_statement) => { if let Value::PipeExpression(pipe_expr) = &expression_statement.expression { @@ -1554,6 +1590,7 @@ impl ExecutorContext { for arg in &call_expr.arguments { let metadata = Metadata { source_range: SourceRange([arg.start(), arg.end()]), + path_to_node: Some(memory.path_to_node.clone()), }; let mem_item = self .arg_into_mem_item(arg, memory, &pipe_info, &metadata, StatementKind::Expression) @@ -1587,10 +1624,13 @@ impl ExecutorContext { } } BodyItem::VariableDeclaration(variable_declaration) => { - for declaration in &variable_declaration.declarations { + // let mut _memory = memory.clone(); + memory.path_to_node.push(("declarations".to_string(), "VariableDeclaration".to_string())); + for (index, declaration) in variable_declaration.declarations.iter().enumerate() { + memory.path_to_node.push((index.to_string(), "index".to_string())); let var_name = declaration.id.name.to_string(); let source_range: SourceRange = declaration.init.clone().into(); - let metadata = Metadata { source_range }; + let metadata = Metadata { source_range, path_to_node: Some(memory.path_to_node.clone())}; let memory_item = self .arg_into_mem_item( @@ -1602,7 +1642,9 @@ impl ExecutorContext { ) .await?; memory.add(&var_name, memory_item, source_range)?; + // _memory.path_to_node.pop(); } + // _memory.path_to_node.pop(); } BodyItem::ReturnStatement(return_statement) => match &return_statement.argument { Value::BinaryExpression(bin_expr) => { @@ -1650,6 +1692,7 @@ impl ExecutorContext { } }, } + // memory.path_to_node.pop(); } if BodyType::Root == body_type { @@ -1660,6 +1703,7 @@ impl ExecutorContext { // and chamfers where the engine would otherwise eat the ID of the segments. true, SourceRange([program.end, program.end]), + memory.path_to_node.clone(), ) .await?; } @@ -1753,6 +1797,7 @@ impl ExecutorContext { .send_modeling_cmd( uuid::Uuid::new_v4(), crate::executor::SourceRange::default(), + crate::executor::PathToNode::default(), kittycad::types::ModelingCmd::ZoomToFit { object_ids: Default::default(), padding: 0.1, @@ -1766,6 +1811,7 @@ impl ExecutorContext { .send_modeling_cmd( uuid::Uuid::new_v4(), crate::executor::SourceRange::default(), + crate::executor::PathToNode::default(), kittycad::types::ModelingCmd::TakeSnapshot { format: kittycad::types::ImageFormat::Png, }, @@ -1861,6 +1907,7 @@ mod tests { stdlib: Arc::new(crate::std::StdLib::new()), settings: Default::default(), is_mock: true, + // path_to_node: Vec::new(), }; let memory = ctx.run(&program, None).await?; diff --git a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs index 1bb2622f6..55d6b2067 100644 --- a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs @@ -41,7 +41,7 @@ use tower_lsp::{ use crate::{ ast::types::{Value, VariableKind}, - executor::SourceRange, + executor::{PathToNode, SourceRange}, lint::checks, lsp::{backend::Backend as _, util::IntoDiagnostic}, parser::PIPE_OPERATOR, @@ -589,7 +589,7 @@ impl Backend { } // Clear the scene, before we execute so it's not fugly as shit. - executor_ctx.engine.clear_scene(SourceRange::default()).await?; + executor_ctx.engine.clear_scene(SourceRange::default(), PathToNode::default()).await?; let memory = match executor_ctx.run(ast, None).await { Ok(memory) => memory, diff --git a/src/wasm-lib/kcl/src/std/fillet.rs b/src/wasm-lib/kcl/src/std/fillet.rs index f16e755e7..6eb55455f 100644 --- a/src/wasm-lib/kcl/src/std/fillet.rs +++ b/src/wasm-lib/kcl/src/std/fillet.rs @@ -9,7 +9,7 @@ use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, - executor::{ExtrudeGroup, FilletOrChamfer, MemoryItem, TagIdentifier, UserVal}, + executor::{ExtrudeGroup, FilletOrChamfer, MemoryItem, TagIdentifier, UserVal, Metadata}, std::Args, }; @@ -148,7 +148,7 @@ pub async fn get_opposite_edge(args: Args) -> Result { source_ranges: vec![args.source_range], }) })?, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone())))], })) } @@ -238,7 +238,7 @@ pub async fn get_next_adjacent_edge(args: Args) -> Result source_ranges: vec![args.source_range], }) })?, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone())))], })) } @@ -333,7 +333,7 @@ pub async fn get_previous_adjacent_edge(args: Args) -> Result, pub source_range: SourceRange, + pub path_to_node: PathToNode, pub ctx: ExecutorContext, pub current_program_memory: ProgramMemory, } @@ -217,12 +218,15 @@ impl Args { pub fn new( args: Vec, source_range: SourceRange, + // path_to_node: PathToNode, ctx: ExecutorContext, current_program_memory: ProgramMemory, ) -> Self { Self { args, source_range, + path_to_node: current_program_memory.path_to_node.clone(), + // path_to_node, ctx, current_program_memory, } @@ -234,7 +238,7 @@ impl Args { id: uuid::Uuid, cmd: kittycad::types::ModelingCmd, ) -> Result<(), crate::errors::KclError> { - self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await + self.ctx.engine.batch_modeling_cmd(id, self.source_range, self.path_to_node.clone(), &cmd).await } // Add a modeling command to the batch that gets executed at the end of the file. @@ -245,7 +249,7 @@ impl Args { id: uuid::Uuid, cmd: kittycad::types::ModelingCmd, ) -> Result<(), crate::errors::KclError> { - self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await + self.ctx.engine.batch_end_cmd(id, self.source_range, self.path_to_node.clone(), &cmd).await } /// Send the modeling cmd and wait for the response. @@ -254,7 +258,7 @@ impl Args { id: uuid::Uuid, cmd: kittycad::types::ModelingCmd, ) -> Result { - self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await + self.ctx.engine.send_modeling_cmd(id, self.source_range, self.path_to_node.clone(), cmd).await } /// Flush just the fillets and chamfers for this specific ExtrudeGroupSet. @@ -303,7 +307,7 @@ impl Args { // Run flush. // Yes, we do need to actually flush the batch here, or references will fail later. - self.ctx.engine.flush_batch(false, SourceRange::default()).await?; + self.ctx.engine.flush_batch(false, SourceRange::default(), self.path_to_node.clone()).await?; Ok(()) } @@ -313,6 +317,8 @@ impl Args { value: j, meta: vec![Metadata { source_range: self.source_range, + // path_to_node: self.path_to_node.clone(), + path_to_node: None, }], })) } diff --git a/src/wasm-lib/kcl/src/std/patterns.rs b/src/wasm-lib/kcl/src/std/patterns.rs index 46e4e8457..82e7032c8 100644 --- a/src/wasm-lib/kcl/src/std/patterns.rs +++ b/src/wasm-lib/kcl/src/std/patterns.rs @@ -10,7 +10,7 @@ use crate::{ errors::{KclError, KclErrorDetails}, executor::{ ExtrudeGroup, ExtrudeGroupSet, Geometries, Geometry, MemoryItem, Point3d, ProgramReturn, SketchGroup, - SketchGroupSet, SourceRange, UserVal, + SketchGroupSet, SourceRange, UserVal, Metadata, }, function_param::FunctionParam, std::{types::Uint, Args}, @@ -85,7 +85,7 @@ pub async fn pattern_transform(args: Args) -> Result { FunctionParam { inner: transform.func, fn_expr: transform.expr, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone())))], ctx: args.ctx.clone(), memory: args.current_program_memory.clone(), }, @@ -204,7 +204,7 @@ async fn make_transform<'a>( // Call the transform fn for this repetition. let repetition_num = MemoryItem::UserVal(UserVal { value: serde_json::Value::Number(i.into()), - meta: vec![source_range.into()], + meta: vec![Metadata::from((source_range, None))], }); let transform_fn_args = vec![repetition_num]; let transform_fn_return = transform_function.call(transform_fn_args).await?; diff --git a/src/wasm-lib/kcl/src/std/revolve.rs b/src/wasm-lib/kcl/src/std/revolve.rs index 9f1e743e1..0cc5f02b0 100644 --- a/src/wasm-lib/kcl/src/std/revolve.rs +++ b/src/wasm-lib/kcl/src/std/revolve.rs @@ -9,7 +9,7 @@ use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, - executor::{ExtrudeGroup, MemoryItem, SketchGroup, TagIdentifier, UserVal}, + executor::{ExtrudeGroup, MemoryItem, SketchGroup, TagIdentifier, UserVal, Metadata}, std::{ extrude::do_post_extrude, fillet::{EdgeReference, DEFAULT_TOLERANCE}, @@ -300,7 +300,7 @@ pub async fn get_edge(args: Args) -> Result { source_ranges: vec![args.source_range], }) })?, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone()))),], })) } diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index 5170f7e3c..3a4b9e5d8 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -14,7 +14,7 @@ use crate::{ errors::{KclError, KclErrorDetails}, executor::{ BasePath, ExtrudeGroup, Face, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d, SketchGroup, - SketchGroupSet, SketchSurface, SourceRange, TagIdentifier, UserVal, + SketchGroupSet, SketchSurface, SourceRange, TagIdentifier, UserVal, Metadata }, std::{ utils::{ @@ -141,7 +141,7 @@ async fn inner_line_to( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; @@ -309,7 +309,7 @@ async fn inner_line( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; @@ -493,7 +493,7 @@ async fn inner_angled_line( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; @@ -1076,7 +1076,7 @@ async fn start_sketch_on_face( y_axis: extrude_group.sketch_group.on.y_axis(), z_axis: extrude_group.sketch_group.on.z_axis(), extrude_group, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone()))),], })) } @@ -1084,7 +1084,7 @@ async fn start_sketch_on_plane(data: PlaneData, args: Args) -> Result let mut plane: Plane = data.clone().into(); // Get the default planes. - let default_planes = args.ctx.engine.default_planes(args.source_range).await?; + let default_planes = args.ctx.engine.default_planes(args.source_range, args.path_to_node.clone()).await?; plane.id = match data { PlaneData::XY => default_planes.xy, @@ -1223,7 +1223,7 @@ pub(crate) async fn inner_start_profile_at( tag: tag.clone(), geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }; @@ -1232,7 +1232,7 @@ pub(crate) async fn inner_start_profile_at( on: sketch_surface.clone(), value: vec![], start: current_path, - meta: vec![args.source_range.into()], + meta: vec![Metadata::from((args.source_range, Some(args.path_to_node.clone()))),], tags: if let Some(tag) = &tag { HashMap::from([(tag.name.to_string(), tag.into())]) } else { @@ -1382,7 +1382,7 @@ pub(crate) async fn inner_close( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }); @@ -1495,7 +1495,7 @@ pub(crate) async fn inner_arc( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; @@ -1605,7 +1605,7 @@ async fn inner_tangential_arc( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; @@ -1713,7 +1713,7 @@ async fn inner_tangential_arc_to( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, center: result.center, @@ -1818,7 +1818,7 @@ async fn inner_bezier_curve( tag, geo_meta: GeoMeta { id, - metadata: args.source_range.into(), + metadata: Metadata::from((args.source_range, Some(args.path_to_node.clone()))), }, }, }; diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index 38c78cde3..682b5f817 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -85,7 +85,7 @@ pub async fn make_default_planes( .await .map_err(|e| format!("{:?}", e))?; let default_planes = engine - .new_default_planes(Default::default()) + .new_default_planes(Default::default(), Default::default()) .await .map_err(String::from)?;