Файли
openidea/crates/ide-workspace/src/file_defaults.rs
T
l0ng-ai 27ed0515e8 feat(editor): support transparent native-to-ascii conversion (#537)
Properties documents passed through charset decoding alone, leaving Unicode escapes in the shared document text.

- Add a default-off "Transparent native-to-ascii conversion" setting under the properties encoding and a stable action. Changing it reloads clean properties buffers in place; dirty buffers keep their codec.
- Decode eligible escapes and surrogate pairs when constructing documents, and save non-ASCII as uppercase escapes in pure ASCII. Backslash pairs, malformed escapes, unpaired surrogates and line terminator escapes stay literal, so an edit cannot split a property across lines.
- Use the same codec for search, closed-file readers, Git diffs, staging and partial commits. Clean saves and undone edits preserve the original bytes.

Fixes #519
2026-10-31 14:20:59 +08:00

110 рядки
3.8 KiB
Rust
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
//! Settings › Editor › File Encodings / Code Style defaults applied by the
//! document layer: encoding, BOM and line separator of new (empty) files, the
//! fallback decoding of files that are not valid UTF-8, and EditorConfig support.
use ide_core::settings::Settings;
use ide_text::LineEnding;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq)]
pub struct FileDefaults {
/// Encoding of new files (WHATWG label).
pub encoding: String,
/// Write a BOM into new UTF-8 files.
pub utf8_bom: bool,
pub line_ending: LineEnding,
/// Fallback for project files that are not valid UTF-8.
pub project_fallback: String,
/// Fallback for files outside `root`.
pub global_fallback: String,
pub properties_fallback: String,
pub native_to_ascii: bool,
pub editorconfig: bool,
pub root: Option<PathBuf>,
}
impl Default for FileDefaults {
fn default() -> Self {
Self::from_settings(&Settings::default(), None)
}
}
impl FileDefaults {
pub fn from_settings(s: &Settings, root: Option<&Path>) -> Self {
let enc = &s.editor.encodings;
let style = &s.editor.code_style;
let line_ending = match style.line_separator.as_str() {
"crlf" => LineEnding::CrLf,
"cr" => LineEnding::Cr,
"lf" => LineEnding::Lf,
_ if cfg!(windows) => LineEnding::CrLf,
_ => LineEnding::Lf,
};
let utf8_bom = enc.utf8_bom == "always" || (cfg!(windows) && enc.utf8_bom == "windows");
Self {
encoding: normalize(&enc.project),
utf8_bom,
line_ending,
project_fallback: normalize(&enc.project),
global_fallback: normalize(&enc.global),
properties_fallback: normalize(&enc.properties),
native_to_ascii: enc.native_to_ascii,
editorconfig: style.editorconfig,
root: root.map(Path::to_path_buf),
}
}
/// Encoding to decode `path` with when it is not valid UTF-8 (`None`: detect).
pub fn fallback_for(&self, path: &Path) -> Option<&str> {
let label = if path.extension().is_some_and(|e| e == "properties") {
&self.properties_fallback
} else if self.root.as_ref().is_none_or(|r| path.starts_with(r)) {
&self.project_fallback
} else {
&self.global_fallback
};
(label != "utf-8").then_some(label.as_str())
}
pub fn native_to_ascii_for(&self, path: &Path) -> bool {
self.native_to_ascii && path.extension().is_some_and(|e| e == "properties")
}
/// Bytes of a new empty file: a BOM when UTF-8 files are created with one.
pub fn new_file_bytes(&self) -> Vec<u8> {
if self.encoding == "utf-8" && self.utf8_bom {
ide_vfs::encode("", "utf-8", true).unwrap_or_default()
} else {
Vec::new()
}
}
}
fn normalize(label: &str) -> String {
let l = label.trim().to_ascii_lowercase();
match l.as_str() {
"" | "utf8" => "utf-8".into(),
_ => l,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_settings() {
let mut s = Settings::default();
s.editor.encodings.utf8_bom = "always".into();
s.editor.encodings.project = "GBK".into();
s.editor.code_style.line_separator = "crlf".into();
let d = FileDefaults::from_settings(&s, Some(Path::new("/p")));
assert!(d.utf8_bom);
assert_eq!(d.line_ending, LineEnding::CrLf);
assert_eq!(d.fallback_for(Path::new("/p/a.txt")), Some("gbk"));
assert_eq!(d.fallback_for(Path::new("/elsewhere/a.txt")), None);
assert_eq!(d.fallback_for(Path::new("/p/a.properties")), Some("iso-8859-1"));
assert!(d.new_file_bytes().is_empty(), "BOM only for UTF-8 new files");
}
}