kcl_lib/project/
mod.rs

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