Change to use nodePath instead of sourceRange for Operations (#7320)
* Add NodePath to operations * Change to use nodePath to get pathToNode instead of sourceRange * Add additional node path unit test * Update output * Fix import statement NodePaths * Update output * Factor into function
This commit is contained in:
@ -676,7 +676,7 @@ impl EdgeCut {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtifactGraph {
|
||||
map: IndexMap<ArtifactId, Artifact>,
|
||||
item_count: usize,
|
||||
pub(super) item_count: usize,
|
||||
}
|
||||
|
||||
impl ArtifactGraph {
|
||||
|
@ -1,13 +1,12 @@
|
||||
use indexmap::IndexMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{types::NumericType, ArtifactId, KclValue};
|
||||
use crate::{ModuleId, SourceRange};
|
||||
use crate::{ModuleId, NodePath, SourceRange};
|
||||
|
||||
/// A CAD modeling operation for display in the feature tree, AKA operations
|
||||
/// timeline.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Operation {
|
||||
@ -18,6 +17,8 @@ pub enum Operation {
|
||||
unlabeled_arg: Option<OpArg>,
|
||||
/// The labeled keyword arguments to the function.
|
||||
labeled_args: IndexMap<String, OpArg>,
|
||||
/// The node path of the operation in the source code.
|
||||
node_path: NodePath,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
/// True if the operation resulted in an error.
|
||||
@ -28,6 +29,8 @@ pub enum Operation {
|
||||
GroupBegin {
|
||||
/// The details of the group.
|
||||
group: Group,
|
||||
/// The node path of the operation in the source code.
|
||||
node_path: NodePath,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
},
|
||||
@ -64,7 +67,7 @@ impl Operation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
@ -95,7 +98,7 @@ pub enum Group {
|
||||
}
|
||||
|
||||
/// An argument to a CAD modeling operation.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpArg {
|
||||
@ -119,7 +122,7 @@ fn is_false(b: &bool) -> bool {
|
||||
|
||||
/// A KCL value used in Operations. `ArtifactId`s are used to refer to the
|
||||
/// actual scene objects. Any data not needed in the UI may be omitted.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum OpKclValue {
|
||||
@ -177,21 +180,21 @@ pub enum OpKclValue {
|
||||
|
||||
pub type OpKclObjectFields = IndexMap<String, OpKclValue>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpSketch {
|
||||
artifact_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpSolid {
|
||||
artifact_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpHelix {
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
||||
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
|
||||
source_range::SourceRange,
|
||||
std::StdFn,
|
||||
CompilationError,
|
||||
CompilationError, NodePath,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -322,6 +322,7 @@ impl FunctionDefinition<'_> {
|
||||
.unlabeled_kw_arg_unconverted()
|
||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
||||
labeled_args: op_labeled_args,
|
||||
node_path: NodePath::placeholder(),
|
||||
source_range: callsite,
|
||||
is_error: false,
|
||||
})
|
||||
@ -337,6 +338,7 @@ impl FunctionDefinition<'_> {
|
||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
|
||||
labeled_args: op_labeled_args,
|
||||
},
|
||||
node_path: NodePath::placeholder(),
|
||||
source_range: callsite,
|
||||
});
|
||||
|
||||
|
@ -7,6 +7,7 @@ use anyhow::Result;
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
||||
use cache::GlobalState;
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use cad_op::{Group, Operation};
|
||||
pub use geometry::*;
|
||||
pub use id_generator::IdGenerator;
|
||||
@ -842,31 +843,14 @@ impl ExecutorContext {
|
||||
let module_path = module_path.clone();
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
|
||||
match &module_path {
|
||||
ModulePath::Main => {
|
||||
// This should never happen.
|
||||
}
|
||||
ModulePath::Local { value, .. } => {
|
||||
// We only want to display the top-level module imports in
|
||||
// the Feature Tree, not transitive imports.
|
||||
if universe_map.contains_key(value) {
|
||||
exec_state.push_op(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
},
|
||||
source_range,
|
||||
});
|
||||
// Due to concurrent execution, we cannot easily
|
||||
// group operations by module. So we leave the
|
||||
// group empty and close it immediately.
|
||||
exec_state.push_op(Operation::GroupEnd);
|
||||
}
|
||||
}
|
||||
ModulePath::Std { .. } => {
|
||||
// We don't want to display stdlib in the Feature Tree.
|
||||
}
|
||||
}
|
||||
self.add_import_module_ops(
|
||||
exec_state,
|
||||
program,
|
||||
module_id,
|
||||
&module_path,
|
||||
source_range,
|
||||
&universe_map,
|
||||
);
|
||||
|
||||
let repr = repr.clone();
|
||||
let exec_state = exec_state.clone();
|
||||
@ -1009,6 +993,67 @@ impl ExecutorContext {
|
||||
Ok((universe, root_imports))
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
program: &crate::Program,
|
||||
module_id: ModuleId,
|
||||
module_path: &ModulePath,
|
||||
source_range: SourceRange,
|
||||
universe_map: &UniverseMap,
|
||||
) {
|
||||
match module_path {
|
||||
ModulePath::Main => {
|
||||
// This should never happen.
|
||||
}
|
||||
ModulePath::Local { value, .. } => {
|
||||
// We only want to display the top-level module imports in
|
||||
// the Feature Tree, not transitive imports.
|
||||
if universe_map.contains_key(value) {
|
||||
use crate::NodePath;
|
||||
|
||||
let node_path = if source_range.is_top_level_module() {
|
||||
let cached_body_items = exec_state.global.artifacts.cached_body_items();
|
||||
NodePath::from_range(&program.ast, cached_body_items, source_range).unwrap_or_default()
|
||||
} else {
|
||||
// The frontend doesn't care about paths in
|
||||
// files other than the top-level module.
|
||||
NodePath::placeholder()
|
||||
};
|
||||
|
||||
exec_state.push_op(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
},
|
||||
node_path,
|
||||
source_range,
|
||||
});
|
||||
// Due to concurrent execution, we cannot easily
|
||||
// group operations by module. So we leave the
|
||||
// group empty and close it immediately.
|
||||
exec_state.push_op(Operation::GroupEnd);
|
||||
}
|
||||
}
|
||||
ModulePath::Std { .. } => {
|
||||
// We don't want to display stdlib in the Feature Tree.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
_exec_state: &mut ExecState,
|
||||
_program: &crate::Program,
|
||||
_module_id: ModuleId,
|
||||
_module_path: &ModulePath,
|
||||
_source_range: SourceRange,
|
||||
_universe_map: &UniverseMap,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Perform the execution of a program. Accept all possible parameters and
|
||||
/// output everything.
|
||||
async fn inner_run(
|
||||
@ -1059,6 +1104,11 @@ impl ExecutorContext {
|
||||
// Don't early return! We need to build other outputs regardless of
|
||||
// whether execution failed.
|
||||
|
||||
// Because of execution caching, we may start with operations from a
|
||||
// previous run.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
let start_op = exec_state.global.artifacts.operations.len();
|
||||
|
||||
self.eval_prelude(exec_state, SourceRange::from(program).start_as_range())
|
||||
.await?;
|
||||
|
||||
@ -1072,6 +1122,29 @@ impl ExecutorContext {
|
||||
)
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
// Fill in NodePath for operations.
|
||||
let cached_body_items = exec_state.global.artifacts.cached_body_items();
|
||||
for op in exec_state.global.artifacts.operations.iter_mut().skip(start_op) {
|
||||
match op {
|
||||
Operation::StdLibCall {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
}
|
||||
| Operation::GroupBegin {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
} => {
|
||||
node_path.fill_placeholder(program, cached_body_items, *source_range);
|
||||
}
|
||||
Operation::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all the async commands completed.
|
||||
self.engine.ensure_async_commands_completed().await?;
|
||||
|
||||
|
@ -381,6 +381,13 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
impl ArtifactState {
|
||||
pub fn cached_body_items(&self) -> usize {
|
||||
self.graph.item_count
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
pub(super) fn new(path: ModulePath, memory: Arc<ProgramMemory>, module_id: Option<ModuleId>) -> Self {
|
||||
ModuleState {
|
||||
|
@ -60,6 +60,20 @@ pub enum Step {
|
||||
}
|
||||
|
||||
impl NodePath {
|
||||
/// Placeholder for when the AST isn't available to create a real path. It
|
||||
/// will be filled in later.
|
||||
pub(crate) fn placeholder() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn fill_placeholder(&mut self, program: &Node<Program>, cached_body_items: usize, range: SourceRange) {
|
||||
if !self.is_empty() {
|
||||
return;
|
||||
}
|
||||
*self = Self::from_range(program, cached_body_items, range).unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Given a program and a [`SourceRange`], return the path to the node that
|
||||
/// contains the range.
|
||||
pub(crate) fn from_range(program: &Node<Program>, cached_body_items: usize, range: SourceRange) -> Option<Self> {
|
||||
@ -390,4 +404,19 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_path_from_range_import() {
|
||||
let code = r#"import "cube.step" as cube
|
||||
import "cylinder.kcl" as cylinder
|
||||
"#;
|
||||
let program = crate::Program::parse_no_errs(code).unwrap();
|
||||
// The entire cylinder import statement.
|
||||
assert_eq!(
|
||||
NodePath::from_range(&program.ast, 0, range(27, 60)).unwrap(),
|
||||
NodePath {
|
||||
steps: vec![Step::ProgramBodyItem { index: 1 }],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ impl SourceRange {
|
||||
ModuleId::from_usize(self.0[2])
|
||||
}
|
||||
|
||||
/// True if this source range is from the top-level module.
|
||||
pub fn is_top_level_module(&self) -> bool {
|
||||
self.module_id().is_top_level()
|
||||
}
|
||||
|
||||
/// Check if the range contains a position.
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
pos >= self.start() && pos <= self.end()
|
||||
|
Reference in New Issue
Block a user