Compare commits

...

7 Commits

Author SHA1 Message Date
b686c79b49 More lsp endpoints we were missing (#6612)
* add prepare rename

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add document color

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-01 02:47:36 +00:00
2d77aa0d36 Revert "Make onboarding optional, able to be ignored on desktop" (#6610)
Revert "Make onboarding optional, able to be ignored on desktop (#6564)"

This reverts commit 820082d7f2.
2025-04-30 21:58:11 -04:00
012102fe86 enhance the signature help (#6606)
update with test

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-30 20:58:46 +00:00
1a6b147107 Fix to not double import math in the prelude (#6605) 2025-04-30 19:49:55 +00:00
2978b34b7b Skip TAB-disabled tests locally by default (#6601) 2025-04-30 15:32:37 -04:00
dee77e814a remove rust bw compatible settings (#6085)
regenerate the settings docs



cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-30 19:00:37 +00:00
820082d7f2 Make onboarding optional, able to be ignored on desktop (#6564)
* Remove unused `telemetryLoader`

* Remove onboarding redirect behavior

* Allow subRoute to be passed to navigateToProject

* Replace warning dialog routes with toasts

* Wire up new utilities and toasts to UI components

* Add home sidebar buttons for tutorial flow

* Rename menu item

* Add flex-1 so home-layout fills available space

* Remove onboarding avatar tests, they are becoming irrelevant

* Consolidate onboarding tests to one longer one

and update it to not use pixel color checks, and use fixtures.

* Shorten warning toast button text

* tsc, lint, and circular deps

* Update circular dep file

* Fix mistakes made in circular update tweaking

* One more dumb created circular dep

* Update src/routes/Onboarding/utils.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Fix narrow screen home layout breaking

* fix: kevin, navigation routes fixed

* fix: filename parsing is correct now for onboarding with the last file sep

* Fix e2e test state checks that are diff on Linux

* Create onboarding project entirely through systemIOMachine

* Fix Windows path construction

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
2025-04-30 14:43:51 -04:00
30 changed files with 883 additions and 706 deletions

View File

@ -7,11 +7,11 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
# If no last run artifact, than run Playwright normally
echo "run playwright normally"
if [[ "$3" == *ubuntu* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron:ubuntu -- --shard=$1/$2 || true
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --shard=$1/$2 || true
elif [[ "$3" == *windows* ]]; then
npm run test:playwright:electron:windows -- --shard=$1/$2 || true
npm run test:playwright:electron -- --grep=@windows --shard=$1/$2 || true
elif [[ "$3" == *macos* ]]; then
npm run test:playwright:electron:macos -- --shard=$1/$2 || true
npm run test:playwright:electron -- --grep=@macos --shard=$1/$2 || true
else
echo "Do not run Playwright. Unable to detect os runtime."
exit 1
@ -31,11 +31,11 @@ while [[ $retry -le $max_retries ]]; do
echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry"
if [[ "$3" == *ubuntu* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron:ubuntu -- --last-failed || true
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --last-failed || true
elif [[ "$3" == *windows* ]]; then
npm run test:playwright:electron:windows -- --last-failed || true
npm run test:playwright:electron -- --grep=@windows --last-failed || true
elif [[ "$3" == *macos* ]]; then
npm run test:playwright:electron:macos -- --last-failed || true
npm run test:playwright:electron -- --grep=@macos --last-failed || true
else
echo "Do not run playwright. Unable to detect os runtime."
exit 1

View File

@ -55,20 +55,6 @@ This setting has further nested options. See the schema for full details.
The onboarding status of the app.
**Default:** None
##### theme_color
The hue of the primary theme color for the app.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
##### dismiss_web_banner
@ -128,13 +114,6 @@ The default unit to use in modeling dimensions.
Highlight edges of 3D objects?
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development. Remove this when we remove backwards compatibility with the old settings file.
**Default:** None
##### enable_ssao

View File

@ -57,34 +57,6 @@ This setting has further nested options. See the schema for full details.
The onboarding status of the app.
**Default:** None
##### project_directory
Backwards compatible project directory setting.
**Default:** None
##### theme
Backwards compatible theme setting.
**Default:** None
##### theme_color
The hue of the primary theme color for the app.
**Default:** None
##### enable_ssao
Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
**Default:** None
##### dismiss_web_banner
@ -103,7 +75,7 @@ When the user is idle, teardown the stream after some time.
##### allow_orbit_in_sketch_mode
When the user is idle, and this is true, the stream will be torn down.
Allow orbiting in sketch mode.
**Default:** None
@ -159,13 +131,6 @@ The controls for how to navigate the 3D view.
Highlight edges of 3D objects?
**Default:** None
##### show_debug_panel
Whether to show the debug panel, which lets you see various states of the app to aid in development. Remove this when we remove backwards compatibility with the old settings file.
**Default:** None
##### enable_ssao

View File

@ -14,9 +14,6 @@ class MyAPIReporter implements Reporter {
await Promise.all(this.pendingRequests)
if (this.allResults.length === 0) {
if (!process.env.CI) {
console.error('TAB API - No results to process')
}
return
}

View File

@ -21,6 +21,7 @@ import {
executorInputPath,
getUtils,
networkingMasks,
settingsToToml,
tomlToSettings,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
@ -510,7 +511,15 @@ test.describe(
)
await fsp.writeFile(
tempSettingsFilePath,
`[settings.app]\nthemeColor = "${color}"`
settingsToToml({
settings: {
app: {
appearance: {
color: parseFloat(color),
},
},
},
})
)
}

30
flake.lock generated
View File

@ -5,11 +5,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1743800763,
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
"lastModified": 1745925850,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"owner": "nix-community",
"repo": "naersk",
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1744316434,
"narHash": "sha256-lzFCg/1C39pyY2hMB2gcuHV79ozpOz/Vu15hdjiFOfI=",
"lastModified": 1745998881,
"narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d19cf9dfc633816a437204555afeb9e722386b76",
"rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
"type": "github"
},
"original": {
@ -36,11 +36,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744316434,
"narHash": "sha256-lzFCg/1C39pyY2hMB2gcuHV79ozpOz/Vu15hdjiFOfI=",
"lastModified": 1745998881,
"narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d19cf9dfc633816a437204555afeb9e722386b76",
"rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
"type": "github"
},
"original": {
@ -52,11 +52,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
@ -78,11 +78,11 @@
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1744338850,
"narHash": "sha256-pwMIVmsb8fjjT92n5XFDqCsplcX70qVMMT7NulumPXs=",
"lastModified": 1745980514,
"narHash": "sha256-CITAeiuXGjDvT5iZBXr6vKVWQwsUQLJUMFO91bfJFC4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "5e64aecc018e6f775572609e7d7485fdba6985a7",
"rev": "7fbdae44b0f40ea432e46fd152ad8be0f8f41ad6",
"type": "github"
},
"original": {

View File

@ -124,7 +124,6 @@
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "electron-rebuild",
"make:dev": "make dev",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
"tron:start": "electron-forge start",
@ -139,14 +138,8 @@
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@windows --quiet",
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@macos --quiet",
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --quiet",
"test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:windows:local": "npm run tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:macos:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:ubuntu:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:ubuntu:engine:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine'",
"test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
},

View File

@ -547,19 +547,10 @@ export class LanguageServerPlugin implements PluginValue {
try {
// First check if rename is possible at this position
const prepareResult = await this.client
.textDocumentPrepareRename({
textDocument: { uri: this.getDocUri() },
position: { line, character },
})
.catch(() => {
// In case prepareRename is not supported,
// we fallback to the default implementation
return this.prepareRenameFallback(view, {
line,
character,
})
})
let prepareResult = this.prepareRenameFallback(view, {
line,
character,
})
if (!prepareResult || 'defaultBehavior' in prepareResult) {
showErrorMessage(view, 'Cannot rename this symbol')

58
rust/Cargo.lock generated
View File

@ -713,6 +713,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "csscolorparser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288"
dependencies = [
"phf",
]
[[package]]
name = "darling"
version = "0.20.10"
@ -1899,6 +1908,7 @@ dependencies = [
"console_error_panic_hook",
"convert_case",
"criterion",
"csscolorparser",
"dashmap 6.1.0",
"dhat",
"expectorate",
@ -2630,6 +2640,48 @@ dependencies = [
"sha2",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand 0.8.5",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "phonenumber"
version = "0.3.7+8.13.52"
@ -3575,6 +3627,12 @@ version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"

View File

@ -31,6 +31,7 @@ clap = { version = "4.5.36", default-features = false, optional = true, features
"derive",
] }
convert_case = "0.8.0"
csscolorparser = "0.7.0"
dashmap = { workspace = true }
dhat = { version = "0.3", optional = true }
fnv = "1.0.7"

View File

@ -533,14 +533,18 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
SignatureHelp {
signatures: vec![SignatureInformation {
label: self.name(),
label: self.fn_signature(true),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: if !self.description().is_empty() {
format!("{}\n\n{}", self.summary(), self.description())
} else {
self.summary()
},
value: format!(
r#"{}
{}"#,
self.summary(),
self.description()
)
.trim()
.to_string(),
})),
parameters: Some(self.args(true).into_iter().map(|arg| arg.into()).collect()),
active_parameter,
@ -1116,4 +1120,21 @@ mod tests {
let kcl_std = crate::docs::kcl_doc::walk_prelude();
crate::lsp::kcl::get_signatures_from_stdlib(&stdlib, &kcl_std);
}
#[test]
fn get_extrude_signature_help() {
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
let sh = extrude_fn.to_signature_help();
assert_eq!(
sh.signatures[0].label,
r#"extrude(
sketches: [Sketch],
length: number,
symmetric?: bool,
bidirectionalLength?: number,
tagStart?: TagNode,
tagEnd?: TagNode,
): [Solid]"#
);
}
}

View File

@ -8,12 +8,11 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::engine::{PlaneName, DEFAULT_PLANE_INFO};
use crate::errors::KclErrorDetails;
#[cfg(feature = "artifact-graph")]
use crate::execution::ArtifactId;
use crate::{
errors::KclError,
engine::{PlaneName, DEFAULT_PLANE_INFO},
errors::{KclError, KclErrorDetails},
execution::{types::NumericType, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
std::{args::TyF64, sketch::PlaneData},

View File

@ -2,13 +2,14 @@ use anyhow::Result;
use crate::{
errors::Suggestion,
lint::rule::{def_finding, Discovered, Finding},
lint::{
checks::offset_plane::start_sketch_on_check_specific_plane,
rule::{def_finding, Discovered, Finding},
},
parsing::ast::types::{Node as AstNode, Program},
walk::Node,
};
use super::offset_plane::start_sketch_on_check_specific_plane;
def_finding!(
Z0002,
"default plane should be called versus explicitly defined",

View File

@ -18,23 +18,24 @@ use tower_lsp::{
jsonrpc::Result as RpcResult,
lsp_types::{
CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
CodeActionProviderCapability, CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions,
CompletionParams, CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport,
DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams,
DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability,
FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams, InsertTextFormat,
MarkupContent, MarkupKind, MessageType, OneOf, Position, RelatedFullDocumentDiagnosticReport,
RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams,
SemanticTokensRegistrationOptions, SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities,
SignatureHelp, SignatureHelpOptions, SignatureHelpParams, StaticRegistrationOptions, TextDocumentItem,
TextDocumentRegistrationOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextEdit, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
},
Client, LanguageServer,
};
@ -827,6 +828,39 @@ impl Backend {
Ok(custom_notifications::UpdateCanExecuteResponse {})
}
/// Returns the new string for the code after rename.
pub fn inner_prepare_rename(
&self,
params: &TextDocumentPositionParams,
new_name: &str,
) -> RpcResult<Option<(String, String)>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(None);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(None);
};
// Parse the ast.
// I don't know if we need to do this again since it should be updated in the context.
// But I figure better safe than sorry since this will write back out to the file.
let module_id = ModuleId::default();
let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
return Ok(None);
};
// Let's convert the position to a character index.
let pos = position_to_char_index(params.position, current_code);
// Now let's perform the rename on the ast.
ast.rename_symbol(new_name, pos);
// Now recast it.
let recast = ast.recast(&Default::default(), 0);
Ok(Some((current_code.to_string(), recast)))
}
}
#[tower_lsp::async_trait]
@ -838,6 +872,7 @@ impl LanguageServer for Backend {
Ok(InitializeResult {
capabilities: ServerCapabilities {
color_provider: Some(ColorProviderCapability::Simple(true)),
code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
resolve_provider: Some(false),
@ -1459,36 +1494,19 @@ impl LanguageServer for Backend {
}
async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
let filename = params.text_document_position.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(None);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
let Some((current_code, new_code)) =
self.inner_prepare_rename(&params.text_document_position, &params.new_name)?
else {
return Ok(None);
};
// Parse the ast.
// I don't know if we need to do this again since it should be updated in the context.
// But I figure better safe than sorry since this will write back out to the file.
let module_id = ModuleId::default();
let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
return Ok(None);
};
// Let's convert the position to a character index.
let pos = position_to_char_index(params.text_document_position.position, current_code);
// Now let's perform the rename on the ast.
ast.rename_symbol(&params.new_name, pos);
// Now recast it.
let recast = ast.recast(&Default::default(), 0);
let source_range = SourceRange::new(0, current_code.len(), module_id);
let range = source_range.to_lsp_range(current_code);
let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
let range = source_range.to_lsp_range(&current_code);
Ok(Some(WorkspaceEdit {
changes: Some(HashMap::from([(
params.text_document_position.text_document.uri,
vec![TextEdit {
new_text: recast,
new_text: new_code,
range,
}],
)])),
@ -1497,6 +1515,18 @@ impl LanguageServer for Backend {
}))
}
async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
if self
.inner_prepare_rename(&params, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
.is_none()
{
return Ok(None);
}
// Return back to the client, that it is safe to use the rename behavior.
Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
}
async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
let filename = params.text_document.uri.to_string();
@ -1552,6 +1582,55 @@ impl LanguageServer for Backend {
Ok(Some(actions))
}
async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(vec![]);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(vec![]);
};
// Get the ast from our map.
let Some(ast) = self.ast_map.get(&filename) else {
return Ok(vec![]);
};
// Get the colors from the ast.
let Ok(colors) = ast.ast.document_color(current_code) else {
return Ok(vec![]);
};
Ok(colors)
}
async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
let filename = params.text_document.uri.to_string();
let Some(current_code) = self.code_map.get(&filename) else {
return Ok(vec![]);
};
let Ok(current_code) = std::str::from_utf8(&current_code) else {
return Ok(vec![]);
};
// Get the ast from our map.
let Some(ast) = self.ast_map.get(&filename) else {
return Ok(vec![]);
};
let pos_start = position_to_char_index(params.range.start, current_code);
let pos_end = position_to_char_index(params.range.end, current_code);
// Get the colors from the ast.
let Ok(Some(presentation)) = ast.ast.color_presentation(&params.color, pos_start, pos_end) else {
return Ok(vec![]);
};
Ok(vec![presentation])
}
}
/// Get completions from our stdlib.
@ -1662,3 +1741,48 @@ async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T>
Some(f(value))
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_position_to_char_index_first_line() {
let code = r#"def foo():
return 42"#;
let position = Position::new(0, 3);
let index = position_to_char_index(position, code);
assert_eq!(index, 3);
}
#[test]
fn test_position_to_char_index() {
let code = r#"def foo():
return 42"#;
let position = Position::new(1, 4);
let index = position_to_char_index(position, code);
assert_eq!(index, 15);
}
#[test]
fn test_position_to_char_index_with_newline() {
let code = r#"def foo():
return 42"#;
let position = Position::new(2, 0);
let index = position_to_char_index(position, code);
assert_eq!(index, 12);
}
#[test]
fn test_position_to_char_at_end() {
let code = r#"def foo():
return 42"#;
let position = Position::new(1, 8);
let index = position_to_char_index(position, code);
assert_eq!(index, 19);
}
}

View File

@ -3,8 +3,8 @@ use std::collections::{BTreeMap, HashMap};
use pretty_assertions::assert_eq;
use tower_lsp::{
lsp_types::{
CodeActionKind, CodeActionOrCommand, Diagnostic, SemanticTokenModifier, SemanticTokenType, TextEdit,
WorkspaceEdit,
CodeActionKind, CodeActionOrCommand, Diagnostic, PrepareRenameResponse, SemanticTokenModifier,
SemanticTokenType, TextEdit, WorkspaceEdit,
},
LanguageServer,
};
@ -1113,7 +1113,13 @@ async fn test_kcl_lsp_signature_help() {
"Expected one signature, got {:?}",
signature_help.signatures
);
assert_eq!(signature_help.signatures[0].label, "startSketchOn");
assert_eq!(
signature_help.signatures[0].label,
r#"startSketchOn(
planeOrSolid: SketchData,
face?: FaceTag,
): SketchSurface"#
);
} else {
panic!("Expected signature help");
}
@ -1196,7 +1202,17 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
"Expected one signature, got {:?}",
signature_help.signatures
);
assert_eq!(signature_help.signatures[0].label, "extrude");
assert_eq!(
signature_help.signatures[0].label,
r#"extrude(
sketches: [Sketch],
length: number,
symmetric?: bool,
bidirectionalLength?: number,
tagStart?: TagNode,
tagEnd?: TagNode,
): [Solid]"#
);
} else {
panic!("Expected signature help");
}
@ -1284,7 +1300,17 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
"Expected one signature, got {:?}",
signature_help.signatures
);
assert_eq!(signature_help.signatures[0].label, "extrude");
assert_eq!(
signature_help.signatures[0].label,
r#"extrude(
sketches: [Sketch],
length: number,
symmetric?: bool,
bidirectionalLength?: number,
tagStart?: TagNode,
tagEnd?: TagNode,
): [Solid]"#
);
} else {
panic!("Expected signature help");
}
@ -1367,7 +1393,17 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
"Expected one signature, got {:?}",
signature_help.signatures
);
assert_eq!(signature_help.signatures[0].label, "extrude");
assert_eq!(
signature_help.signatures[0].label,
r#"extrude(
sketches: [Sketch],
length: number,
symmetric?: bool,
bidirectionalLength?: number,
tagStart?: TagNode,
tagEnd?: TagNode,
): [Solid]"#
);
} else {
panic!("Expected signature help");
}
@ -1455,7 +1491,17 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
"Expected one signature, got {:?}",
signature_help.signatures
);
assert_eq!(signature_help.signatures[0].label, "extrude");
assert_eq!(
signature_help.signatures[0].label,
r#"extrude(
sketches: [Sketch],
length: number,
symmetric?: bool,
bidirectionalLength?: number,
tagStart?: TagNode,
tagEnd?: TagNode,
): [Solid]"#
);
} else {
panic!("Expected signature help");
}
@ -4100,3 +4146,173 @@ async fn kcl_test_kcl_lsp_code_actions_lint_offset_planes() {
})
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_prepare_rename() {
let server = kcl_lsp_server(false).await.unwrap();
// 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: r#"thing= 1"#.to_string(),
},
})
.await;
// Send rename request.
let result = server
.prepare_rename(tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
})
.await
.unwrap()
.unwrap();
// Check the result.
assert_eq!(
result,
PrepareRenameResponse::DefaultBehavior { default_behavior: true }
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_document_color() {
let server = kcl_lsp_server(false).await.unwrap();
// 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: r#"// Add color to a revolved solid.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = Y)
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)"#
.to_string(),
},
})
.await;
// Send document color request.
let result = server
.document_color(tower_lsp::lsp_types::DocumentColorParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
.await
.unwrap();
// Check the result.
assert_eq!(
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
},
}]
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_color_presentation() {
let server = kcl_lsp_server(false).await.unwrap();
let text = r#"// Add color to a revolved solid.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = Y)
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)"#;
// 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: text.to_string(),
},
})
.await;
// Send document color request.
let result = server
.document_color(tower_lsp::lsp_types::DocumentColorParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
.await
.unwrap();
// Check the result.
assert_eq!(
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
},
}]
);
// Send color presentation request.
let result = server
.color_presentation(tower_lsp::lsp_types::ColorPresentationParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
green: 0.0,
blue: 1.0,
alpha: 1.0,
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
.await
.unwrap();
// Check the result.
assert_eq!(
result,
vec![tower_lsp::lsp_types::ColorPresentation {
label: "#ff00ff".to_string(),
text_edit: None,
additional_text_edits: None,
}]
);
}

View File

@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, str::FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -32,6 +32,21 @@ impl LiteralValue {
_ => None,
}
}
pub fn is_color(&self) -> Option<csscolorparser::Color> {
if let Self::String(s) = self {
// Check if the string is a color.
if s.starts_with('#') && s.len() == 7 {
let Ok(c) = csscolorparser::Color::from_str(s) else {
return None;
};
return Some(c);
}
}
None
}
}
impl fmt::Display for LiteralValue {

View File

@ -14,7 +14,8 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, SymbolKind,
Color, ColorInformation, ColorPresentation, CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange,
FoldingRangeKind, SymbolKind,
};
pub use crate::parsing::ast::types::{
@ -389,6 +390,99 @@ impl Node<Program> {
true
}
/// Find all the color strings in the program.
/// For example `appearance(color = "#ff0000")`
/// This is to fulfill the `documentColor` request in LSP.
pub fn document_color<'a>(&'a self, code: &str) -> Result<Vec<ColorInformation>> {
let colors = Rc::new(RefCell::new(vec![]));
let add_color = |literal: &Node<Literal>| {
// Check if the string is a color.
if let Some(c) = literal.value.is_color() {
let color = ColorInformation {
range: literal.as_source_range().to_lsp_range(code),
color: tower_lsp::lsp_types::Color {
red: c.r,
green: c.g,
blue: c.b,
alpha: c.a,
},
};
if colors.borrow().iter().any(|c| *c == color) {
return;
}
colors.borrow_mut().push(color);
}
};
// The position must be within the variable declaration.
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
match node {
crate::walk::Node::CallExpressionKw(call) => {
if call.inner.callee.inner.name.inner.name == "appearance" {
for arg in &call.arguments {
if arg.label.inner.name == "color" {
// Get the value of the argument.
if let Expr::Literal(literal) = &arg.arg {
add_color(literal);
}
}
}
}
}
crate::walk::Node::Literal(literal) => {
// Check if the literal is a color.
add_color(literal);
}
_ => {
// Do nothing.
}
}
Ok::<bool, anyhow::Error>(true)
})?;
let colors = colors.take();
Ok(colors)
}
/// This is to fulfill the `colorPresentation` request in LSP.
pub fn color_presentation<'a>(
&'a self,
color: &Color,
pos_start: usize,
pos_end: usize,
) -> Result<Option<ColorPresentation>> {
let found = Rc::new(RefCell::new(false));
// Find the literal with the same start and end.
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
match node {
crate::walk::Node::Literal(literal) => {
if literal.start == pos_start && literal.end == pos_end && literal.value.is_color().is_some() {
found.replace(true);
return Ok(true);
}
}
_ => {
// Do nothing.
}
}
Ok::<bool, anyhow::Error>(true)
})?;
let found = found.take();
if !found {
return Ok(None);
}
let new_color = csscolorparser::Color::new(color.red, color.green, color.blue, color.alpha);
Ok(Some(ColorPresentation {
// The label will be what they replace the color with.
label: new_color.to_hex_string(),
text_edit: None,
additional_text_edits: None,
}))
}
}
impl Program {

View File

@ -27,42 +27,8 @@ pub struct Configuration {
}
impl Configuration {
// TODO: remove this when we remove backwards compatibility with the old settings file.
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
let mut settings = toml::from_str::<Self>(toml_str)?;
if let Some(project_directory) = &settings.settings.app.project_directory {
if settings.settings.project.directory.to_string_lossy().is_empty() {
settings.settings.project.directory.clone_from(project_directory);
settings.settings.app.project_directory = None;
}
}
if let Some(theme) = &settings.settings.app.theme {
if settings.settings.app.appearance.theme == AppTheme::default() {
settings.settings.app.appearance.theme = *theme;
settings.settings.app.theme = None;
}
}
if let Some(theme_color) = &settings.settings.app.theme_color {
if settings.settings.app.appearance.color == AppColor::default() {
settings.settings.app.appearance.color = theme_color.clone().into();
settings.settings.app.theme_color = None;
}
}
if let Some(enable_ssao) = settings.settings.app.enable_ssao {
if settings.settings.modeling.enable_ssao.into() {
settings.settings.modeling.enable_ssao = enable_ssao.into();
settings.settings.app.enable_ssao = None;
}
}
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
pub fn parse_and_validate(toml_str: &str) -> Result<Self> {
let settings = toml::from_str::<Self>(toml_str)?;
settings.validate()?;
@ -84,22 +50,20 @@ pub struct Settings {
#[validate(nested)]
pub modeling: ModelingSettings,
/// Settings that affect the behavior of the KCL text editor.
#[serde(default, alias = "textEditor", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub text_editor: TextEditorSettings,
/// Settings that affect the behavior of project management.
#[serde(default, alias = "projects", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub project: ProjectSettings,
/// Settings that affect the behavior of the command bar.
#[serde(default, alias = "commandBar", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub command_bar: CommandBarSettings,
}
/// Application wide settings.
// TODO: When we remove backwards compatibility with the old settings file, we can remove the
// aliases to camelCase (and projects plural) from everywhere.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
@ -109,27 +73,11 @@ pub struct AppSettings {
#[validate(nested)]
pub appearance: AppearanceSettings,
/// The onboarding status of the app.
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub onboarding_status: OnboardingStatus,
/// Backwards compatible project directory setting.
#[serde(default, alias = "projectDirectory", skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub project_directory: Option<std::path::PathBuf>,
/// Backwards compatible theme setting.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub theme: Option<AppTheme>,
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "Option::is_none", alias = "themeColor")]
#[ts(skip)]
pub theme_color: Option<FloatOrInt>,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, alias = "enableSSAO", skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub enable_ssao: Option<bool>,
/// Permanently dismiss the banner warning to download the desktop app.
/// This setting only applies to the web app. And is temporary until we have Linux support.
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub dismiss_web_banner: bool,
/// When the user is idle, teardown the stream after some time.
#[serde(
@ -139,12 +87,12 @@ pub struct AppSettings {
skip_serializing_if = "is_default"
)]
stream_idle_mode: Option<u32>,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
/// Allow orbiting in sketch mode.
#[serde(default, skip_serializing_if = "is_default")]
pub allow_orbit_in_sketch_mode: bool,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
}
@ -300,31 +248,25 @@ impl From<AppTheme> for kittycad::types::Color {
#[ts(export)]
pub struct ModelingSettings {
/// The default unit to use in modeling dimensions.
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub base_unit: UnitLength,
/// The projection mode the camera should use while modeling.
#[serde(default, alias = "cameraProjection", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub camera_projection: CameraProjectionType,
/// The methodology the camera should use to orbit around the model.
#[serde(default, alias = "cameraOrbit", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub camera_orbit: CameraOrbitType,
/// The controls for how to navigate the 3D view.
#[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub mouse_controls: MouseControlType,
/// Highlight edges of 3D objects?
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
#[ts(skip)]
pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, skip_serializing_if = "is_default")]
pub enable_ssao: DefaultTrue,
/// Whether or not to show a scale grid in the 3D modeling view
#[serde(default, alias = "showScaleGrid", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub show_scale_grid: bool,
}
@ -435,21 +377,17 @@ impl From<UnitLength> for kittycad_modeling_cmds::units::UnitLength {
pub enum MouseControlType {
#[default]
#[display("zoo")]
#[serde(rename = "zoo", alias = "Zoo", alias = "KittyCAD")]
#[serde(rename = "zoo")]
Zoo,
#[display("onshape")]
#[serde(rename = "onshape", alias = "OnShape")]
#[serde(rename = "onshape")]
OnShape,
#[serde(alias = "Trackpad Friendly")]
TrackpadFriendly,
#[serde(alias = "Solidworks")]
Solidworks,
#[serde(alias = "NX")]
Nx,
#[serde(alias = "Creo")]
Creo,
#[display("autocad")]
#[serde(rename = "autocad", alias = "AutoCAD")]
#[serde(rename = "autocad")]
AutoCad,
}
@ -487,10 +425,10 @@ pub enum CameraOrbitType {
#[ts(export)]
pub struct TextEditorSettings {
/// Whether to wrap text in the editor or overflow with scroll.
#[serde(default, alias = "textWrapping", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub text_wrapping: DefaultTrue,
/// Whether to make the cursor blink in the editor.
#[serde(default, alias = "blinkingCursor", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub blinking_cursor: DefaultTrue,
}
@ -503,7 +441,7 @@ pub struct ProjectSettings {
#[serde(default, skip_serializing_if = "is_default")]
pub directory: std::path::PathBuf,
/// The default project name to use when creating a new project.
#[serde(default, alias = "defaultProjectName", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub default_project_name: ProjectNameTemplate,
}
@ -536,7 +474,7 @@ impl From<String> for ProjectNameTemplate {
#[ts(export)]
pub struct CommandBarSettings {
/// Whether to include settings in the command bar.
#[serde(default, alias = "includeSettings", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub include_settings: DefaultTrue,
}
@ -611,305 +549,9 @@ mod tests {
use super::{
AppColor, AppSettings, AppTheme, AppearanceSettings, CameraProjectionType, CommandBarSettings, Configuration,
ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength,
ModelingSettings, MouseControlType, OnboardingStatus, ProjectNameTemplate, ProjectSettings, Settings,
TextEditorSettings, UnitLength,
};
use crate::settings::types::CameraOrbitType;
#[test]
// Test that we can deserialize a project file from the old format.
// TODO: We can remove this functionality after a few versions.
fn test_backwards_compatible_project_settings_file_pw() {
let old_project_file = r#"[settings.app]
theme = "dark"
onboardingStatus = "dismissed"
projectDirectory = ""
enableSSAO = false
[settings.modeling]
defaultUnit = "in"
cameraProjection = "orthographic"
mouseControls = "KittyCAD"
showDebugPanel = true
[settings.projects]
defaultProjectName = "untitled"
[settings.textEditor]
textWrapping = true
#"#;
//let parsed = toml::from_str::<Configuration(old_project_file).unwrap();
let parsed = Configuration::backwards_compatible_toml_parse(old_project_file).unwrap();
assert_eq!(
parsed,
Configuration {
settings: Settings {
app: AppSettings {
appearance: AppearanceSettings {
theme: AppTheme::Dark,
color: Default::default()
},
onboarding_status: OnboardingStatus::Dismissed,
project_directory: None,
theme: None,
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
stream_idle_mode: None,
allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
},
modeling: ModelingSettings {
base_unit: UnitLength::In,
camera_projection: CameraProjectionType::Orthographic,
camera_orbit: Default::default(),
mouse_controls: Default::default(),
show_debug_panel: Default::default(),
highlight_edges: Default::default(),
enable_ssao: false.into(),
show_scale_grid: false,
},
text_editor: TextEditorSettings {
text_wrapping: true.into(),
blinking_cursor: true.into()
},
project: Default::default(),
command_bar: CommandBarSettings {
include_settings: true.into()
},
}
}
);
}
#[test]
// Test that we can deserialize a project file from the old format.
// TODO: We can remove this functionality after a few versions.
fn test_backwards_compatible_project_settings_file() {
let old_project_file = r#"[settings.app]
theme = "dark"
themeColor = "138"
[settings.modeling]
defaultUnit = "yd"
showDebugPanel = true
[settings.textEditor]
textWrapping = false
blinkingCursor = false
[settings.commandBar]
includeSettings = false
#"#;
//let parsed = toml::from_str::<Configuration(old_project_file).unwrap();
let parsed = Configuration::backwards_compatible_toml_parse(old_project_file).unwrap();
assert_eq!(
parsed,
Configuration {
settings: Settings {
app: AppSettings {
appearance: AppearanceSettings {
theme: AppTheme::Dark,
color: 138.0.into()
},
onboarding_status: Default::default(),
project_directory: None,
theme: None,
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
show_debug_panel: true,
stream_idle_mode: None,
allow_orbit_in_sketch_mode: false,
},
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(),
enable_ssao: true.into(),
show_scale_grid: false,
show_debug_panel: Default::default(),
},
text_editor: TextEditorSettings {
text_wrapping: false.into(),
blinking_cursor: false.into()
},
project: Default::default(),
command_bar: CommandBarSettings {
include_settings: false.into()
},
}
}
);
}
#[test]
// Test that we can deserialize a app settings file from the old format.
// TODO: We can remove this functionality after a few versions.
fn test_backwards_compatible_app_settings_file() {
let old_app_settings_file = r#"[settings.app]
onboardingStatus = "dismissed"
projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects"
theme = "dark"
themeColor = "138"
[settings.modeling]
defaultUnit = "yd"
showDebugPanel = true
[settings.textEditor]
textWrapping = false
blinkingCursor = false
[settings.commandBar]
includeSettings = false
[settings.projects]
defaultProjectName = "projects-$nnn"
#"#;
//let parsed = toml::from_str::<Configuration>(old_app_settings_file).unwrap();
let parsed = Configuration::backwards_compatible_toml_parse(old_app_settings_file).unwrap();
assert_eq!(
parsed,
Configuration {
settings: Settings {
app: AppSettings {
appearance: AppearanceSettings {
theme: AppTheme::Dark,
color: 138.0.into()
},
onboarding_status: OnboardingStatus::Dismissed,
project_directory: None,
theme: None,
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
stream_idle_mode: None,
allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
},
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: CameraOrbitType::Spherical,
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: Default::default(),
enable_ssao: true.into(),
show_scale_grid: false,
},
text_editor: TextEditorSettings {
text_wrapping: false.into(),
blinking_cursor: false.into()
},
project: ProjectSettings {
directory: "/Users/macinatormax/Documents/kittycad-modeling-projects".into(),
default_project_name: "projects-$nnn".to_string().into()
},
command_bar: CommandBarSettings {
include_settings: false.into()
},
}
}
);
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
onboarding_status = "dismissed"
show_debug_panel = true
[settings.app.appearance]
theme = "dark"
color = 138.0
[settings.modeling]
base_unit = "yd"
[settings.text_editor]
text_wrapping = false
blinking_cursor = false
[settings.project]
directory = "/Users/macinatormax/Documents/kittycad-modeling-projects"
default_project_name = "projects-$nnn"
[settings.command_bar]
include_settings = false
"#
);
}
#[test]
fn test_settings_backwards_compat_partial() {
let partial_settings_file = r#"[settings.app]
onboardingStatus = "dismissed"
projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
//let parsed = toml::from_str::<Configuration>(partial_settings_file).unwrap();
let parsed = Configuration::backwards_compatible_toml_parse(partial_settings_file).unwrap();
assert_eq!(
parsed,
Configuration {
settings: Settings {
app: AppSettings {
appearance: AppearanceSettings {
theme: AppTheme::System,
color: Default::default()
},
onboarding_status: OnboardingStatus::Dismissed,
project_directory: None,
theme: None,
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
show_debug_panel: false,
stream_idle_mode: None,
allow_orbit_in_sketch_mode: false,
},
modeling: ModelingSettings {
base_unit: UnitLength::Mm,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: true.into(),
show_debug_panel: Default::default(),
enable_ssao: true.into(),
show_scale_grid: false,
},
text_editor: TextEditorSettings {
text_wrapping: true.into(),
blinking_cursor: true.into()
},
project: ProjectSettings {
directory: "/Users/macinatormax/Documents/kittycad-modeling-projects".into(),
default_project_name: "untitled".to_string().into()
},
command_bar: CommandBarSettings {
include_settings: true.into()
},
}
}
);
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
onboarding_status = "dismissed"
[settings.project]
directory = "/Users/macinatormax/Documents/kittycad-modeling-projects"
"#
);
}
#[test]
fn test_settings_empty_file_parses() {
@ -922,10 +564,87 @@ directory = "/Users/macinatormax/Documents/kittycad-modeling-projects"
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(serialized, r#""#);
let parsed = Configuration::backwards_compatible_toml_parse(empty_settings_file).unwrap();
let parsed = Configuration::parse_and_validate(empty_settings_file).unwrap();
assert_eq!(parsed, Configuration::default());
}
#[test]
fn test_settings_parse_basic() {
let settings_file = r#"[settings.app]
default_project_name = "untitled"
directory = ""
onboarding_status = "dismissed"
[settings.app.appearance]
theme = "dark"
[settings.modeling]
enable_ssao = false
base_unit = "in"
mouse_controls = "zoo"
camera_projection = "perspective"
[settings.project]
default_project_name = "untitled"
directory = ""
[settings.text_editor]
text_wrapping = true"#;
let expected = Configuration {
settings: Settings {
app: AppSettings {
onboarding_status: OnboardingStatus::Dismissed,
appearance: AppearanceSettings {
theme: AppTheme::Dark,
color: AppColor(264.5),
},
..Default::default()
},
modeling: ModelingSettings {
enable_ssao: false.into(),
base_unit: UnitLength::In,
mouse_controls: MouseControlType::Zoo,
camera_projection: CameraProjectionType::Perspective,
..Default::default()
},
project: ProjectSettings {
default_project_name: ProjectNameTemplate("untitled".to_string()),
directory: "".into(),
},
text_editor: TextEditorSettings {
text_wrapping: true.into(),
..Default::default()
},
command_bar: CommandBarSettings {
include_settings: true.into(),
},
},
};
let parsed = toml::from_str::<Configuration>(settings_file).unwrap();
assert_eq!(parsed, expected,);
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
onboarding_status = "dismissed"
[settings.app.appearance]
theme = "dark"
[settings.modeling]
base_unit = "in"
camera_projection = "perspective"
enable_ssao = false
"#
);
let parsed = Configuration::parse_and_validate(settings_file).unwrap();
assert_eq!(parsed, expected);
}
#[test]
fn test_color_validation() {
let color = AppColor(360.0);
@ -960,7 +679,7 @@ directory = "/Users/macinatormax/Documents/kittycad-modeling-projects"
let settings_file = r#"[settings.app.appearance]
color = 1567.4"#;
let result = Configuration::backwards_compatible_toml_parse(settings_file);
let result = Configuration::parse_and_validate(settings_file);
if let Ok(r) = result {
panic!("Expected an error, but got success: {:?}", r);
}

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use validator::Validate;
use crate::settings::types::{
is_default, AppColor, CommandBarSettings, DefaultTrue, FloatOrInt, OnboardingStatus, TextEditorSettings, UnitLength,
is_default, AppColor, CommandBarSettings, DefaultTrue, OnboardingStatus, TextEditorSettings, UnitLength,
};
/// Project specific settings for the app.
@ -27,27 +27,8 @@ pub struct ProjectConfiguration {
impl ProjectConfiguration {
// TODO: remove this when we remove backwards compatibility with the old settings file.
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
let mut settings = toml::from_str::<Self>(toml_str)?;
if let Some(theme_color) = &settings.settings.app.theme_color {
if settings.settings.app.appearance.color == AppColor::default() {
settings.settings.app.appearance.color = theme_color.clone().into();
settings.settings.app.theme_color = None;
}
}
if let Some(enable_ssao) = settings.settings.app.enable_ssao {
if settings.settings.modeling.enable_ssao.into() {
settings.settings.modeling.enable_ssao = enable_ssao.into();
settings.settings.app.enable_ssao = None;
}
}
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
pub fn parse_and_validate(toml_str: &str) -> Result<Self> {
let settings = toml::from_str::<Self>(toml_str)?;
settings.validate()?;
@ -69,11 +50,11 @@ pub struct PerProjectSettings {
#[validate(nested)]
pub modeling: ProjectModelingSettings,
/// Settings that affect the behavior of the KCL text editor.
#[serde(default, alias = "textEditor")]
#[serde(default)]
#[validate(nested)]
pub text_editor: TextEditorSettings,
/// Settings that affect the behavior of the command bar.
#[serde(default, alias = "commandBar")]
#[serde(default)]
#[validate(nested)]
pub command_bar: CommandBarSettings,
}
@ -90,32 +71,24 @@ pub struct ProjectAppSettings {
#[validate(nested)]
pub appearance: ProjectAppearanceSettings,
/// The onboarding status of the app.
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub onboarding_status: OnboardingStatus,
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "Option::is_none", alias = "themeColor")]
#[ts(skip)]
pub theme_color: Option<FloatOrInt>,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, alias = "enableSSAO", skip_serializing_if = "Option::is_none")]
#[ts(skip)]
pub enable_ssao: Option<bool>,
/// Permanently dismiss the banner warning to download the desktop app.
/// This setting only applies to the web app. And is temporary until we have Linux support.
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub dismiss_web_banner: bool,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub stream_idle_mode: bool,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub allow_orbit_in_sketch_mode: bool,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
/// Settings that affect the behavior of the command bar.
#[serde(default, alias = "namedViews", skip_serializing_if = "IndexMap::is_empty")]
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub named_views: IndexMap<uuid::Uuid, NamedView>,
}
@ -136,17 +109,11 @@ pub struct ProjectAppearanceSettings {
#[ts(export)]
pub struct ProjectModelingSettings {
/// The default unit to use in modeling dimensions.
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub base_unit: UnitLength,
/// Highlight edges of 3D objects?
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")]
#[serde(default, skip_serializing_if = "is_default")]
pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
#[ts(skip)]
pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, skip_serializing_if = "is_default")]
pub enable_ssao: DefaultTrue,
@ -161,31 +128,31 @@ fn named_view_point_version_one() -> f64 {
#[ts(export)]
pub struct NamedView {
/// User defined name to identify the named view. A label.
#[serde(default, alias = "name")]
#[serde(default)]
pub name: String,
/// Engine camera eye off set
#[serde(default, alias = "eyeOffset")]
#[serde(default)]
pub eye_offset: f64,
/// Engine camera vertical FOV
#[serde(default, alias = "fovY")]
#[serde(default)]
pub fov_y: f64,
// Engine camera is orthographic or perspective projection
#[serde(default, alias = "isOrtho")]
#[serde(default)]
pub is_ortho: bool,
/// Engine camera is orthographic camera scaling enabled
#[serde(default, alias = "orthoScaleEnabled")]
#[serde(default)]
pub ortho_scale_enabled: bool,
/// Engine camera orthographic scaling factor
#[serde(default, alias = "orthoScaleFactor")]
#[serde(default)]
pub ortho_scale_factor: f64,
/// Engine camera position that the camera pivots around
#[serde(default, alias = "pivotPosition")]
#[serde(default)]
pub pivot_position: [f64; 3],
/// Engine camera orientation in relation to the pivot position
#[serde(default, alias = "pivotRotation")]
#[serde(default)]
pub pivot_rotation: [f64; 4],
/// Engine camera world coordinate system orientation
#[serde(default, alias = "worldCoordSystem")]
#[serde(default)]
pub world_coord_system: String,
/// Version number of the view point if the engine camera API changes
#[serde(default = "named_view_point_version_one")]
@ -204,80 +171,6 @@ mod tests {
};
use crate::settings::types::UnitLength;
#[test]
// Test that we can deserialize a project file from the old format.
// TODO: We can remove this functionality after a few versions.
fn test_backwards_compatible_project_settings_file() {
let old_project_file = r#"[settings.app]
themeColor = "138"
[settings.textEditor]
textWrapping = false
blinkingCursor = false
[settings.modeling]
showDebugPanel = true
[settings.commandBar]
includeSettings = false
#"#;
//let parsed = toml::from_str::<ProjectConfiguration(old_project_file).unwrap();
let parsed = ProjectConfiguration::backwards_compatible_toml_parse(old_project_file).unwrap();
assert_eq!(
parsed,
ProjectConfiguration {
settings: PerProjectSettings {
app: ProjectAppSettings {
appearance: ProjectAppearanceSettings { color: 138.0.into() },
onboarding_status: Default::default(),
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
stream_idle_mode: false,
allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
named_views: IndexMap::default()
},
modeling: ProjectModelingSettings {
base_unit: UnitLength::Mm,
highlight_edges: Default::default(),
show_debug_panel: Default::default(),
enable_ssao: true.into(),
},
text_editor: TextEditorSettings {
text_wrapping: false.into(),
blinking_cursor: false.into()
},
command_bar: CommandBarSettings {
include_settings: false.into()
},
}
}
);
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
show_debug_panel = true
[settings.app.appearance]
color = 138.0
[settings.modeling]
[settings.text_editor]
text_wrapping = false
blinking_cursor = false
[settings.command_bar]
include_settings = false
"#
);
}
#[test]
fn test_project_settings_empty_file_parses() {
let empty_settings_file = r#""#;
@ -299,7 +192,7 @@ include_settings = false
"#
);
let parsed = ProjectConfiguration::backwards_compatible_toml_parse(empty_settings_file).unwrap();
let parsed = ProjectConfiguration::parse_and_validate(empty_settings_file).unwrap();
assert_eq!(parsed, ProjectConfiguration::default());
}
@ -308,7 +201,7 @@ include_settings = false
let settings_file = r#"[settings.app.appearance]
color = 1567.4"#;
let result = ProjectConfiguration::backwards_compatible_toml_parse(settings_file);
let result = ProjectConfiguration::parse_and_validate(settings_file);
if let Ok(r) = result {
panic!("Expected an error, but got success: {:?}", r);
}
@ -376,9 +269,7 @@ color = 1567.4"#;
app: ProjectAppSettings {
appearance: ProjectAppearanceSettings { color: 138.0.into() },
onboarding_status: Default::default(),
theme_color: None,
dismiss_web_banner: false,
enable_ssao: None,
stream_idle_mode: false,
allow_orbit_in_sketch_mode: false,
show_debug_panel: true,
@ -418,7 +309,6 @@ color = 1567.4"#;
modeling: ProjectModelingSettings {
base_unit: UnitLength::Yd,
highlight_edges: Default::default(),
show_debug_panel: Default::default(),
enable_ssao: true.into(),
},
text_editor: TextEditorSettings {

View File

@ -6,7 +6,6 @@
export import * from "std::types"
export import "std::units"
export import * from "std::math"
export import "std::math"
export import * from "std::sketch"
export import * from "std::solid"
export import "std::turns"

View File

@ -136,7 +136,7 @@ pub fn default_app_settings() -> Result<JsValue, String> {
pub fn parse_app_settings(toml_str: &str) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let settings = kcl_lib::Configuration::backwards_compatible_toml_parse(toml_str).map_err(|e| e.to_string())?;
let settings = kcl_lib::Configuration::parse_and_validate(toml_str).map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead.
@ -160,8 +160,7 @@ pub fn default_project_settings() -> Result<JsValue, String> {
pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let settings =
kcl_lib::ProjectConfiguration::backwards_compatible_toml_parse(toml_str).map_err(|e| e.to_string())?;
let settings = kcl_lib::ProjectConfiguration::parse_and_validate(toml_str).map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead.
@ -176,8 +175,6 @@ pub fn serialize_configuration(val: JsValue) -> Result<JsValue, String> {
let config: kcl_lib::Configuration = val.into_serde().map_err(|e| e.to_string())?;
let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
let settings = kcl_lib::Configuration::backwards_compatible_toml_parse(&toml_str).map_err(|e| e.to_string())?;
let toml_str = toml::to_string_pretty(&settings).map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead.
@ -192,9 +189,6 @@ pub fn serialize_project_configuration(val: JsValue) -> Result<JsValue, String>
let config: kcl_lib::ProjectConfiguration = val.into_serde().map_err(|e| e.to_string())?;
let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
let settings =
kcl_lib::ProjectConfiguration::backwards_compatible_toml_parse(&toml_str).map_err(|e| e.to_string())?;
let toml_str = toml::to_string_pretty(&settings).map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead.

View File

@ -230,7 +230,7 @@ const FileTreeItem = ({
codeManager.updateCodeStateEditor(code)
} else if (isImportedInCurrentFile && eventType === 'change') {
await rustContext.clearSceneAndBustCache(
{ settings: await jsAppSettings() },
await jsAppSettings(),
codeManager?.currentFilePath || undefined
)
await kclManager.executeAst()

View File

@ -324,7 +324,7 @@ export class KclManager {
// the cache and clear the scene.
if (this._astParseFailed && this._switchedFiles) {
await this.singletons.rustContext.clearSceneAndBustCache(
{ settings: await jsAppSettings() },
await jsAppSettings(),
this.singletons.codeManager.currentFilePath || undefined
)
} else if (this._switchedFiles) {

View File

@ -65,7 +65,7 @@ export async function executeAst({
path?: string
}): Promise<ExecutionResult> {
try {
const settings = { settings: await jsAppSettings() }
const settings = await jsAppSettings()
const execState = await rustContext.execute(ast, settings, path)
await rustContext.waitForAllEngineCommands()
@ -92,7 +92,7 @@ export async function executeAstMock({
usePrevMemory?: boolean
}): Promise<ExecutionResult> {
try {
const settings = { settings: await jsAppSettings() }
const settings = await jsAppSettings()
const execState = await rustContext.executeMock(
ast,
settings,

View File

@ -1434,7 +1434,7 @@ export class EngineCommandManager extends EventTarget {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.onEngineConnectionOpened = async () => {
await this.rustContext?.clearSceneAndBustCache(
{ settings: await jsAppSettings() },
await jsAppSettings(),
this.codeManager?.currentFilePath || undefined
)

View File

@ -660,7 +660,9 @@ export function getKclVersion(): string {
/**
* Serialize a project configuration to a TOML string.
*/
export function serializeConfiguration(configuration: any): string | Error {
export function serializeConfiguration(
configuration: DeepPartial<Configuration>
): string | Error {
try {
return serialize_configuration(configuration)
} catch (e: any) {
@ -672,7 +674,7 @@ export function serializeConfiguration(configuration: any): string | Error {
* Serialize a project configuration to a TOML string.
*/
export function serializeProjectConfiguration(
configuration: any
configuration: DeepPartial<ProjectConfiguration>
): string | Error {
try {
return serialize_project_configuration(configuration)

View File

@ -58,6 +58,29 @@ export function mouseControlsToCameraSystem(
}
}
export function cameraSystemToMouseControl(
cameraSystem: CameraSystem
): MouseControlType | undefined {
switch (cameraSystem) {
case 'Zoo':
return 'zoo'
case 'OnShape':
return 'onshape'
case 'Trackpad Friendly':
return 'trackpad_friendly'
case 'Solidworks':
return 'solidworks'
case 'NX':
return 'nx'
case 'Creo':
return 'creo'
case 'AutoCAD':
return 'autocad'
default:
return undefined
}
}
interface MouseGuardHandler {
description: string
callback: (e: MouseEvent) => boolean

View File

@ -13,7 +13,10 @@ import {
serializeProjectConfiguration,
} from '@src/lang/wasm'
import { initPromise } from '@src/lang/wasmUtils'
import { mouseControlsToCameraSystem } from '@src/lib/cameraControls'
import {
cameraSystemToMouseControl,
mouseControlsToCameraSystem,
} from '@src/lib/cameraControls'
import { BROWSER_PROJECT_NAME } from '@src/lib/constants'
import {
getInitialDefaultDir,
@ -86,6 +89,50 @@ export function configurationToSettingsPayload(
}
}
export function settingsPayloadToConfiguration(
configuration: DeepPartial<SaveSettingsPayload>
): DeepPartial<Configuration> {
return {
settings: {
app: {
appearance: {
theme: configuration?.app?.theme,
color: configuration?.app?.themeColor
? parseFloat(configuration.app.themeColor)
: undefined,
},
onboarding_status: configuration?.app?.onboardingStatus,
dismiss_web_banner: configuration?.app?.dismissWebBanner,
stream_idle_mode: configuration?.app?.streamIdleMode,
allow_orbit_in_sketch_mode: configuration?.app?.allowOrbitInSketchMode,
show_debug_panel: configuration?.app?.showDebugPanel,
},
modeling: {
base_unit: configuration?.modeling?.defaultUnit,
camera_projection: configuration?.modeling?.cameraProjection,
camera_orbit: configuration?.modeling?.cameraOrbit,
mouse_controls: configuration?.modeling?.mouseControls
? cameraSystemToMouseControl(configuration?.modeling?.mouseControls)
: undefined,
highlight_edges: configuration?.modeling?.highlightEdges,
enable_ssao: configuration?.modeling?.enableSSAO,
show_scale_grid: configuration?.modeling?.showScaleGrid,
},
text_editor: {
text_wrapping: configuration?.textEditor?.textWrapping,
blinking_cursor: configuration?.textEditor?.blinkingCursor,
},
project: {
directory: configuration?.app?.projectDirectory,
default_project_name: configuration?.projects?.defaultProjectName,
},
command_bar: {
include_settings: configuration?.commandBar?.includeSettings,
},
},
}
}
export function isNamedView(
namedView: DeepPartial<NamedView> | undefined
): namedView is NamedView {
@ -156,6 +203,41 @@ export function projectConfigurationToSettingsPayload(
}
}
export function settingsPayloadToProjectConfiguration(
configuration: DeepPartial<SaveSettingsPayload>
): DeepPartial<ProjectConfiguration> {
return {
settings: {
app: {
appearance: {
color: configuration?.app?.themeColor
? parseFloat(configuration.app.themeColor)
: undefined,
},
onboarding_status: configuration?.app?.onboardingStatus,
dismiss_web_banner: configuration?.app?.dismissWebBanner,
allow_orbit_in_sketch_mode: configuration?.app?.allowOrbitInSketchMode,
show_debug_panel: configuration?.app?.showDebugPanel,
named_views: deepPartialNamedViewsToNamedViews(
configuration?.app?.namedViews
),
},
modeling: {
base_unit: configuration?.modeling?.defaultUnit,
highlight_edges: configuration?.modeling?.highlightEdges,
enable_ssao: configuration?.modeling?.enableSSAO,
},
text_editor: {
text_wrapping: configuration?.textEditor?.textWrapping,
blinking_cursor: configuration?.textEditor?.blinkingCursor,
},
command_bar: {
include_settings: configuration?.commandBar?.includeSettings,
},
},
}
}
function localStorageAppSettingsPath() {
return '/settings.toml'
}
@ -204,6 +286,7 @@ export function readLocalStorageProjectSettingsFile():
const projectSettings = parseProjectSettings(stored)
if (err(projectSettings)) {
const settings = defaultProjectSettings()
if (err(settings)) return settings
const tomlStr = serializeProjectConfiguration(settings)
if (err(tomlStr)) return tomlStr
@ -281,7 +364,9 @@ export async function saveSettings(
// Get the user settings.
const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
const appTomlString = serializeConfiguration({ settings: jsAppSettings })
const appTomlString = serializeConfiguration(
settingsPayloadToConfiguration(jsAppSettings)
)
if (err(appTomlString)) return
// Write the app settings.
@ -298,9 +383,9 @@ export async function saveSettings(
// Get the project settings.
const jsProjectSettings = getChangedSettingsAtLevel(allSettings, 'project')
const projectTomlString = serializeProjectConfiguration({
settings: jsProjectSettings,
})
const projectTomlString = serializeProjectConfiguration(
settingsPayloadToProjectConfiguration(jsProjectSettings)
)
if (err(projectTomlString)) return
// Write the project settings.
@ -453,7 +538,7 @@ export function getSettingInputType(setting: Setting) {
return typeof setting.default as 'string' | 'boolean'
}
export const jsAppSettings = async () => {
export const jsAppSettings = async (): Promise<DeepPartial<Configuration>> => {
let jsAppSettings = default_app_settings()
if (!TEST) {
// TODO: https://github.com/KittyCAD/modeling-app/issues/6445
@ -464,5 +549,5 @@ export const jsAppSettings = async () => {
jsAppSettings = getAllCurrentSettings(settings)
}
}
return jsAppSettings
return settingsPayloadToConfiguration(jsAppSettings)
}

View File

@ -9,6 +9,6 @@ export async function enginelessExecutor(
usePrevMemory?: boolean,
path?: string
): Promise<ExecState> {
const settings = { settings: await jsAppSettings() }
const settings = await jsAppSettings()
return await rustContext.executeMock(ast, settings, path, usePrevMemory)
}

View File

@ -307,6 +307,8 @@ export const settingsMachine = setup({
resetSettings: assign(({ context, event }) => {
if (!('level' in event)) return {}
console.log('Resetting settings at level', event.level)
// Create a new, blank payload
const newPayload =
event.level === 'user'