flams_ffi/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3use flams_lsp::state::{DocData, UrlOrFile};
4use flams_ontology::uris::DocumentURI;
5use flams_ontology::uris::URIRefTrait;
6use flams_system::backend::archives::source_files::{SourceDir, SourceEntry};
7use flams_system::backend::archives::Archive;
8use flams_system::backend::GlobalBackend;
9
10use flams_lsp::documents::LSPDocument;
11use flams_lsp::state::DocData::{Data, Doc};
12use flams_lsp::state::UrlOrFile::File;
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, TreeChildIter};
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
29pub fn to_json<T: Serialize>(data: &T) -> *const libc::c_char {
30    CString::new(serde_json::to_string(data).unwrap())
31        .unwrap()
32        .into_raw()
33}
34
35#[unsafe(no_mangle)]
36pub unsafe extern "C" fn free_string(s: *mut libc::c_char) {
37    unsafe {
38        if s.is_null() {
39            return;
40        }
41        drop(CString::from_raw(s));
42    }
43}
44
45static GLOBAL_STATE: LazyLock<Mutex<HMap<UrlOrFile, DocData>>> =
46    LazyLock::new(|| Mutex::new(HMap::default()));
47
48#[unsafe(no_mangle)]
49pub extern "C" fn initialize() {
50    tracing_subscriber::fmt().init();
51    let _ce = color_eyre::install();
52    let spec = flams_system::settings::SettingsSpec::default();
53    // spec.lsp = true;
54    tokio::runtime::Builder::new_multi_thread()
55        .enable_all()
56        .build()
57        .expect("Failed to initialize Tokio runtime")
58        .block_on(async {
59            flams_system::settings::Settings::initialize(spec);
60            GlobalBackend::initialize();
61        });
62}
63
64fn _get_all_files() -> Vec<(Arc<Path>, DocumentURI)> {
65    let mut files: Vec<(Arc<Path>, DocumentURI)> = Vec::new();
66    for a in GlobalBackend::get().all_archives().iter() {
67        if let Archive::Local(a) = a {
68            a.with_sources(|d| {
69                for e in <_ as TreeChildIter<SourceDir>>::dfs(d.children.iter()) {
70                    match e {
71                        SourceEntry::File(f) => {
72                            let Ok(uri) = DocumentURI::from_archive_relpath(a.uri().owned(), &f.relative_path) else { continue};
73                            files.push((
74                                f.relative_path
75                                    .split('/')
76                                    .fold(a.source_dir(), |p, s| p.join(s))
77                                    .into(),
78                                uri
79                            ));
80                        }
81                        _ => {}
82                    }
83                }
84            })
85        }
86    }
87    files
88}
89
90#[unsafe(no_mangle)]
91pub extern "C" fn load_all_files() {
92    let files = _get_all_files();
93    let len = files.len();
94    tracing::info!("Linting {len} files");
95
96    let mut state = GLOBAL_STATE.lock().unwrap();
97    state.clear();
98    // let mut lspstore = LSPStore::<true>::new(&mut state);
99    for (p, uri) in files {
100        if let Some(ret) = LSPStore::<true>::new(&mut state).load(p.as_ref(), &uri) {
101            state.insert(File(p.clone()), Data(ret, true));
102        }
103    }
104    tracing::info!("Finished linting {len} files");
105}
106
107#[unsafe(no_mangle)]
108pub unsafe extern "C" fn list_of_all_files() -> *const libc::c_char {
109    to_json(
110        &_get_all_files()
111            .into_iter()
112            .map(|(p, _)| p.as_ref().to_str().unwrap().to_string())
113            .collect::<Vec<_>>()
114    )
115}
116
117#[unsafe(no_mangle)]
118pub unsafe extern "C" fn list_of_loaded_files() -> *const libc::c_char {
119    let state = GLOBAL_STATE.lock().unwrap();
120    let paths: Vec<&str> = state
121        .keys()
122        .map(|k| match k {
123            File(p) => p.as_ref().to_str().unwrap(),
124            x => {
125                tracing::warn!("Unexpected key: {:?}", x);
126                ""
127            }
128        })
129        .filter(|s| !s.is_empty())
130        .collect();
131    to_json(&paths)
132}
133
134#[unsafe(no_mangle)]
135pub unsafe extern "C" fn get_file_annotations(path: *const libc::c_char) -> *const libc::c_char {
136    let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
137    let state = GLOBAL_STATE.lock().unwrap();
138    let doc = state.get(&File(Path::new(path_str).into()));
139    match doc {
140        Some(Data(data, _)) => to_json(&data.lock().annotations),
141        Some(Doc(_lspdoc)) => CString::new("").unwrap().into_raw(),
142        None => CString::new("").unwrap().into_raw(),
143    }
144}
145
146#[unsafe(no_mangle)]
147pub unsafe extern "C" fn unload_file(path: *const libc::c_char) {
148    let path_str: &str = unsafe { CStr::from_ptr(path).to_str().unwrap() };
149    let mut state = GLOBAL_STATE.lock().unwrap();
150    state.remove(&File(Path::new(path_str).into()));
151}
152
153#[unsafe(no_mangle)]
154pub unsafe extern "C" fn load_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
158    let lspdoc = LSPDocument::new("".to_string(), File(Path::new(path_str).into()));
159    let p = Path::new(path_str);
160    let uri: &DocumentURI = lspdoc.document_uri().unwrap();
161    if let Some(ret) = LSPStore::<true>::new(&mut state).load(p.as_ref(), &uri) {
162        state.insert(File(p.into()), Data(ret, true));
163    }
164}
165
166#[unsafe(no_mangle)]
167pub extern "C" fn unload_all_files() {
168    let mut state = GLOBAL_STATE.lock().unwrap();
169    state.clear();
170}