diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png index 3f1fab6ee8..9b2c795c46 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png index 9c2f83d441..554ab776a7 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-2-Google-Chrome-linux.png index 54e98af8fa..b20edd54d4 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-3-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-3-Google-Chrome-linux.png index 68b08617b7..7afe5dc532 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-3-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-3-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-4-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-4-Google-Chrome-linux.png index ceba8b8204..57f85a7e85 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-4-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-4-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-5-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-5-Google-Chrome-linux.png index 9fd6eb69e8..c47e77b43e 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-5-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-5-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-6-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-6-Google-Chrome-linux.png index 05f9223c1d..fd7bf13000 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-6-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-each-default-plane-should-be-stable-6-Google-Chrome-linux.png differ diff --git a/src/editor/plugins/lsp/index.ts b/src/editor/plugins/lsp/index.ts index 7ba2ace984..efc28a9904 100644 --- a/src/editor/plugins/lsp/index.ts +++ b/src/editor/plugins/lsp/index.ts @@ -21,9 +21,12 @@ interface LSPRequestMap { LSP.SemanticTokensParams, LSP.SemanticTokens ] - getCompletions: [CopilotLspCompletionParams, CopilotCompletionResponse] - notifyAccepted: [CopilotAcceptCompletionParams, any] - notifyRejected: [CopilotRejectCompletionParams, any] + 'copilot/getCompletions': [ + CopilotLspCompletionParams, + CopilotCompletionResponse + ] + 'copilot/notifyAccepted': [CopilotAcceptCompletionParams, any] + 'copilot/notifyRejected': [CopilotRejectCompletionParams, any] } // Client to server @@ -215,7 +218,7 @@ export class LanguageServerClient { } async getCompletion(params: CopilotLspCompletionParams) { - const response = await this.request('getCompletions', params) + const response = await this.request('copilot/getCompletions', params) // this.queuedUids = [...response.completions.map((c) => c.uuid)] return response @@ -235,11 +238,11 @@ export class LanguageServerClient { } async acceptCompletion(params: CopilotAcceptCompletionParams) { - return await this.request('notifyAccepted', params) + return await this.request('copilot/notifyAccepted', params) } async rejectCompletions(params: CopilotRejectCompletionParams) { - return await this.request('notifyRejected', params) + return await this.request('copilot/notifyRejected', params) } private processNotifications(notification: LSP.NotificationMessage) { diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 48042464e8..1ac1e4582d 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -927,7 +927,7 @@ dependencies = [ [[package]] name = "derive-docs" -version = "0.1.13" +version = "0.1.14" dependencies = [ "Inflector", "anyhow", diff --git a/src/wasm-lib/derive-docs/Cargo.toml b/src/wasm-lib/derive-docs/Cargo.toml index 3e88491534..cfa968437a 100644 --- a/src/wasm-lib/derive-docs/Cargo.toml +++ b/src/wasm-lib/derive-docs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "derive-docs" description = "A tool for generating documentation from Rust derive macros" -version = "0.1.13" +version = "0.1.14" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/src/wasm-lib/derive-docs/src/lib.rs b/src/wasm-lib/derive-docs/src/lib.rs index db0fe0aa58..a870e801e2 100644 --- a/src/wasm-lib/derive-docs/src/lib.rs +++ b/src/wasm-lib/derive-docs/src/lib.rs @@ -383,7 +383,7 @@ fn do_stdlib_inner( fn #boxed_fn_name_ident( args: crate::std::Args, ) -> std::pin::Pin< - Box>>, + Box> + Send>, > { Box::pin(#fn_name_ident(args)) } @@ -783,11 +783,10 @@ fn generate_code_block_test( let tokens = crate::token::lexer(#code_block); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()).await.unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx).await.unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); diff --git a/src/wasm-lib/derive-docs/tests/array.gen b/src/wasm-lib/derive-docs/tests/array.gen index 39f8db9b05..304ebb6bfd 100644 --- a/src/wasm-lib/derive-docs/tests/array.gen +++ b/src/wasm-lib/derive-docs/tests/array.gen @@ -28,14 +28,11 @@ mod test_examples_show { let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -123,14 +120,11 @@ mod test_examples_show { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -205,8 +199,8 @@ fn boxed_show( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(show(args)) diff --git a/src/wasm-lib/derive-docs/tests/box.gen b/src/wasm-lib/derive-docs/tests/box.gen index d4ad417fb4..b45981987b 100644 --- a/src/wasm-lib/derive-docs/tests/box.gen +++ b/src/wasm-lib/derive-docs/tests/box.gen @@ -28,14 +28,11 @@ mod test_examples_show { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_show( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(show(args)) diff --git a/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen b/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen index 9dbecb2a8e..1506f0fcb1 100644 --- a/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen +++ b/src/wasm-lib/derive-docs/tests/doc_comment_with_code.gen @@ -28,14 +28,11 @@ mod test_examples_my_func { let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -123,14 +120,11 @@ mod test_examples_my_func { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -205,8 +199,8 @@ fn boxed_my_func( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(my_func(args)) diff --git a/src/wasm-lib/derive-docs/tests/doc_comment_with_code_on_ignored_function.gen b/src/wasm-lib/derive-docs/tests/doc_comment_with_code_on_ignored_function.gen index 8a33f70911..3f20f048d2 100644 --- a/src/wasm-lib/derive-docs/tests/doc_comment_with_code_on_ignored_function.gen +++ b/src/wasm-lib/derive-docs/tests/doc_comment_with_code_on_ignored_function.gen @@ -29,14 +29,11 @@ mod test_examples_import { let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nimport"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -125,14 +122,11 @@ mod test_examples_import { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -207,8 +201,8 @@ fn boxed_import( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(import(args)) diff --git a/src/wasm-lib/derive-docs/tests/lineTo.gen b/src/wasm-lib/derive-docs/tests/lineTo.gen index 0419898770..fc840a08b8 100644 --- a/src/wasm-lib/derive-docs/tests/lineTo.gen +++ b/src/wasm-lib/derive-docs/tests/lineTo.gen @@ -28,14 +28,11 @@ mod test_examples_line_to { let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -123,14 +120,11 @@ mod test_examples_line_to { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -205,8 +199,8 @@ fn boxed_line_to( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(line_to(args)) diff --git a/src/wasm-lib/derive-docs/tests/min.gen b/src/wasm-lib/derive-docs/tests/min.gen index 3839e82bf2..7d270a9272 100644 --- a/src/wasm-lib/derive-docs/tests/min.gen +++ b/src/wasm-lib/derive-docs/tests/min.gen @@ -28,14 +28,11 @@ mod test_examples_min { let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmin"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -123,14 +120,11 @@ mod test_examples_min { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmin"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -205,8 +199,8 @@ fn boxed_min( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(min(args)) diff --git a/src/wasm-lib/derive-docs/tests/option.gen b/src/wasm-lib/derive-docs/tests/option.gen index c02bd20e5e..cc1b77c8bc 100644 --- a/src/wasm-lib/derive-docs/tests/option.gen +++ b/src/wasm-lib/derive-docs/tests/option.gen @@ -28,14 +28,11 @@ mod test_examples_show { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_show( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(show(args)) diff --git a/src/wasm-lib/derive-docs/tests/option_input_format.gen b/src/wasm-lib/derive-docs/tests/option_input_format.gen index a2398d794c..ec3ae3a542 100644 --- a/src/wasm-lib/derive-docs/tests/option_input_format.gen +++ b/src/wasm-lib/derive-docs/tests/option_input_format.gen @@ -28,14 +28,11 @@ mod test_examples_import { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_import( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(import(args)) diff --git a/src/wasm-lib/derive-docs/tests/return_vec_box_sketch_group.gen b/src/wasm-lib/derive-docs/tests/return_vec_box_sketch_group.gen index e17ed3b68e..47f3884beb 100644 --- a/src/wasm-lib/derive-docs/tests/return_vec_box_sketch_group.gen +++ b/src/wasm-lib/derive-docs/tests/return_vec_box_sketch_group.gen @@ -28,14 +28,11 @@ mod test_examples_import { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_import( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(import(args)) diff --git a/src/wasm-lib/derive-docs/tests/return_vec_sketch_group.gen b/src/wasm-lib/derive-docs/tests/return_vec_sketch_group.gen index 1e845feb61..3d898ff09c 100644 --- a/src/wasm-lib/derive-docs/tests/return_vec_sketch_group.gen +++ b/src/wasm-lib/derive-docs/tests/return_vec_sketch_group.gen @@ -28,14 +28,11 @@ mod test_examples_import { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_import( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(import(args)) diff --git a/src/wasm-lib/derive-docs/tests/show.gen b/src/wasm-lib/derive-docs/tests/show.gen index 5f3b78ff45..fb4c7514c0 100644 --- a/src/wasm-lib/derive-docs/tests/show.gen +++ b/src/wasm-lib/derive-docs/tests/show.gen @@ -28,14 +28,11 @@ mod test_examples_show { let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow"); let parser = crate::parser::Parser::new(tokens); let program = parser.ast().unwrap(); - let mut mem: crate::executor::ProgramMemory = Default::default(); let units = kittycad::types::UnitLength::Mm; let ctx = crate::executor::ExecutorContext::new(ws, units.clone()) .await .unwrap(); - crate::executor::execute(program, &mut mem, crate::executor::BodyType::Root, &ctx) - .await - .unwrap(); + ctx.run(program, None).await.unwrap(); let (x, y) = crate::std::utils::get_camera_zoom_magnitude_per_unit_length(units); ctx.engine .send_modeling_cmd( @@ -110,8 +107,8 @@ fn boxed_show( ) -> std::pin::Pin< Box< dyn std::future::Future< - Output = anyhow::Result, - >, + Output = anyhow::Result, + > + Send, >, > { Box::pin(show(args)) diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml index 1c7c7397dd..73f5637d31 100644 --- a/src/wasm-lib/kcl/Cargo.toml +++ b/src/wasm-lib/kcl/Cargo.toml @@ -19,7 +19,7 @@ chrono = "0.4.37" clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true } dashmap = "5.5.3" databake = { version = "0.1.7", features = ["derive"] } -derive-docs = { version = "0.1.13", path = "../derive-docs" } +derive-docs = { version = "0.1.14", path = "../derive-docs" } form_urlencoded = "1.2.1" futures = { version = "0.3.30" } git_rev = "0.1.0" @@ -44,6 +44,7 @@ zip = { version = "0.6.6", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = { version = "0.3.69" } +tokio = { version = "1.37.0", features = ["sync"] } tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] } wasm-bindgen = "0.2.91" wasm-bindgen-futures = "0.4.42" diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index fa293d3485..4af0b32884 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -711,7 +711,7 @@ impl BinaryPart { } } - #[async_recursion::async_recursion(?Send)] + #[async_recursion::async_recursion] pub async fn get_result( &self, memory: &mut ProgramMemory, @@ -1005,7 +1005,7 @@ impl CallExpression { ) } - #[async_recursion::async_recursion(?Send)] + #[async_recursion::async_recursion] pub async fn execute( &self, memory: &mut ProgramMemory, @@ -1112,7 +1112,7 @@ impl CallExpression { // Call the stdlib function let p = func.function().clone().body; - let results = match crate::executor::execute(p, &mut fn_memory, BodyType::Block, ctx).await { + let results = match ctx.inner_execute(p, &mut fn_memory, BodyType::Block).await { Ok(results) => results, Err(err) => { // We need to override the source ranges so we don't get the embedded kcl @@ -1690,7 +1690,7 @@ impl ArrayExpression { None } - #[async_recursion::async_recursion(?Send)] + #[async_recursion::async_recursion] pub async fn execute( &self, memory: &mut ProgramMemory, @@ -1837,7 +1837,7 @@ impl ObjectExpression { None } - #[async_recursion::async_recursion(?Send)] + #[async_recursion::async_recursion] pub async fn execute( &self, memory: &mut ProgramMemory, @@ -2271,14 +2271,16 @@ impl BinaryExpression { if left_source_range.contains(pos) { return self.left.get_hover_value_for_position(pos, code); - } else if right_source_range.contains(pos) { + } + + if right_source_range.contains(pos) { return self.right.get_hover_value_for_position(pos, code); } None } - #[async_recursion::async_recursion(?Send)] + #[async_recursion::async_recursion] pub async fn get_result( &self, memory: &mut ProgramMemory, @@ -2636,7 +2638,6 @@ impl PipeExpression { } } -#[async_recursion::async_recursion(?Send)] async fn execute_pipe_body( memory: &mut ProgramMemory, body: &[Value], diff --git a/src/wasm-lib/kcl/src/engine/mod.rs b/src/wasm-lib/kcl/src/engine/mod.rs index 17d09339fa..1233565939 100644 --- a/src/wasm-lib/kcl/src/engine/mod.rs +++ b/src/wasm-lib/kcl/src/engine/mod.rs @@ -94,7 +94,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { } else { batched_requests }; - // println!("Running batch: {final_req:#?}"); // Create the map of original command IDs to source range. // This is for the wasm side, kurt needs it for selections. diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 4f51e8235b..a6d4d5f90e 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -296,7 +296,7 @@ pub type MemoryFunction = expression: Box, metadata: Vec, ctx: ExecutorContext, - ) -> std::pin::Pin, KclError>>>>; + ) -> std::pin::Pin, KclError>> + Send>>; fn force_memory_function< F: Fn( @@ -305,7 +305,7 @@ fn force_memory_function< Box, Vec, ExecutorContext, - ) -> std::pin::Pin, KclError>>>>, + ) -> std::pin::Pin, KclError>> + Send>>, >( f: F, ) -> F { @@ -992,243 +992,257 @@ impl ExecutorContext { is_mock: false, }) } -} -/// Execute an AST's program. -pub async fn execute_outer( - program: crate::ast::types::Program, - memory: &mut ProgramMemory, - _options: BodyType, - ctx: &ExecutorContext, -) -> Result { - // Before we even start executing the program, set the units. - ctx.engine - .send_modeling_cmd( - uuid::Uuid::new_v4(), - SourceRange::default(), - kittycad::types::ModelingCmd::SetSceneUnits { - unit: ctx.units.clone(), - }, - ) - .await?; - execute(program, memory, _options, ctx).await -} - -/// Execute an AST's program. -#[async_recursion(?Send)] -pub(crate) async fn execute( - program: crate::ast::types::Program, - memory: &mut ProgramMemory, - _options: BodyType, - ctx: &ExecutorContext, -) -> Result { - let pipe_info = PipeInfo::default(); - - // Iterate over the body of the program. - for statement in &program.body { - match statement { - BodyItem::ExpressionStatement(expression_statement) => { - if let Value::PipeExpression(pipe_expr) = &expression_statement.expression { - pipe_expr.get_result(memory, &pipe_info, ctx).await?; - } else if let Value::CallExpression(call_expr) = &expression_statement.expression { - let fn_name = call_expr.callee.name.to_string(); - let mut args: Vec = Vec::new(); - for arg in &call_expr.arguments { - match arg { - Value::Literal(literal) => args.push(literal.into()), - Value::Identifier(identifier) => { - let memory_item = memory.get(&identifier.name, identifier.into())?; - args.push(memory_item.clone()); + /// Perform the execution of a program. + /// You can optionally pass in some initialization memory. + /// Kurt uses this for partial execution. + pub async fn run( + &self, + program: crate::ast::types::Program, + memory: Option, + ) -> Result { + // Before we even start executing the program, set the units. + self.engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + SourceRange::default(), + kittycad::types::ModelingCmd::SetSceneUnits { + unit: self.units.clone(), + }, + ) + .await?; + let mut memory = if let Some(memory) = memory { + memory.clone() + } else { + Default::default() + }; + self.inner_execute(program, &mut memory, crate::executor::BodyType::Root) + .await + } + + /// Execute an AST's program. + #[async_recursion] + pub(crate) async fn inner_execute( + &self, + program: crate::ast::types::Program, + memory: &mut ProgramMemory, + _body_type: BodyType, + ) -> Result { + let pipe_info = PipeInfo::default(); + + // Iterate over the body of the program. + for statement in &program.body { + match statement { + BodyItem::ExpressionStatement(expression_statement) => { + if let Value::PipeExpression(pipe_expr) = &expression_statement.expression { + pipe_expr.get_result(memory, &pipe_info, self).await?; + } else if let Value::CallExpression(call_expr) = &expression_statement.expression { + let fn_name = call_expr.callee.name.to_string(); + let mut args: Vec = Vec::new(); + for arg in &call_expr.arguments { + match arg { + Value::Literal(literal) => args.push(literal.into()), + Value::Identifier(identifier) => { + let memory_item = memory.get(&identifier.name, identifier.into())?; + args.push(memory_item.clone()); + } + Value::CallExpression(call_expr) => { + let result = call_expr.execute(memory, &pipe_info, self).await?; + args.push(result); + } + Value::BinaryExpression(binary_expression) => { + let result = binary_expression.get_result(memory, &pipe_info, self).await?; + args.push(result); + } + Value::UnaryExpression(unary_expression) => { + let result = unary_expression.get_result(memory, &pipe_info, self).await?; + args.push(result); + } + Value::ObjectExpression(object_expression) => { + let result = object_expression.execute(memory, &pipe_info, self).await?; + args.push(result); + } + Value::ArrayExpression(array_expression) => { + let result = array_expression.execute(memory, &pipe_info, self).await?; + args.push(result); + } + // We do nothing for the rest. + _ => (), + } + } + match self.stdlib.get_either(&call_expr.callee.name) { + FunctionKind::Core(func) => { + let args = crate::std::Args::new(args, call_expr.into(), self.clone()); + let result = func.std_lib_fn()(args).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + FunctionKind::Std(func) => { + let mut newmem = memory.clone(); + let result = self + .inner_execute(func.program().to_owned(), &mut newmem, BodyType::Block) + .await?; + memory.return_ = result.return_; + } + FunctionKind::UserDefined => { + if let Some(func) = memory.clone().root.get(&fn_name) { + let result = func.call_fn(args.clone(), memory.clone(), self.clone()).await?; + + memory.return_ = result; + } else { + return Err(KclError::Semantic(KclErrorDetails { + message: format!("No such name {} defined", fn_name), + source_ranges: vec![call_expr.into()], + })); + } + } + } + } + } + BodyItem::VariableDeclaration(variable_declaration) => { + for declaration in &variable_declaration.declarations { + let var_name = declaration.id.name.to_string(); + let source_range: SourceRange = declaration.init.clone().into(); + let metadata = Metadata { source_range }; + + match &declaration.init { + Value::None(none) => { + memory.add(&var_name, none.into(), source_range)?; + } + Value::Literal(literal) => { + memory.add(&var_name, literal.into(), source_range)?; } - Value::CallExpression(call_expr) => { - let result = call_expr.execute(memory, &pipe_info, ctx).await?; - args.push(result); + Value::Identifier(identifier) => { + let value = memory.get(&identifier.name, identifier.into())?; + memory.add(&var_name, value.clone(), source_range)?; } Value::BinaryExpression(binary_expression) => { - let result = binary_expression.get_result(memory, &pipe_info, ctx).await?; - args.push(result); + let result = binary_expression.get_result(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; } - Value::UnaryExpression(unary_expression) => { - let result = unary_expression.get_result(memory, &pipe_info, ctx).await?; - args.push(result); + Value::FunctionExpression(function_expression) => { + let mem_func = force_memory_function( + |args: Vec, + memory: ProgramMemory, + function_expression: Box, + _metadata: Vec, + ctx: ExecutorContext| { + Box::pin(async move { + let mut fn_memory = + assign_args_to_params(&function_expression, args, memory.clone())?; + + let result = ctx + .inner_execute( + function_expression.body.clone(), + &mut fn_memory, + BodyType::Block, + ) + .await?; + + Ok(result.return_) + }) + }, + ); + memory.add( + &var_name, + MemoryItem::Function { + expression: function_expression.clone(), + meta: vec![metadata], + func: Some(mem_func), + }, + source_range, + )?; } - Value::ObjectExpression(object_expression) => { - let result = object_expression.execute(memory, &pipe_info, ctx).await?; - args.push(result); + Value::CallExpression(call_expression) => { + let result = call_expression.execute(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; } - Value::ArrayExpression(array_expression) => { - let result = array_expression.execute(memory, &pipe_info, ctx).await?; - args.push(result); + Value::PipeExpression(pipe_expression) => { + let result = pipe_expression.get_result(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; } - // We do nothing for the rest. - _ => (), - } - } - match ctx.stdlib.get_either(&call_expr.callee.name) { - FunctionKind::Core(func) => { - let args = crate::std::Args::new(args, call_expr.into(), ctx.clone()); - let result = func.std_lib_fn()(args).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - FunctionKind::Std(func) => { - let mut newmem = memory.clone(); - let result = execute(func.program().to_owned(), &mut newmem, BodyType::Block, ctx).await?; - memory.return_ = result.return_; - } - FunctionKind::UserDefined => { - if let Some(func) = memory.clone().root.get(&fn_name) { - let result = func.call_fn(args.clone(), memory.clone(), ctx.clone()).await?; - - memory.return_ = result; - } else { + Value::PipeSubstitution(pipe_substitution) => { return Err(KclError::Semantic(KclErrorDetails { - message: format!("No such name {} defined", fn_name), - source_ranges: vec![call_expr.into()], + message: format!( + "pipe substitution not implemented for declaration of variable {}", + var_name + ), + source_ranges: vec![pipe_substitution.into()], })); } + Value::ArrayExpression(array_expression) => { + let result = array_expression.execute(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; + } + Value::ObjectExpression(object_expression) => { + let result = object_expression.execute(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; + } + Value::MemberExpression(member_expression) => { + let result = member_expression.get_result(memory)?; + memory.add(&var_name, result, source_range)?; + } + Value::UnaryExpression(unary_expression) => { + let result = unary_expression.get_result(memory, &pipe_info, self).await?; + memory.add(&var_name, result, source_range)?; + } } } } - } - BodyItem::VariableDeclaration(variable_declaration) => { - for declaration in &variable_declaration.declarations { - let var_name = declaration.id.name.to_string(); - let source_range: SourceRange = declaration.init.clone().into(); - let metadata = Metadata { source_range }; - - match &declaration.init { - Value::None(none) => { - memory.add(&var_name, none.into(), source_range)?; - } - Value::Literal(literal) => { - memory.add(&var_name, literal.into(), source_range)?; - } - Value::Identifier(identifier) => { - let value = memory.get(&identifier.name, identifier.into())?; - memory.add(&var_name, value.clone(), source_range)?; - } - Value::BinaryExpression(binary_expression) => { - let result = binary_expression.get_result(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } - Value::FunctionExpression(function_expression) => { - let mem_func = force_memory_function( - |args: Vec, - memory: ProgramMemory, - function_expression: Box, - _metadata: Vec, - ctx: ExecutorContext| { - Box::pin(async move { - let mut fn_memory = - assign_args_to_params(&function_expression, args, memory.clone())?; - - let result = execute( - function_expression.body.clone(), - &mut fn_memory, - BodyType::Block, - &ctx, - ) - .await?; - - Ok(result.return_) - }) - }, - ); - memory.add( - &var_name, - MemoryItem::Function { - expression: function_expression.clone(), - meta: vec![metadata], - func: Some(mem_func), - }, - source_range, - )?; - } - Value::CallExpression(call_expression) => { - let result = call_expression.execute(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } - Value::PipeExpression(pipe_expression) => { - let result = pipe_expression.get_result(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } - Value::PipeSubstitution(pipe_substitution) => { - return Err(KclError::Semantic(KclErrorDetails { - message: format!( - "pipe substitution not implemented for declaration of variable {}", - var_name - ), - source_ranges: vec![pipe_substitution.into()], - })); - } - Value::ArrayExpression(array_expression) => { - let result = array_expression.execute(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } - Value::ObjectExpression(object_expression) => { - let result = object_expression.execute(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } - Value::MemberExpression(member_expression) => { - let result = member_expression.get_result(memory)?; - memory.add(&var_name, result, source_range)?; - } - Value::UnaryExpression(unary_expression) => { - let result = unary_expression.get_result(memory, &pipe_info, ctx).await?; - memory.add(&var_name, result, source_range)?; - } + BodyItem::ReturnStatement(return_statement) => match &return_statement.argument { + Value::BinaryExpression(bin_expr) => { + let result = bin_expr.get_result(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); } - } + Value::UnaryExpression(unary_expr) => { + let result = unary_expr.get_result(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::Identifier(identifier) => { + let value = memory.get(&identifier.name, identifier.into())?.clone(); + memory.return_ = Some(ProgramReturn::Value(value)); + } + Value::Literal(literal) => { + memory.return_ = Some(ProgramReturn::Value(literal.into())); + } + Value::ArrayExpression(array_expr) => { + let result = array_expr.execute(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::ObjectExpression(obj_expr) => { + let result = obj_expr.execute(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::CallExpression(call_expr) => { + let result = call_expr.execute(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::MemberExpression(member_expr) => { + let result = member_expr.get_result(memory)?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::PipeExpression(pipe_expr) => { + let result = pipe_expr.get_result(memory, &pipe_info, self).await?; + memory.return_ = Some(ProgramReturn::Value(result)); + } + Value::PipeSubstitution(_) => {} + Value::FunctionExpression(_) => {} + Value::None(none) => { + memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none))); + } + }, } - BodyItem::ReturnStatement(return_statement) => match &return_statement.argument { - Value::BinaryExpression(bin_expr) => { - let result = bin_expr.get_result(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::UnaryExpression(unary_expr) => { - let result = unary_expr.get_result(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::Identifier(identifier) => { - let value = memory.get(&identifier.name, identifier.into())?.clone(); - memory.return_ = Some(ProgramReturn::Value(value)); - } - Value::Literal(literal) => { - memory.return_ = Some(ProgramReturn::Value(literal.into())); - } - Value::ArrayExpression(array_expr) => { - let result = array_expr.execute(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::ObjectExpression(obj_expr) => { - let result = obj_expr.execute(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::CallExpression(call_expr) => { - let result = call_expr.execute(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::MemberExpression(member_expr) => { - let result = member_expr.get_result(memory)?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::PipeExpression(pipe_expr) => { - let result = pipe_expr.get_result(memory, &pipe_info, ctx).await?; - memory.return_ = Some(ProgramReturn::Value(result)); - } - Value::PipeSubstitution(_) => {} - Value::FunctionExpression(_) => {} - Value::None(none) => { - memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none))); - } - }, } - } - // Flush the batch queue. - ctx.engine.flush_batch(SourceRange([program.end, program.end])).await?; + // Flush the batch queue. + self.engine.flush_batch(SourceRange([program.end, program.end])).await?; + + Ok(memory.clone()) + } - Ok(memory.clone()) + /// Update the units for the executor. + pub fn update_units(&mut self, units: kittycad::types::UnitLength) { + self.units = units; + } } /// For each argument given, @@ -1299,7 +1313,6 @@ mod tests { let tokens = crate::token::lexer(code); let parser = crate::parser::Parser::new(tokens); let program = parser.ast()?; - let mut mem: ProgramMemory = Default::default(); let ctx = ExecutorContext { engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)), fs: crate::fs::FileManager::new(), @@ -1307,7 +1320,7 @@ mod tests { units: kittycad::types::UnitLength::Mm, is_mock: false, }; - let memory = execute_outer(program, &mut mem, BodyType::Root, &ctx).await?; + let memory = ctx.run(program, None).await?; Ok(memory) } diff --git a/src/wasm-lib/kcl/src/lsp/backend.rs b/src/wasm-lib/kcl/src/lsp/backend.rs index 36e3e09689..012dcec1d9 100644 --- a/src/wasm-lib/kcl/src/lsp/backend.rs +++ b/src/wasm-lib/kcl/src/lsp/backend.rs @@ -37,10 +37,21 @@ pub trait Backend { fn clear_code_state(&self); /// On change event. - async fn on_change(&self, params: TextDocumentItem); + async fn inner_on_change(&self, params: TextDocumentItem); + + async fn on_change(&self, params: TextDocumentItem) { + // Check if the document is in the current code map and if it is the same as what we have + // stored. + let filename = params.uri.to_string(); + if let Some(current_code) = self.current_code_map().get(&filename) { + if current_code.value() == params.text.as_bytes() { + return; + } + } - async fn update_memory(&self, params: TextDocumentItem) { - self.insert_current_code_map(params.uri.to_string(), params.text.as_bytes().to_vec()); + // Otherwise update the code map and call the inner on change. + self.insert_current_code_map(filename, params.text.as_bytes().to_vec()); + self.inner_on_change(params).await; } async fn update_from_disk + std::marker::Send>(&self, path: P) -> Result<()> { @@ -171,7 +182,6 @@ pub trait Backend { version: params.text_document.version, language_id: params.text_document.language_id, }; - self.update_memory(new_params.clone()).await; self.on_change(new_params).await; } @@ -182,7 +192,6 @@ pub trait Backend { version: params.text_document.version, language_id: Default::default(), }; - self.update_memory(new_params.clone()).await; self.on_change(new_params).await; } @@ -194,7 +203,6 @@ pub trait Backend { version: Default::default(), language_id: Default::default(), }; - self.update_memory(new_params.clone()).await; self.on_change(new_params).await; } } diff --git a/src/wasm-lib/kcl/src/lsp/copilot/mod.rs b/src/wasm-lib/kcl/src/lsp/copilot/mod.rs index 2d34452449..1a02a65e11 100644 --- a/src/wasm-lib/kcl/src/lsp/copilot/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/copilot/mod.rs @@ -103,7 +103,7 @@ impl crate::lsp::backend::Backend for Backend { self.current_code_map.clear(); } - async fn on_change(&self, _params: TextDocumentItem) { + async fn inner_on_change(&self, _params: TextDocumentItem) { // We don't need to do anything here. } } diff --git a/src/wasm-lib/kcl/src/lsp/kcl/custom_notifications.rs b/src/wasm-lib/kcl/src/lsp/kcl/custom_notifications.rs new file mode 100644 index 0000000000..05a9206bdb --- /dev/null +++ b/src/wasm-lib/kcl/src/lsp/kcl/custom_notifications.rs @@ -0,0 +1,29 @@ +//! Custom notifications for the KCL LSP server that are not part of the LSP specification. + +use serde::{Deserialize, Serialize}; +use tower_lsp::lsp_types::{notification::Notification, TextDocumentIdentifier}; + +/// A notification that the AST has changed. +#[derive(Debug)] +pub enum AstUpdated {} + +impl Notification for AstUpdated { + type Params = crate::ast::types::Program; + const METHOD: &'static str = "kcl/astUpdated"; +} + +/// A notification that the Memory has changed. +#[derive(Debug)] +pub enum MemoryUpdated {} + +impl Notification for MemoryUpdated { + type Params = crate::executor::ProgramMemory; + const METHOD: &'static str = "kcl/memoryUpdated"; +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpdateUnitsParams { + pub text_document: TextDocumentIdentifier, + pub units: kittycad::types::UnitLength, +} diff --git a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs index c1ef4ed006..f511b42db6 100644 --- a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs @@ -1,6 +1,10 @@ //! Functions for the `kcl` lsp server. -use std::{collections::HashMap, io::Write, str::FromStr}; +use std::{collections::HashMap, io::Write, str::FromStr, sync::Arc}; + +use tokio::sync::RwLock; + +pub mod custom_notifications; use anyhow::Result; #[cfg(feature = "cli")] @@ -29,7 +33,10 @@ use tower_lsp::{ Client, LanguageServer, }; -use crate::{ast::types::VariableKind, executor::SourceRange, lsp::backend::Backend as _, parser::PIPE_OPERATOR}; +use crate::{ + ast::types::VariableKind, errors::KclError, executor::SourceRange, lsp::backend::Backend as _, + parser::PIPE_OPERATOR, +}; /// A subcommand for running the server. #[derive(Clone, Debug)] @@ -63,6 +70,8 @@ pub struct Backend { pub token_map: DashMap>, /// AST maps. pub ast_map: DashMap, + /// Memory maps. + pub memory_map: DashMap, /// Current code. pub current_code_map: DashMap>, /// Diagnostics. @@ -75,6 +84,8 @@ pub struct Backend { pub zoo_client: kittycad::Client, /// If we can send telemetry for this user. pub can_send_telemetry: bool, + /// Optional executor context to use if we want to execute the code. + pub executor_ctx: Arc>>, } // Implement the shared backend trait for the language server. @@ -125,7 +136,7 @@ impl crate::lsp::backend::Backend for Backend { self.semantic_tokens_map.clear(); } - async fn on_change(&self, params: TextDocumentItem) { + async fn inner_on_change(&self, params: TextDocumentItem) { // We already updated the code map in the shared backend. // Lets update the tokens. @@ -188,35 +199,40 @@ impl crate::lsp::backend::Backend for Backend { let result = parser.ast(); let ast = match result { Ok(ast) => ast, - Err(e) => { - let diagnostic = e.to_lsp_diagnostic(¶ms.text); - // We got errors, update the diagnostics. - self.diagnostics_map.insert( - params.uri.to_string(), - DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport { - related_documents: None, - full_document_diagnostic_report: FullDocumentDiagnosticReport { - result_id: None, - items: vec![diagnostic.clone()], - }, - }), - ); - - // Publish the diagnostic. - // If the client supports it. - self.client - .publish_diagnostics(params.uri, vec![diagnostic], None) - .await; - + Err(err) => { + self.add_to_diagnostics(¶ms, err).await; return; } }; - // Update the symbols map. - self.symbols_map - .insert(params.uri.to_string(), ast.get_lsp_symbols(¶ms.text)); + // Check if the ast changed. + let ast_changed = match self.ast_map.get(¶ms.uri.to_string()) { + Some(old_ast) => { + // Check if the ast changed. + *old_ast.value() != ast + } + None => true, + }; + + // If the ast changed update the map and symbols and execute if we need to. + if ast_changed { + // Update the symbols map. + self.symbols_map + .insert(params.uri.to_string(), ast.get_lsp_symbols(¶ms.text)); + + self.ast_map.insert(params.uri.to_string(), ast.clone()); + + // Send the notification to the client that the ast was updated. + self.client + .send_notification::(ast.clone()) + .await; + + // Execute the code if we have an executor context. + // This function automatically executes if we should & updates the diagnostics if we got + // errors. + self.execute(¶ms, ast).await; + } - self.ast_map.insert(params.uri.to_string(), ast); // Lets update the diagnostics, since we got no errors. self.diagnostics_map.insert( params.uri.to_string(), @@ -236,6 +252,50 @@ impl crate::lsp::backend::Backend for Backend { } impl Backend { + async fn add_to_diagnostics(&self, params: &TextDocumentItem, err: KclError) { + let diagnostic = err.to_lsp_diagnostic(¶ms.text); + // We got errors, update the diagnostics. + self.diagnostics_map.insert( + params.uri.to_string(), + DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport { + related_documents: None, + full_document_diagnostic_report: FullDocumentDiagnosticReport { + result_id: None, + items: vec![diagnostic.clone()], + }, + }), + ); + + // Publish the diagnostic. + // If the client supports it. + self.client + .publish_diagnostics(params.uri.clone(), vec![diagnostic], None) + .await; + } + + async fn execute(&self, params: &TextDocumentItem, ast: crate::ast::types::Program) { + // Execute the code if we have an executor context. + let Some(executor_ctx) = self.executor_ctx.read().await.clone() else { + return; + }; + + let memory = match executor_ctx.run(ast, None).await { + Ok(memory) => memory, + Err(err) => { + self.add_to_diagnostics(params, err).await; + + return; + } + }; + drop(executor_ctx); // Drop the lock here. + + self.memory_map.insert(params.uri.to_string(), memory.clone()); + // Send the notification to the client that the memory was updated. + self.client + .send_notification::(memory) + .await; + } + fn get_semantic_token_type_index(&self, token_type: SemanticTokenType) -> Option { self.token_types.iter().position(|x| *x == token_type) } @@ -368,6 +428,51 @@ impl Backend { Ok(()) } + + pub async fn update_units(&self, params: custom_notifications::UpdateUnitsParams) { + { + let Some(mut executor_ctx) = self.executor_ctx.read().await.clone() else { + self.client + .log_message(MessageType::ERROR, "no executor context set to update units for") + .await; + return; + }; + + self.client + .log_message(MessageType::INFO, format!("update units: {:?}", params)) + .await; + + // Set the engine units. + executor_ctx.update_units(params.units); + + // Update the locked executor context. + *self.executor_ctx.write().await = Some(executor_ctx.clone()); + } + // Lock is dropped here since nested. + // This is IMPORTANT. + + let filename = params.text_document.uri.to_string(); + + // Get the current code. + let Some(current_code) = self.current_code_map.get(&filename) else { + return; + }; + let Ok(current_code) = std::str::from_utf8(¤t_code) else { + return; + }; + + // Get the current ast. + let Some(ast) = self.ast_map.get(&filename) else { + return; + }; + let new_params = TextDocumentItem { + uri: params.text_document.uri, + text: std::mem::take(&mut current_code.to_string()), + version: Default::default(), + language_id: Default::default(), + }; + self.execute(&new_params, ast.value().clone()).await; + } } #[tower_lsp::async_trait] diff --git a/src/wasm-lib/kcl/src/lsp/tests.rs b/src/wasm-lib/kcl/src/lsp/tests.rs index d4d3bcf974..02447fea8b 100644 --- a/src/wasm-lib/kcl/src/lsp/tests.rs +++ b/src/wasm-lib/kcl/src/lsp/tests.rs @@ -7,21 +7,57 @@ use anyhow::Result; use pretty_assertions::assert_eq; use tower_lsp::LanguageServer; +fn new_zoo_client() -> kittycad::Client { + let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),); + let http_client = reqwest::Client::builder() + .user_agent(user_agent) + // For file conversions we need this to be long. + .timeout(std::time::Duration::from_secs(600)) + .connect_timeout(std::time::Duration::from_secs(60)); + let ws_client = reqwest::Client::builder() + .user_agent(user_agent) + // For file conversions we need this to be long. + .timeout(std::time::Duration::from_secs(600)) + .connect_timeout(std::time::Duration::from_secs(60)) + .connection_verbose(true) + .tcp_keepalive(std::time::Duration::from_secs(600)) + .http1_only(); + + let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set"); + + // Create the client. + let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client); + // Set a local engine address if it's set. + if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") { + client.set_base_url(addr); + } + + client +} + // Create a fake kcl lsp server for testing. -fn kcl_lsp_server() -> Result { +async fn kcl_lsp_server(execute: bool) -> Result { let stdlib = crate::std::StdLib::new(); let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?; let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?; // We can unwrap here because we know the tokeniser is valid, since // we have a test for it. - let token_types = crate::token::TokenType::all_semantic_token_types().unwrap(); + let token_types = crate::token::TokenType::all_semantic_token_types()?; - // We don't actually need to authenticate to the backend for this test. - let mut zoo_client = kittycad::Client::new(""); - zoo_client.set_base_url("https://api.dev.zoo.dev"); + let zoo_client = new_zoo_client(); + + let executor_ctx = if execute { + let ws = zoo_client + .modeling() + .commands_ws(None, None, None, None, None, None, Some(false)) + .await?; + Some(crate::executor::ExecutorContext::new(ws, kittycad::types::UnitLength::Mm).await?) + } else { + None + }; // Create the backend. - let (service, _) = tower_lsp::LspService::new(|client| crate::lsp::kcl::Backend { + let (service, _) = tower_lsp::LspService::build(|client| crate::lsp::kcl::Backend { client, fs: crate::fs::FileManager::new(), workspace_folders: Default::default(), @@ -30,13 +66,18 @@ fn kcl_lsp_server() -> Result { token_types, token_map: Default::default(), ast_map: Default::default(), + memory_map: Default::default(), current_code_map: Default::default(), diagnostics_map: Default::default(), symbols_map: Default::default(), semantic_tokens_map: Default::default(), zoo_client, can_send_telemetry: true, - }); + executor_ctx: Arc::new(tokio::sync::RwLock::new(executor_ctx)), + }) + .custom_method("kcl/updateUnits", crate::lsp::kcl::Backend::update_units) + .finish(); + let server = service.inner(); Ok(server.clone()) @@ -63,9 +104,9 @@ fn copilot_lsp_server() -> Result { Ok(server.clone()) } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_updating_kcl_lsp_files() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); assert_eq!(server.current_code_map.len(), 0); @@ -96,7 +137,7 @@ async fn test_updating_kcl_lsp_files() { } ); - assert_eq!(server.current_code_map.len(), 8); + assert_eq!(server.current_code_map.len(), 9); // Run open file. server @@ -111,7 +152,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 9); + assert_eq!(server.current_code_map.len(), 10); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -127,7 +168,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 9); + assert_eq!(server.current_code_map.len(), 10); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -146,7 +187,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -172,7 +213,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -193,7 +234,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -213,7 +254,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 11); + assert_eq!(server.current_code_map.len(), 12); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -237,7 +278,7 @@ async fn test_updating_kcl_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -271,7 +312,7 @@ async fn test_updating_kcl_lsp_files() { ); // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -307,7 +348,7 @@ async fn test_updating_kcl_lsp_files() { name: "my-project2".to_string(), } ); - assert_eq!(server.current_code_map.len(), 8); + assert_eq!(server.current_code_map.len(), 9); // Just make sure that one of the current files read from disk is accurate. assert_eq!( server @@ -319,7 +360,7 @@ async fn test_updating_kcl_lsp_files() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_updating_copilot_lsp_files() { let server = copilot_lsp_server().unwrap(); @@ -352,7 +393,7 @@ async fn test_updating_copilot_lsp_files() { } ); - assert_eq!(server.current_code_map.len(), 8); + assert_eq!(server.current_code_map.len(), 9); // Run open file. server @@ -367,7 +408,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 9); + assert_eq!(server.current_code_map.len(), 10); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -383,7 +424,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 9); + assert_eq!(server.current_code_map.len(), 10); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -402,7 +443,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -428,7 +469,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -449,7 +490,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -469,7 +510,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 11); + assert_eq!(server.current_code_map.len(), 12); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -493,7 +534,7 @@ async fn test_updating_copilot_lsp_files() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -527,7 +568,7 @@ async fn test_updating_copilot_lsp_files() { ); // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -558,7 +599,7 @@ async fn test_updating_copilot_lsp_files() { ); // Check the code map. - assert_eq!(server.current_code_map.len(), 10); + assert_eq!(server.current_code_map.len(), 11); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -594,12 +635,12 @@ async fn test_updating_copilot_lsp_files() { name: "my-project2".to_string(), } ); - assert_eq!(server.current_code_map.len(), 8); + assert_eq!(server.current_code_map.len(), 9); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_create_zip() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); assert_eq!(server.current_code_map.len(), 0); @@ -630,7 +671,7 @@ async fn test_kcl_lsp_create_zip() { } ); - assert_eq!(server.current_code_map.len(), 8); + assert_eq!(server.current_code_map.len(), 9); // Run open file. server @@ -645,7 +686,7 @@ async fn test_kcl_lsp_create_zip() { .await; // Check the code map. - assert_eq!(server.current_code_map.len(), 9); + assert_eq!(server.current_code_map.len(), 10); assert_eq!( server.current_code_map.get("file:///test.kcl").unwrap().value(), "test".as_bytes() @@ -669,15 +710,15 @@ async fn test_kcl_lsp_create_zip() { files.insert(file.name().to_string(), file.size()); } - assert_eq!(files.len(), 9); + assert_eq!(files.len(), 10); let util_path = format!("{}/util.rs", string_path).replace("file://", ""); assert!(files.get(&util_path).is_some()); assert_eq!(files.get("/test.kcl"), Some(&4)); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_completions() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -718,9 +759,9 @@ st"# } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_on_hover() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -762,9 +803,9 @@ async fn test_kcl_lsp_on_hover() { } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_signature_help() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -810,9 +851,9 @@ async fn test_kcl_lsp_signature_help() { } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_semantic_tokens() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -852,9 +893,9 @@ async fn test_kcl_lsp_semantic_tokens() { } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_document_symbol() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -892,9 +933,9 @@ startSketchOn('XY')"# } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_formatting() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -939,9 +980,9 @@ async fn test_kcl_lsp_formatting() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_rename() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -986,9 +1027,9 @@ async fn test_kcl_lsp_rename() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_diagnostic_no_errors() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -1028,9 +1069,9 @@ async fn test_kcl_lsp_diagnostic_no_errors() { } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_lsp_diagnostic_has_errors() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send open file. server @@ -1074,7 +1115,7 @@ async fn test_kcl_lsp_diagnostic_has_errors() { } } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_copilot_lsp_set_editor_info() { let server = copilot_lsp_server().unwrap(); @@ -1104,7 +1145,7 @@ async fn test_copilot_lsp_set_editor_info() { assert_eq!(editor_info.editor_info.version, "1.0.0"); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] #[ignore] // Ignore til hosted model is faster (@jessfraz working on). async fn test_copilot_lsp_completions_raw() { let server = copilot_lsp_server().unwrap(); @@ -1158,7 +1199,7 @@ async fn test_copilot_lsp_completions_raw() { assert_eq!(completions, completions_hit_cache); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] #[ignore] // Ignore til hosted model is faster (@jessfraz working on). async fn test_copilot_lsp_completions() { let server = copilot_lsp_server().unwrap(); @@ -1224,7 +1265,7 @@ async fn test_copilot_lsp_completions() { .await; } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_copilot_on_save() { let server = copilot_lsp_server().unwrap(); @@ -1246,9 +1287,9 @@ async fn test_copilot_on_save() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_kcl_on_save() { - let server = kcl_lsp_server().unwrap(); + let server = kcl_lsp_server(false).await.unwrap(); // Send save file. server @@ -1268,7 +1309,7 @@ async fn test_kcl_on_save() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_copilot_rename_not_exists() { let server = copilot_lsp_server().unwrap(); @@ -1290,7 +1331,7 @@ async fn test_copilot_rename_not_exists() { ); } -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_lsp_initialized() { let copilot_server = copilot_lsp_server().unwrap(); @@ -1309,7 +1350,7 @@ async fn test_lsp_initialized() { assert_eq!(copilot_server.current_code_map.len(), 0); // Now do the same for kcl. - let kcl_server = kcl_lsp_server().unwrap(); + let kcl_server = kcl_lsp_server(false).await.unwrap(); // Send initialize request. kcl_server @@ -1327,3 +1368,200 @@ async fn test_lsp_initialized() { copilot_server.shutdown().await.unwrap(); kcl_server.shutdown().await.unwrap(); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_kcl_lsp_on_change_update_ast() { + let server = kcl_lsp_server(false).await.unwrap(); + + let same_text = r#"const thing = 1"#.to_string(); + + // Send open file. + server + .did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams { + text_document: tower_lsp::lsp_types::TextDocumentItem { + uri: "file:///test.kcl".try_into().unwrap(), + language_id: "kcl".to_string(), + version: 1, + text: same_text.clone(), + }, + }) + .await; + + // Get the ast. + let ast = server.ast_map.get("file:///test.kcl").unwrap().clone(); + + // Send change file. + server + .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { + text_document: tower_lsp::lsp_types::VersionedTextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + version: 1, + }, + content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: same_text.clone(), + }], + }) + .await; + + // Make sure the ast is the same. + assert_eq!(ast, server.ast_map.get("file:///test.kcl").unwrap().clone()); + + // Update the text. + let new_text = r#"const thing = 2"#.to_string(); + // Send change file. + server + .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { + text_document: tower_lsp::lsp_types::VersionedTextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + version: 2, + }, + content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: new_text.clone(), + }], + }) + .await; + + assert!(ast != server.ast_map.get("file:///test.kcl").unwrap().clone()); + + // Make sure we never updated the memory since we aren't running the engine. + assert!(server.memory_map.get("file:///test.kcl").is_none()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn serial_test_kcl_lsp_on_change_update_memory() { + let server = kcl_lsp_server(true).await.unwrap(); + + let same_text = r#"const thing = 1"#.to_string(); + + // Send open file. + server + .did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams { + text_document: tower_lsp::lsp_types::TextDocumentItem { + uri: "file:///test.kcl".try_into().unwrap(), + language_id: "kcl".to_string(), + version: 1, + text: same_text.clone(), + }, + }) + .await; + + // Get the memory. + let memory = server.memory_map.get("file:///test.kcl").unwrap().clone(); + + // Send change file. + server + .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { + text_document: tower_lsp::lsp_types::VersionedTextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + version: 1, + }, + content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: same_text.clone(), + }], + }) + .await; + + // Make sure the memory is the same. + assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone()); + + // Update the text. + let new_text = r#"const thing = 2"#.to_string(); + // Send change file. + server + .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { + text_document: tower_lsp::lsp_types::VersionedTextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + version: 2, + }, + content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: new_text.clone(), + }], + }) + .await; + + assert!(memory != server.memory_map.get("file:///test.kcl").unwrap().clone()); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 10)] +async fn serial_test_kcl_lsp_update_units() { + let server = kcl_lsp_server(true).await.unwrap(); + + let same_text = r#"fn cube = (pos, scale) => { + const sg = startSketchOn('XY') + |> startProfileAt(pos, %) + |> line([0, scale], %) + |> line([scale, 0], %) + |> line([0, -scale], %) + + return sg +} +const part001 = cube([0,0], 20) + |> close(%) + |> extrude(20, %)"# + .to_string(); + + // Send open file. + server + .did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams { + text_document: tower_lsp::lsp_types::TextDocumentItem { + uri: "file:///test.kcl".try_into().unwrap(), + language_id: "kcl".to_string(), + version: 1, + text: same_text.clone(), + }, + }) + .await; + + // Get the memory. + let memory = server.memory_map.get("file:///test.kcl").unwrap().clone(); + + // Send change file. + server + .did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams { + text_document: tower_lsp::lsp_types::VersionedTextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + version: 1, + }, + content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: same_text.clone(), + }], + }) + .await; + + // Make sure the memory is the same. + assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone()); + + let units = server.executor_ctx.read().await.clone().unwrap().units; + + assert_eq!(units, kittycad::types::UnitLength::Mm); + + // Update the units. + server + .update_units(crate::lsp::kcl::custom_notifications::UpdateUnitsParams { + text_document: tower_lsp::lsp_types::TextDocumentIdentifier { + uri: "file:///test.kcl".try_into().unwrap(), + }, + units: kittycad::types::UnitLength::M, + }) + .await; + + println!("updated units"); + + let units = server.executor_ctx.read().await.clone().unwrap().units; + assert_eq!(units, kittycad::types::UnitLength::M); + + println!("units are correct"); + + // Make sure it forced a memory update. + assert!(memory != server.memory_map.get("file:///test.kcl").unwrap().clone()); +} diff --git a/src/wasm-lib/kcl/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs index f0bdea2301..589cfd2db7 100644 --- a/src/wasm-lib/kcl/src/std/mod.rs +++ b/src/wasm-lib/kcl/src/std/mod.rs @@ -33,7 +33,8 @@ use crate::{ std::{kcl_stdlib::KclStdLibFn, sketch::SketchOnFaceTag}, }; -pub type StdFn = fn(Args) -> std::pin::Pin>>>; +pub type StdFn = fn(Args) -> std::pin::Pin> + Send>>; + pub type FnMap = HashMap; lazy_static! { diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index 2801d5b23f..a12319f70a 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -26,7 +26,7 @@ pub async fn execute_wasm( use kcl_lib::executor::ExecutorContext; let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?; - let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?; + let memory: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?; let units = kittycad::types::UnitLength::from_str(units).map_err(|e| e.to_string())?; let engine = kcl_lib::engine::conn_wasm::EngineConnection::new(engine_manager) @@ -41,9 +41,7 @@ pub async fn execute_wasm( is_mock, }; - let memory = kcl_lib::executor::execute_outer(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx) - .await - .map_err(String::from)?; + let memory = ctx.run(program, Some(memory)).await.map_err(String::from)?; // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the // gloo-serialize crate instead. JsValue::from_serde(&memory).map_err(|e| e.to_string()) @@ -210,7 +208,7 @@ pub async fn kcl_lsp_run(config: ServerConfig, token: String, is_dev: bool) -> R } }; - let (service, socket) = LspService::new(|client| kcl_lib::lsp::kcl::Backend { + let (service, socket) = LspService::build(|client| kcl_lib::lsp::kcl::Backend { client, fs: kcl_lib::fs::FileManager::new(fs), workspace_folders: Default::default(), @@ -219,13 +217,17 @@ pub async fn kcl_lsp_run(config: ServerConfig, token: String, is_dev: bool) -> R token_types, token_map: Default::default(), ast_map: Default::default(), + memory_map: Default::default(), current_code_map: Default::default(), diagnostics_map: Default::default(), symbols_map: Default::default(), semantic_tokens_map: Default::default(), zoo_client, can_send_telemetry: privacy_settings.can_train_on_data, - }); + executor_ctx: Default::default(), + }) + .custom_method("kcl/updateUnits", kcl_lib::lsp::kcl::Backend::update_units) + .finish(); let input = wasm_bindgen_futures::stream::JsStream::from(into_server); let input = input @@ -279,13 +281,19 @@ pub async fn copilot_lsp_run(config: ServerConfig, token: String, is_dev: bool) telemetry: Default::default(), zoo_client, }) - .custom_method("setEditorInfo", kcl_lib::lsp::copilot::Backend::set_editor_info) + .custom_method("copilot/setEditorInfo", kcl_lib::lsp::copilot::Backend::set_editor_info) .custom_method( - "getCompletions", + "copilot/getCompletions", kcl_lib::lsp::copilot::Backend::get_completions_cycling, ) - .custom_method("notifyAccepted", kcl_lib::lsp::copilot::Backend::accept_completion) - .custom_method("notifyRejected", kcl_lib::lsp::copilot::Backend::reject_completions) + .custom_method( + "copilot/notifyAccepted", + kcl_lib::lsp::copilot::Backend::accept_completion, + ) + .custom_method( + "copilot/notifyRejected", + kcl_lib::lsp::copilot::Backend::reject_completions, + ) .finish(); let input = wasm_bindgen_futures::stream::JsStream::from(into_server); diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 5ad543d816..95896997de 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -38,10 +38,9 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) -> let tokens = kcl_lib::token::lexer(code); let parser = kcl_lib::parser::Parser::new(tokens); let program = parser.ast()?; - let mut mem: kcl_lib::executor::ProgramMemory = Default::default(); let ctx = kcl_lib::executor::ExecutorContext::new(ws, units.clone()).await?; - let _ = kcl_lib::executor::execute_outer(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?; + let _ = ctx.run(program, None).await?; let (x, y) = kcl_lib::std::utils::get_camera_zoom_magnitude_per_unit_length(units); diff --git a/src/wasm-lib/tests/modify/main.rs b/src/wasm-lib/tests/modify/main.rs index 134b3459ee..1dff4a93e2 100644 --- a/src/wasm-lib/tests/modify/main.rs +++ b/src/wasm-lib/tests/modify/main.rs @@ -39,10 +39,8 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid let tokens = kcl_lib::token::lexer(code); let parser = kcl_lib::parser::Parser::new(tokens); let program = parser.ast()?; - let mut mem: kcl_lib::executor::ProgramMemory = Default::default(); let ctx = kcl_lib::executor::ExecutorContext::new(ws, kittycad::types::UnitLength::Mm).await?; - let memory = - kcl_lib::executor::execute_outer(program.clone(), &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?; + let memory = ctx.run(program.clone(), None).await?; // We need to get the sketch ID. // Get the sketch group ID from memory.