emmylua_code_analysis/resources/
mod.rs

1mod best_resource_path;
2
3use std::path::{Path, PathBuf};
4
5pub use best_resource_path::get_best_resources_dir;
6use include_dir::{Dir, DirEntry, include_dir};
7
8use crate::{LuaFileInfo, get_locale_code, load_workspace_files};
9
10static RESOURCE_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources");
11const VERSION: &str = env!("CARGO_PKG_VERSION");
12
13pub fn load_resource_std(
14    create_resources_dir: Option<String>,
15    is_jit: bool,
16) -> (PathBuf, Vec<LuaFileInfo>) {
17    // 指定了输出的资源目录, 目前只有 lsp 会指定
18    if let Some(create_resources_dir) = create_resources_dir {
19        let resource_path = if create_resources_dir.is_empty() {
20            get_best_resources_dir()
21        } else {
22            PathBuf::from(&create_resources_dir)
23        };
24        // 此时会存在 i18n, 我们需要根据当前语言环境切换到对应语言的 std 目录
25        let std_dir = get_std_dir(&resource_path);
26        let result = load_resource_from_file_system(&resource_path);
27        if let Some(mut files) = result {
28            if !is_jit {
29                remove_jit_resource(&mut files);
30            }
31            return (std_dir, files);
32        }
33    }
34    // 没有指定资源目录, 那么直接使用默认的资源目录, 此时不会存在 i18n
35    let resoucres_dir = get_best_resources_dir();
36    let std_dir = resoucres_dir.join("std");
37    let files = load_resource_from_include_dir();
38    let mut files = files
39        .into_iter()
40        .filter_map(|file| {
41            if file.path.ends_with(".lua") {
42                let path = resoucres_dir
43                    .join(&file.path)
44                    .to_str()
45                    .expect("UTF-8 paths")
46                    .to_string();
47                Some(LuaFileInfo {
48                    path,
49                    content: file.content,
50                })
51            } else {
52                None
53            }
54        })
55        .collect::<_>();
56    if !is_jit {
57        remove_jit_resource(&mut files);
58    }
59    (std_dir, files)
60}
61
62fn remove_jit_resource(files: &mut Vec<LuaFileInfo>) {
63    const JIT_FILES_TO_REMOVE: &[&str] = &[
64        "jit.lua",
65        "jit/profile.lua",
66        "jit/util.lua",
67        "string/buffer.lua",
68        "table/clear.lua",
69        "table/new.lua",
70        "ffi.lua",
71    ];
72    files.retain(|file| {
73        let path = Path::new(&file.path);
74        !JIT_FILES_TO_REMOVE
75            .iter()
76            .any(|suffix| path.ends_with(suffix))
77    });
78}
79
80fn load_resource_from_file_system(resources_dir: &Path) -> Option<Vec<LuaFileInfo>> {
81    // lsp i18n 的资源在更早之前的 crates\emmylua_ls\src\handlers\initialized\std_i18n.rs 中写入到文件系统
82    if check_need_dump_to_file_system() {
83        log::info!("Creating resources dir: {:?}", resources_dir);
84        let files = load_resource_from_include_dir();
85        for file in &files {
86            let path = resources_dir.join(&file.path);
87            let parent = path.parent()?;
88            if !parent.exists() {
89                match std::fs::create_dir_all(parent) {
90                    Ok(_) => {}
91                    Err(e) => {
92                        log::error!("Failed to create dir: {:?}, {:?}", parent, e);
93                        return None;
94                    }
95                }
96            }
97
98            match std::fs::write(&path, &file.content) {
99                Ok(_) => {}
100                Err(e) => {
101                    log::error!("Failed to write file: {:?}, {:?}", path, e);
102                    return None;
103                }
104            }
105        }
106
107        let version_path = resources_dir.join("version");
108        let content = VERSION.to_string();
109        match std::fs::write(&version_path, content) {
110            Ok(_) => {}
111            Err(e) => {
112                log::error!("Failed to write file: {:?}, {:?}", version_path, e);
113                return None;
114            }
115        }
116    }
117
118    let std_dir = get_std_dir(&resources_dir);
119    let match_pattern = vec!["**/*.lua".to_string()];
120    let files = match load_workspace_files(&std_dir, &match_pattern, &Vec::new(), &Vec::new(), None)
121    {
122        Ok(files) => files,
123        Err(e) => {
124            log::error!("Failed to load std lib: {:?}", e);
125            vec![]
126        }
127    };
128
129    Some(files)
130}
131
132fn check_need_dump_to_file_system() -> bool {
133    if cfg!(debug_assertions) {
134        return true;
135    }
136
137    let resoucres_dir = get_best_resources_dir();
138    let version_path = resoucres_dir.join("version");
139
140    if !version_path.exists() {
141        return true;
142    }
143
144    let Ok(content) = std::fs::read_to_string(&version_path) else {
145        return true;
146    };
147    let version = content.trim();
148    if version != VERSION {
149        return true;
150    }
151
152    false
153}
154
155pub fn load_resource_from_include_dir() -> Vec<LuaFileInfo> {
156    let mut files = Vec::new();
157    walk_resource_dir(&RESOURCE_DIR, &mut files);
158    files
159}
160
161fn walk_resource_dir(dir: &Dir, files: &mut Vec<LuaFileInfo>) {
162    for entry in dir.entries() {
163        match entry {
164            DirEntry::File(file) => {
165                let path = file.path();
166                let content = file.contents_utf8().expect("UTF-8 paths");
167
168                files.push(LuaFileInfo {
169                    path: path.to_str().expect("UTF-8 paths").to_string(),
170                    content: content.to_string(),
171                });
172            }
173            DirEntry::Dir(subdir) => {
174                walk_resource_dir(subdir, files);
175            }
176        }
177    }
178}
179
180// 优先使用当前语言环境的 std-{locale} 目录, 否则回退到默认的 std 目录
181fn get_std_dir(resources_dir: &Path) -> PathBuf {
182    let locale = get_locale_code(&rust_i18n::locale());
183    if locale != "en" {
184        let locale_dir = resources_dir.join(format!("std-{locale}"));
185        if locale_dir.exists() {
186            return locale_dir;
187        }
188    }
189    resources_dir.join("std")
190}