1pub mod abbreviations;
39pub mod cli;
40pub mod config;
41pub mod diff;
42pub mod files;
43pub mod format;
44pub mod git_diff;
45pub mod init;
46pub mod lsp;
47pub mod output;
48pub mod parser;
49pub mod reflow;
50pub mod sdiff;
51pub mod sentence;
52pub mod watch;
53
54use anyhow::Result;
55
56use crate::format::Format;
57use crate::parser::FormatParser;
58use crate::parser::latex::LatexParser;
59use crate::parser::markdown::MarkdownParser;
60use crate::parser::org::OrgParser;
61use crate::parser::plaintext::PlaintextParser;
62use crate::reflow::{ReflowConfig, reflow};
63use crate::sentence::SentenceSplitter;
64use crate::sentence::unicode::UnicodeSentenceSplitter;
65
66pub struct FormatConfig {
68 pub format: Format,
69 pub max_width: usize,
70 pub use_neural: bool,
71 pub neural_lang: String,
72 pub neural_model_path: Option<std::path::PathBuf>,
73 pub extra_abbreviations: Vec<String>,
74 pub use_pandoc: bool,
75 pub pandoc_format: Option<String>,
77}
78
79pub fn build_splitter(config: &FormatConfig) -> Result<Box<dyn SentenceSplitter>> {
81 if config.use_neural {
82 let neural = if let Some(ref path) = config.neural_model_path {
83 sentence::neural::NeuralSentenceSplitter::from_path(path)
84 } else {
85 sentence::neural::NeuralSentenceSplitter::new(&config.neural_lang)
86 };
87 Ok(Box::new(neural.map_err(|e| anyhow::anyhow!("{e}"))?))
88 } else {
89 Ok(Box::new(UnicodeSentenceSplitter::for_lang(
90 &config.neural_lang,
91 &config.extra_abbreviations,
92 )))
93 }
94}
95
96pub fn format_text(input: &str, config: &FormatConfig) -> Result<String> {
98 let splitter = build_splitter(config)?;
99 format_text_with_splitter(input, config, splitter.as_ref())
100}
101
102pub fn format_text_with_splitter(
104 input: &str,
105 config: &FormatConfig,
106 splitter: &dyn SentenceSplitter,
107) -> Result<String> {
108 let parser: Box<dyn FormatParser> = if config.use_pandoc {
109 let pandoc_fmt = config
111 .pandoc_format
112 .as_deref()
113 .unwrap_or(match config.format {
114 Format::Org => "org",
115 Format::Latex => "latex",
116 Format::Markdown => "markdown",
117 Format::Rst => "rst",
118 Format::Plaintext => "markdown",
119 });
120 Box::new(parser::pandoc::PandocParser::new(pandoc_fmt))
121 } else {
122 match config.format {
123 Format::Org => Box::new(OrgParser),
124 Format::Latex => Box::new(LatexParser),
125 Format::Markdown => Box::new(MarkdownParser),
126 Format::Rst => Box::new(parser::rst::RstParser),
127 Format::Plaintext => Box::new(PlaintextParser),
128 }
129 };
130
131 let had_trailing_newline = input.ends_with('\n');
132 let uses_crlf = input.contains("\r\n");
133
134 let normalized;
136 let work_input = if uses_crlf {
137 normalized = input.replace("\r\n", "\n");
138 &normalized
139 } else {
140 input
141 };
142
143 let regions = parser.parse(work_input);
144 let reflow_config = ReflowConfig {
145 max_width: config.max_width,
146 };
147
148 let mut output = reflow(®ions, splitter, &reflow_config);
149
150 if had_trailing_newline && !output.ends_with('\n') {
152 output.push('\n');
153 } else if !had_trailing_newline {
154 while output.ends_with('\n') {
155 output.pop();
156 }
157 }
158
159 if uses_crlf {
161 output = output.replace('\n', "\r\n");
162 }
163
164 Ok(output)
165}
166
167pub fn format_range(
170 input: &str,
171 config: &FormatConfig,
172 start: usize,
173 end: usize,
174) -> Result<String> {
175 let lines: Vec<&str> = input.lines().collect();
176 let total = lines.len();
177
178 let start = start.max(1);
180 let end = end.min(total);
181
182 if start > total {
183 return Ok(input.to_string());
184 }
185
186 let range_text = lines[start - 1..end].join("\n");
188 let formatted = format_text(&range_text, config)?;
189
190 let mut result = String::new();
192 for (i, line) in lines.iter().enumerate() {
193 let line_num = i + 1;
194 if line_num < start {
195 result.push_str(line);
196 result.push('\n');
197 }
198 }
199 result.push_str(&formatted);
200 if !formatted.ends_with('\n') && end < total {
201 result.push('\n');
202 }
203 for (i, line) in lines.iter().enumerate() {
204 let line_num = i + 1;
205 if line_num > end {
206 result.push_str(line);
207 if line_num < total {
208 result.push('\n');
209 }
210 }
211 }
212
213 if input.ends_with('\n') && !result.ends_with('\n') {
215 result.push('\n');
216 } else if !input.ends_with('\n') {
217 while result.ends_with('\n') {
218 result.pop();
219 }
220 }
221
222 Ok(result)
223}