use super::config::{
exceeds_multiline_limits, should_passthrough, source_line_offset_or_record_gap, LineMapping,
MultilineConfig, PreprocessedText,
};
use super::string_extract::{extract_string_part, ContinuationType};
use super::structural::collect_structural_fragments;
use crate::fragment_cache::FragmentCache;
pub(crate) fn preprocess_multiline<'a>(
text: impl Into<std::borrow::Cow<'a, str>>,
config: &MultilineConfig,
fragment_cache: &FragmentCache,
) -> PreprocessedText<'a> {
preprocess_multiline_inner(text, config, fragment_cache, false)
}
pub(crate) fn preprocess_multiline_admitted<'a>(
text: impl Into<std::borrow::Cow<'a, str>>,
config: &MultilineConfig,
fragment_cache: &FragmentCache,
) -> PreprocessedText<'a> {
preprocess_multiline_inner(text, config, fragment_cache, true)
}
fn preprocess_multiline_inner<'a>(
text: impl Into<std::borrow::Cow<'a, str>>,
config: &MultilineConfig,
fragment_cache: &FragmentCache,
admission_proven: bool,
) -> PreprocessedText<'a> {
let text_owned: std::borrow::Cow<'a, str> = text.into();
let text: &str = &text_owned;
let should_pass = if admission_proven {
exceeds_multiline_limits(text)
} else {
should_passthrough(text)
};
if should_pass {
return passthrough_text(text_owned);
}
let lines: Vec<&str> = text.lines().collect();
if lines.is_empty() {
return PreprocessedText {
text: std::borrow::Cow::Borrowed(""),
original_end: 0,
mappings: Vec::new(),
};
}
let mut result_lines = Vec::with_capacity(lines.len());
let mut mappings = Vec::new();
let source_line_offsets = crate::compute_line_offsets(text);
let mut current_offset = 0usize;
let mut index = 0;
while index < lines.len() {
let (joined_line, lines_consumed, line_mappings) =
process_line_chain(&lines, &source_line_offsets, index, config, current_offset);
let total_len = joined_line.len();
if !joined_line.is_empty() {
mappings.extend(line_mappings);
}
current_offset += total_len + 1;
result_lines.push(joined_line);
index += lines_consumed.max(1);
}
let joined_text = result_lines.join("\n");
let original_end = text.len();
let joined_trimmed = joined_text.trim();
let text_trimmed = text.trim();
let is_real_concatenation = joined_trimmed != text_trimmed;
let will_append = is_real_concatenation && !joined_text.is_empty();
let structural_base = if will_append {
original_end + 1 + joined_text.len()
} else {
original_end + 1
};
let (structural_joined, structural_mappings) = collect_structural_fragments(
&lines,
&source_line_offsets,
structural_base,
fragment_cache,
);
if !will_append && structural_joined.is_empty() {
let mut original_mappings = identity_line_mappings(text, original_end);
original_mappings.extend(mappings);
return PreprocessedText {
text: text_owned,
original_end,
mappings: original_mappings,
};
}
let mut final_text = text.to_string();
let mut appended_any = false;
if will_append {
final_text.push('\n');
final_text.push_str(&joined_text);
let append_start = original_end + 1;
for mapping in &mut mappings {
mapping.start_offset += append_start;
mapping.end_offset += append_start;
}
appended_any = true;
}
if !structural_joined.is_empty() {
if !appended_any {
final_text.push('\n');
}
final_text.push_str(&structural_joined.join("\n"));
mappings.extend(structural_mappings);
}
let mut original_mappings = identity_line_mappings(text, original_end);
original_mappings.extend(mappings);
PreprocessedText {
text: std::borrow::Cow::Owned(final_text),
original_end,
mappings: original_mappings,
}
}
fn identity_line_mappings(text: &str, original_end: usize) -> Vec<LineMapping> {
let mut original_mappings = Vec::new();
let mut offset = 0;
for (line_idx, line) in text.split('\n').enumerate() {
let end = offset + line.len();
original_mappings.push(LineMapping {
line_number: line_idx + 1,
start_offset: offset,
end_offset: (end + 1).min(original_end),
original_start_offset: offset,
transport_decoded: false,
});
offset = end + 1;
}
original_mappings
}
fn passthrough_text(text: std::borrow::Cow<'_, str>) -> PreprocessedText<'_> {
let original_end = text.len();
let mappings = if text.is_empty() {
Vec::new()
} else {
identity_line_mappings(&text, original_end)
};
PreprocessedText {
text,
original_end,
mappings,
}
}
fn process_line_chain(
lines: &[&str],
source_line_offsets: &[usize],
start_idx: usize,
config: &MultilineConfig,
base_offset: usize,
) -> (String, usize, Vec<LineMapping>) {
let mut joined_parts = Vec::new();
let mut current_idx = start_idx;
let mut lines_consumed = 0usize;
let original_start_line = start_idx + 1;
let join_limit = config.max_join_lines.max(1);
while current_idx < lines.len() && lines_consumed < join_limit {
let line = lines[current_idx];
let (part, continues, continuation_type) =
extract_string_part(line, config, current_idx > start_idx);
if current_idx == start_idx {
if !part.is_empty() {
joined_parts.push(part);
}
if !continues {
lines_consumed += 1;
break;
}
} else {
if continuation_type == ContinuationType::Backslash
|| continuation_type == ContinuationType::PlusOperator
|| continuation_type == ContinuationType::DotOperator
|| continuation_type == ContinuationType::Implicit
|| continuation_type == ContinuationType::TemplateLiteral
|| !part.is_empty()
{
joined_parts.push(part);
}
if !continues {
lines_consumed += 1;
break;
}
}
lines_consumed += 1;
current_idx += 1;
}
let joined = joined_parts.join("");
let mappings = if joined.is_empty() {
Vec::new()
} else {
vec![LineMapping {
start_offset: base_offset,
end_offset: base_offset + joined.len(),
line_number: original_start_line,
original_start_offset: source_line_offset_or_record_gap(source_line_offsets, start_idx),
transport_decoded: false,
}]
};
(joined, lines_consumed, mappings)
}