flams_ffi/
lib.rs

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    // use this as a test to see if the FFI works
26    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    // spec.lsp = true;
57    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>();
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    // let mut lspstore = LSPStore::<true>::new(&mut state);
104    for (p, uri) in files {
105        if let Some(ret) = LSPStore::<true>::new(&mut state).load(p.as_ref(), &uri) {
106            state.insert(File(p.clone()), Data(ret, true));
107        }
108    }
109    tracing::info!("Finished linting {len} files");
110}
111
112#[unsafe(no_mangle)]
113pub unsafe extern "C" fn list_of_all_files() -> *const libc::c_char {
114    to_json(
115        &_get_all_files()
116            .into_iter()
117            .map(|(p, _)| p.as_ref().to_str().unwrap().to_string())
118            .collect::<Vec<_>>(),
119    )
120}
121
122#[unsafe(no_mangle)]
123pub unsafe extern "C" fn list_of_loaded_files() -> *const libc::c_char {
124    let state = GLOBAL_STATE.lock().unwrap();
125    let paths: Vec<&str> = state
126        .keys()
127        .map(|k| match k {
128            File(p) => p.as_ref().to_str().unwrap(),
129            x => {
130                tracing::warn!("Unexpected key: {:?}", x);
131                ""
132            }
133        })
134        .filter(|s| !s.is_empty())
135        .collect();
136    to_json(&paths)
137}
138
139#[unsafe(no_mangle)]
140pub unsafe extern "C" fn get_file_annotations(path: *const libc::c_char) -> *const libc::c_char {
141    let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
142    let state = GLOBAL_STATE.lock().unwrap();
143    let doc = state.get(&File(Path::new(path_str).into()));
144    match doc {
145        Some(Data(data, _)) => to_json(&data.lock().annotations),
146        Some(Doc(_lspdoc)) => CString::new("").unwrap().into_raw(),
147        None => CString::new("").unwrap().into_raw(),
148    }
149}
150
151#[unsafe(no_mangle)]
152pub unsafe extern "C" fn unload_file(path: *const libc::c_char) {
153    let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
154    let mut state = GLOBAL_STATE.lock().unwrap();
155    state.remove(&File(Path::new(path_str).into()));
156}
157
158#[unsafe(no_mangle)]
159pub unsafe extern "C" fn load_file(path: *const libc::c_char) {
160    let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
161    let mut state = GLOBAL_STATE.lock().unwrap();
162
163    let lspdoc = LSPDocument::new("".to_string(), File(Path::new(path_str).into()));
164    let p = Path::new(path_str);
165    let uri: &DocumentUri = lspdoc.document_uri().unwrap();
166    if let Some(ret) = LSPStore::<true>::new(&mut state).load(p.as_ref(), &uri) {
167        state.insert(File(p.into()), Data(ret, true));
168    }
169}
170
171#[unsafe(no_mangle)]
172pub extern "C" fn unload_all_files() {
173    let mut state = GLOBAL_STATE.lock().unwrap();
174    state.clear();
175}
176
177#[unsafe(no_mangle)]
178pub extern "C" fn reset_global_backend() {
179    tokio::runtime::Builder::new_multi_thread()
180        .enable_all()
181        .build()
182        .expect("Failed to initialize Tokio runtime")
183        .block_on(async {
184            GlobalBackend.reset::<flams_system::TokioEngine>();
185        });
186}