Skip to main content

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>(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    // let mut lspstore = LSPStore::<true>::new(&mut state);
104    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}