1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use flams_lsp::state::{DocData, UrlOrFile};
4
5use flams_lsp::documents::LSPDocument;
6use flams_lsp::state::DocData::{Data, Doc};
7use flams_lsp::state::UrlOrFile::File;
8use flams_math_archives::backend::GlobalBackend;
9use flams_math_archives::source_files::SourceEntry;
10use flams_math_archives::{Archive, MathArchive};
11use ftml_ontology::utils::RefTree;
12use ftml_uris::DocumentUri;
13use std::ffi::{CStr, CString};
14use std::path::Path;
15use std::sync::{Arc, LazyLock, Mutex};
16
17use flams_lsp::LSPStore;
18use flams_utils::prelude::HMap;
19use serde::Serialize;
20
21extern crate tokio;
22
23#[unsafe(no_mangle)]
24pub extern "C" fn hello_world(arg: usize) {
25 println!("Hi from Rust! arg: {}", arg);
27}
28
29#[no_mangle]
30pub static FFI_VERSION: usize = 1;
31
32pub fn to_json<T: Serialize>(data: &T) -> *const libc::c_char {
33 CString::new(serde_json::to_string(data).unwrap())
34 .unwrap()
35 .into_raw()
36}
37
38#[unsafe(no_mangle)]
39pub unsafe extern "C" fn free_string(s: *mut libc::c_char) {
40 unsafe {
41 if s.is_null() {
42 return;
43 }
44 drop(CString::from_raw(s));
45 }
46}
47
48static GLOBAL_STATE: LazyLock<Mutex<HMap<UrlOrFile, DocData>>> =
49 LazyLock::new(|| Mutex::new(HMap::default()));
50
51#[unsafe(no_mangle)]
52pub extern "C" fn initialize() {
53 tracing_subscriber::fmt().init();
54 let _ce = color_eyre::install();
55 let spec = flams_system::settings::SettingsSpec::default();
56 tokio::runtime::Builder::new_multi_thread()
58 .enable_all()
59 .build()
60 .expect("Failed to initialize Tokio runtime")
61 .block_on(async {
62 flams_system::settings::Settings::initialize(spec);
63 GlobalBackend::initialize::<flams_system::TokioEngine>(false);
64 });
65}
66
67fn _get_all_files() -> Vec<(Arc<Path>, DocumentUri)> {
68 let mut files: Vec<(Arc<Path>, DocumentUri)> = Vec::new();
69 for a in GlobalBackend.all_archives().iter() {
70 if let Archive::Local(a) = a {
71 a.with_sources(|d| {
72 for e in d.dfs() {
73 if let SourceEntry::File(f) = e {
74 let Ok(uri) = DocumentUri::from_archive_relpath(
75 a.uri().clone(),
76 f.relative_path.as_ref(),
77 ) else {
78 continue;
79 };
80 files.push((
81 f.relative_path
82 .steps()
83 .fold(a.source_dir(), |p, s| p.join(s))
84 .into(),
85 uri,
86 ));
87 }
88 }
89 });
90 }
91 }
92 files
93}
94
95#[unsafe(no_mangle)]
96pub extern "C" fn load_all_files() {
97 let files = _get_all_files();
98 let len = files.len();
99 tracing::info!("Linting {len} files");
100
101 let mut state = GLOBAL_STATE.lock().unwrap();
102 state.clear();
103 for (p, uri) in files {
105 if let Some(ret) =
106 LSPStore::<true>::new(&mut state, None, &[], false).load(p.as_ref(), &uri)
107 {
108 state.insert(File(p.clone()), Data(ret, true));
109 }
110 }
111 tracing::info!("Finished linting {len} files");
112}
113
114#[unsafe(no_mangle)]
115pub unsafe extern "C" fn list_of_all_files() -> *const libc::c_char {
116 to_json(
117 &_get_all_files()
118 .into_iter()
119 .map(|(p, _)| p.as_ref().to_str().unwrap().to_string())
120 .collect::<Vec<_>>(),
121 )
122}
123
124#[unsafe(no_mangle)]
125pub unsafe extern "C" fn list_of_loaded_files() -> *const libc::c_char {
126 let state = GLOBAL_STATE.lock().unwrap();
127 let paths: Vec<&str> = state
128 .keys()
129 .map(|k| match k {
130 File(p) => p.as_ref().to_str().unwrap(),
131 x => {
132 tracing::warn!("Unexpected key: {:?}", x);
133 ""
134 }
135 })
136 .filter(|s| !s.is_empty())
137 .collect();
138 to_json(&paths)
139}
140
141#[unsafe(no_mangle)]
142pub unsafe extern "C" fn get_file_annotations(path: *const libc::c_char) -> *const libc::c_char {
143 let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
144 let state = GLOBAL_STATE.lock().unwrap();
145 let doc = state.get(&File(Path::new(path_str).into()));
146 match doc {
147 Some(Data(data, _)) => to_json(&data.lock().annotations),
148 Some(Doc(_lspdoc)) => CString::new("").unwrap().into_raw(),
149 None => CString::new("").unwrap().into_raw(),
150 }
151}
152
153#[unsafe(no_mangle)]
154pub unsafe extern "C" fn unload_file(path: *const libc::c_char) {
155 let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
156 let mut state = GLOBAL_STATE.lock().unwrap();
157 state.remove(&File(Path::new(path_str).into()));
158}
159
160#[unsafe(no_mangle)]
161pub unsafe extern "C" fn load_file(path: *const libc::c_char) {
162 let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
163 let mut state = GLOBAL_STATE.lock().unwrap();
164
165 let lspdoc = LSPDocument::new("".to_string(), File(Path::new(path_str).into()));
166 let p = Path::new(path_str);
167 let uri: &DocumentUri = lspdoc.document_uri().unwrap();
168 if let Some(ret) = LSPStore::<true>::new(&mut state, None, &[], false).load(p.as_ref(), &uri) {
169 state.insert(File(p.into()), Data(ret, true));
170 }
171}
172
173#[unsafe(no_mangle)]
174pub extern "C" fn unload_all_files() {
175 let mut state = GLOBAL_STATE.lock().unwrap();
176 state.clear();
177}
178
179#[unsafe(no_mangle)]
180pub extern "C" fn reset_global_backend() {
181 tokio::runtime::Builder::new_multi_thread()
182 .enable_all()
183 .build()
184 .expect("Failed to initialize Tokio runtime")
185 .block_on(async {
186 GlobalBackend.reset::<flams_system::TokioEngine>(false);
187 let _ = tokio::task::spawn_blocking(|| {
188 for e in flams_system::iter::<flams_math_archives::FlamsExtension>() {
189 (e.on_reload)();
190 }
191 });
192 });
193}