kcl_lib/project/
mod.rs

1#![allow(dead_code)]
2
3use std::sync::Arc;
4
5use indexmap::IndexMap;
6use kcl_api::{Error, FileId, LifecycleApi, ProjectId, Result, SceneGraph, Version};
7use tokio::sync::RwLock;
8
9#[derive(Debug, Clone)]
10pub struct Project {
11    id: ProjectId,
12    files: IndexMap<FileId, File>,
13    open_file: FileId,
14    cur_version: Version,
15}
16
17impl Project {
18    fn scene_graph(&self) -> SceneGraph {
19        SceneGraph::empty(self.id, self.open_file, self.cur_version)
20    }
21}
22
23#[derive(Debug, Clone)]
24struct File {
25    version: Version,
26    path: String,
27    text: String,
28}
29
30lazy_static::lazy_static! {
31        static ref PROJECT: Arc<RwLock<Option<Project>>> = Default::default();
32}
33
34#[derive(Debug, Clone)]
35pub struct ProjectManager;
36
37impl ProjectManager {
38    async fn with_project<T>(f: impl FnOnce(&Option<Project>) -> T) -> T {
39        f(&*PROJECT.read().await)
40    }
41
42    async fn with_project_mut<T>(f: impl FnOnce(&mut Option<Project>) -> T) -> T {
43        f(&mut *PROJECT.write().await)
44    }
45}
46
47impl LifecycleApi for ProjectManager {
48    async fn open_project(&self, id: ProjectId, files: Vec<kcl_api::File>, open_file: FileId) -> Result<()> {
49        Self::with_project_mut(move |project| {
50            *project = Some(Project {
51                id,
52                files: files
53                    .into_iter()
54                    .map(|f| {
55                        (
56                            f.id,
57                            File {
58                                version: Version(0),
59                                path: f.path,
60                                text: f.text,
61                            },
62                        )
63                    })
64                    .collect(),
65                open_file,
66                cur_version: Version(0),
67            });
68            Ok(())
69        })
70        .await
71    }
72
73    async fn add_file(&self, project_id: ProjectId, file: kcl_api::File) -> Result<()> {
74        Self::with_project_mut(move |project| {
75            let Some(project) = project else {
76                return Err(Error::bad_project(project_id, None));
77            };
78            if project.id != project_id {
79                return Err(Error::bad_project(project_id, Some(project.id)));
80            }
81            if project.files.contains_key(&file.id) {
82                return Err(Error::file_id_in_use(file.id, &project.files[&file.id].path));
83            }
84            project.files.insert(
85                file.id,
86                File {
87                    version: Version(0),
88                    path: file.path,
89                    text: file.text,
90                },
91            );
92            Ok(())
93        })
94        .await
95    }
96
97    async fn remove_file(&self, project_id: ProjectId, file_id: FileId) -> Result<()> {
98        Self::with_project_mut(move |project| {
99            let Some(project) = project else {
100                return Err(Error::bad_project(project_id, None));
101            };
102            if project.id != project_id {
103                return Err(Error::bad_project(project_id, Some(project.id)));
104            }
105            let old = project.files.swap_remove(&file_id);
106            if old.is_none() {
107                return Err(Error::bad_file(file_id, None));
108            }
109            Ok(())
110        })
111        .await
112    }
113
114    async fn update_file(&self, project_id: ProjectId, file_id: FileId, text: String) -> Result<()> {
115        Self::with_project_mut(move |project| {
116            let Some(project) = project else {
117                return Err(Error::bad_project(project_id, None));
118            };
119            if project.id != project_id {
120                return Err(Error::bad_project(project_id, Some(project.id)));
121            }
122            let Some(file) = project.files.get_mut(&file_id) else {
123                return Err(Error::bad_file(file_id, None));
124            };
125            file.text = text;
126            Ok(())
127        })
128        .await
129    }
130
131    async fn switch_file(&self, project_id: ProjectId, file_id: FileId) -> Result<()> {
132        Self::with_project_mut(move |project| {
133            let Some(project) = project else {
134                return Err(Error::bad_project(project_id, None));
135            };
136            if project.id != project_id {
137                return Err(Error::bad_project(project_id, Some(project.id)));
138            }
139            let Some(file) = project.files.get(&file_id) else {
140                return Err(Error::bad_file(file_id, None));
141            };
142            project.open_file = file_id;
143            project.cur_version = file.version;
144            Ok(())
145        })
146        .await
147    }
148
149    async fn refresh(&self, project_id: ProjectId) -> Result<()> {
150        Self::with_project_mut(move |project| {
151            let Some(project) = project else {
152                return Err(Error::bad_project(project_id, None));
153            };
154            if project.id != project_id {
155                return Err(Error::bad_project(project_id, Some(project.id)));
156            }
157            Ok(())
158        })
159        .await
160    }
161}
162
163// Dummy struct to ensure we export the types from the API crate :-(
164#[derive(ts_rs::TS, serde::Serialize, serde::Deserialize)]
165#[ts(export)]
166pub struct IgnoreMe {
167    pub a: kcl_api::Error,
168    pub b: kcl_api::SceneGraphDelta,
169    pub c: kcl_api::File,
170}