///|
pub(all) struct TextEdit {
start_offset : Int
end_offset : Int
replacement : String
title : String
safe : Bool
} derive(Debug, Eq)
///|
pub(all) struct FixPlan {
edits : Array[TextEdit]
safe_count : Int
review_count : Int
safe_source : String
diff : String
} derive(Debug, Eq)
///|
fn canonical_format_edit(source : String, config : EditorConfig) -> TextEdit? {
let formatted = format_config(config)
if formatted == source {
None
} else {
Some({
start_offset: 0,
end_offset: source.length(),
replacement: formatted,
title: "应用规范化排版",
safe: true,
})
}
}
///|
fn suggested_root_edit(config : EditorConfig) -> TextEdit? {
if config.root {
None
} else {
Some({
start_offset: 0,
end_offset: 0,
replacement: "root = true\n\n",
title: "将当前文件设为配置搜索根",
safe: false,
})
}
}
///|
fn utf8_bom_review_edits(config : EditorConfig) -> Array[TextEdit] {
let edits : Array[TextEdit] = []
for section in config.sections {
for property in section.properties {
if property.key == "charset" && lower_ascii(property.value) == "utf-8-bom" {
edits.push({
start_offset: property.span.start.offset,
end_offset: property.span.end.offset,
replacement: "charset = utf-8",
title: "改用无 BOM 的 UTF-8",
safe: false,
})
}
}
}
edits
}
///|
fn sort_edits_descending(edits : Array[TextEdit]) -> Array[TextEdit] {
let ordered = edits.copy()
ordered.sort_by((left, right) => right.start_offset - left.start_offset)
ordered
}
///|
fn ranges_overlap(left : TextEdit, right : TextEdit) -> Bool {
left.start_offset < right.end_offset && right.start_offset < left.end_offset
}
///|
/// Validate that edits can be applied together without ambiguity.
pub fn validate_edits(
edits : Array[TextEdit],
source_length : Int,
) -> Array[String] {
let errors : Array[String] = []
for index, edit in edits {
if edit.start_offset < 0 ||
edit.end_offset < edit.start_offset ||
edit.end_offset > source_length {
errors.push("编辑 `" + edit.title + "` 的范围超出源码边界。")
}
for previous in 0.. String {
let output = StringBuilder::new()
let ordered = sort_edits_descending(edits)
let mut result = source
for edit in ordered {
result = result[:edit.start_offset].to_owned() +
edit.replacement +
result[edit.end_offset:].to_owned()
}
output.write_string(result)
output.to_string()
}
///|
/// Build conservative automatic edits and separate review-required proposals.
pub fn plan_fixes(source : String) -> FixPlan {
let parsed = parse(source)
let edits : Array[TextEdit] = []
match canonical_format_edit(source, parsed.config) {
Some(edit) => edits.push(edit)
None => ()
}
match suggested_root_edit(parsed.config) {
Some(edit) => edits.push(edit)
None => ()
}
for edit in utf8_bom_review_edits(parsed.config) {
edits.push(edit)
}
let mut safe_count = 0
let mut review_count = 0
for edit in edits {
if edit.safe {
safe_count += 1
} else {
review_count += 1
}
}
let safe_edits : Array[TextEdit] = []
for edit in edits {
if edit.safe {
safe_edits.push(edit)
}
}
let safe_source = if validate_edits(safe_edits, source.length()).length() == 0 {
apply_edits(source, safe_edits)
} else {
source
}
{
edits,
safe_count,
review_count,
safe_source,
diff: unified_diff(source, safe_source),
}
}