on 376c19e help sign in
helix/helix-view/src/document.rs
1537 lines 55 kB history raw
1use anyhow::{anyhow, bail, Context, Error};
2use arc_swap::access::DynAccess;
3use futures_util::future::BoxFuture;
4use futures_util::FutureExt;
5use helix_core::auto_pairs::AutoPairs;
6use helix_core::doc_formatter::TextFormat;
7use helix_core::syntax::Highlight;
8use helix_core::text_annotations::TextAnnotations;
9use helix_core::Range;
10use helix_vcs::{DiffHandle, DiffProviderRegistry};
12use serde::de::{self, Deserialize, Deserializer};
13use serde::Serialize;
14use std::borrow::Cow;
15use std::cell::Cell;
16use std::collections::HashMap;
17use std::fmt::Display;
18use std::future::Future;
19use std::path::{Path, PathBuf};
20use std::str::FromStr;
21use std::sync::Arc;
22use std::time::SystemTime;
24use helix_core::{
25 encoding,
26 history::{History, State, UndoKind},
27 indent::{auto_detect_indent_style, IndentStyle},
28 line_ending::auto_detect_line_ending,
29 syntax::{self, LanguageConfiguration},
30 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, Syntax, Transaction,
31 DEFAULT_LINE_ENDING,
32};
34use crate::editor::{Config, RedrawHandle};
35use crate::{DocumentId, Editor, Theme, View, ViewId};
37/// 8kB of buffer space for encoding and decoding `Rope`s.
38const BUF_SIZE: usize = 8192;
40const DEFAULT_INDENT: IndentStyle = IndentStyle::Tabs;
42pub const DEFAULT_LANGUAGE_NAME: &str = "text";
44pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";
46#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47pub enum Mode {
48 Normal = 0,
49 Select = 1,
50 Insert = 2,
53impl Display for Mode {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Mode::Normal => f.write_str("normal"),
57 Mode::Select => f.write_str("select"),
58 Mode::Insert => f.write_str("insert"),
59 }
60 }
63impl FromStr for Mode {
64 type Err = Error;
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 match s {
68 "normal" => Ok(Mode::Normal),
69 "select" => Ok(Mode::Select),
70 "insert" => Ok(Mode::Insert),
71 _ => bail!("Invalid mode '{}'", s),
72 }
73 }
76// toml deserializer doesn't seem to recognize string as enum
77impl<'de> Deserialize<'de> for Mode {
78 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79 where
80 D: Deserializer<'de>,
81 {
82 let s = String::deserialize(deserializer)?;
83 s.parse().map_err(de::Error::custom)
84 }
87impl Serialize for Mode {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: serde::Serializer,
91 {
92 serializer.collect_str(self)
93 }
96/// A snapshot of the text of a document that we want to write out to disk
97#[derive(Debug, Clone)]
98pub struct DocumentSavedEvent {
99 pub revision: usize,
100 pub doc_id: DocumentId,
101 pub path: PathBuf,
102 pub text: Rope,
105pub type DocumentSavedEventResult = Result<DocumentSavedEvent, anyhow::Error>;
106pub type DocumentSavedEventFuture = BoxFuture<'static, DocumentSavedEventResult>;
108pub struct Document {
109 pub(crate) id: DocumentId,
110 text: Rope,
111 selections: HashMap<ViewId, Selection>,
113 path: Option<PathBuf>,
114 encoding: &'static encoding::Encoding,
116 pub restore_cursor: bool,
118 /// Current indent style.
119 pub indent_style: IndentStyle,
121 /// The document's default line ending.
122 pub line_ending: LineEnding,
124 syntax: Option<Syntax>,
125 /// Corresponding language scope name. Usually `source.<lang>`.
126 pub(crate) language: Option<Arc<LanguageConfiguration>>,
128 /// Pending changes since last history commit.
129 changes: ChangeSet,
130 /// State at last commit. Used for calculating reverts.
131 old_state: Option<State>,
132 /// Undo tree.
133 // It can be used as a cell where we will take it out to get some parts of the history and put
134 // it back as it separated from the edits. We could split out the parts manually but that will
135 // be more troublesome.
136 pub history: Cell<History>,
137 pub config: Arc<dyn DynAccess<Config>>,
139 pub savepoint: Option<Transaction>,
141 // Last time we wrote to the file. This will carry the time the file was last opened if there
142 // were no saves.
143 last_saved_time: SystemTime,
145 last_saved_revision: usize,
146 version: i32, // should be usize?
147 pub(crate) modified_since_accessed: bool,
149 diagnostics: Vec<Diagnostic>,
150 language_server: Option<Arc<helix_lsp::Client>>,
152 diff_handle: Option<DiffHandle>,
155use std::{fmt, mem};
156impl fmt::Debug for Document {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.debug_struct("Document")
159 .field("id", &self.id)
160 .field("text", &self.text)
161 .field("selections", &self.selections)
162 .field("path", &self.path)
163 .field("encoding", &self.encoding)
164 .field("restore_cursor", &self.restore_cursor)
165 .field("syntax", &self.syntax)
166 .field("language", &self.language)
167 .field("changes", &self.changes)
168 .field("old_state", &self.old_state)
169 // .field("history", &self.history)
170 .field("last_saved_time", &self.last_saved_time)
171 .field("last_saved_revision", &self.last_saved_revision)
172 .field("version", &self.version)
173 .field("modified_since_accessed", &self.modified_since_accessed)
174 .field("diagnostics", &self.diagnostics)
175 // .field("language_server", &self.language_server)
176 .finish()
177 }
180// The documentation and implementation of this function should be up-to-date with
181// its sibling function, `to_writer()`.
182//
183/// Decodes a stream of bytes into UTF-8, returning a `Rope` and the
184/// encoding it was decoded as. The optional `encoding` parameter can
185/// be used to override encoding auto-detection.
186pub fn from_reader<R: std::io::Read + ?Sized>(
187 reader: &mut R,
188 encoding: Option<&'static encoding::Encoding>,
189) -> Result<(Rope, &'static encoding::Encoding), Error> {
190 // These two buffers are 8192 bytes in size each and are used as
191 // intermediaries during the decoding process. Text read into `buf`
192 // from `reader` is decoded into `buf_out` as UTF-8. Once either
193 // `buf_out` is full or the end of the reader was reached, the
194 // contents are appended to `builder`.
195 let mut buf = [0u8; BUF_SIZE];
196 let mut buf_out = [0u8; BUF_SIZE];
197 let mut builder = RopeBuilder::new();
199 // By default, the encoding of the text is auto-detected via the
200 // `chardetng` crate which requires sample data from the reader.
201 // As a manual override to this auto-detection is possible, the
202 // same data is read into `buf` to ensure symmetry in the upcoming
203 // loop.
204 let (encoding, mut decoder, mut slice, mut is_empty) = {
205 let read = reader.read(&mut buf)?;
206 let is_empty = read == 0;
207 let encoding = encoding.unwrap_or_else(|| {
208 let mut encoding_detector = chardetng::EncodingDetector::new();
209 encoding_detector.feed(&buf, is_empty);
210 encoding_detector.guess(None, true)
211 });
212 let decoder = encoding.new_decoder();
214 // If the amount of bytes read from the reader is less than
215 // `buf.len()`, it is undesirable to read the bytes afterwards.
216 let slice = &buf[..read];
217 (encoding, decoder, slice, is_empty)
218 };
220 // `RopeBuilder::append()` expects a `&str`, so this is the "real"
221 // output buffer. When decoding, the number of bytes in the output
222 // buffer will often exceed the number of bytes in the input buffer.
223 // The `result` returned by `decode_to_str()` will state whether or
224 // not that happened. The contents of `buf_str` is appended to
225 // `builder` and it is reused for the next iteration of the decoding
226 // loop.
227 //
228 // As it is possible to read less than the buffer's maximum from `read()`
229 // even when the end of the reader has yet to be reached, the end of
230 // the reader is determined only when a `read()` call returns `0`.
231 //
232 // SAFETY: `buf_out` is a zero-initialized array, thus it will always
233 // contain valid UTF-8.
234 let buf_str = unsafe { std::str::from_utf8_unchecked_mut(&mut buf_out[..]) };
235 let mut total_written = 0usize;
236 loop {
237 let mut total_read = 0usize;
239 // An inner loop is necessary as it is possible that the input buffer
240 // may not be completely decoded on the first `decode_to_str()` call
241 // which would happen in cases where the output buffer is filled to
242 // capacity.
243 loop {
244 let (result, read, written, ..) = decoder.decode_to_str(
245 &slice[total_read..],
246 &mut buf_str[total_written..],
247 is_empty,
248 );
250 // These variables act as the read and write cursors of `buf` and `buf_str` respectively.
251 // They are necessary in case the output buffer fills before decoding of the entire input
252 // loop is complete. Otherwise, the loop would endlessly iterate over the same `buf` and
253 // the data inside the output buffer would be overwritten.
254 total_read += read;
255 total_written += written;
256 match result {
257 encoding::CoderResult::InputEmpty => {
258 debug_assert_eq!(slice.len(), total_read);
259 break;
260 }
261 encoding::CoderResult::OutputFull => {
262 debug_assert!(slice.len() > total_read);
263 builder.append(&buf_str[..total_written]);
264 total_written = 0;
265 }
266 }
267 }
268 // Once the end of the stream is reached, the output buffer is
269 // flushed and the loop terminates.
270 if is_empty {
271 debug_assert_eq!(reader.read(&mut buf)?, 0);
272 builder.append(&buf_str[..total_written]);
273 break;
274 }
276 // Once the previous input has been processed and decoded, the next set of
277 // data is fetched from the reader. The end of the reader is determined to
278 // be when exactly `0` bytes were read from the reader, as per the invariants
279 // of the `Read` trait.
280 let read = reader.read(&mut buf)?;
281 slice = &buf[..read];
282 is_empty = read == 0;
283 }
284 let rope = builder.finish();
285 Ok((rope, encoding))
288// The documentation and implementation of this function should be up-to-date with
289// its sibling function, `from_reader()`.
290//
291/// Encodes the text inside `rope` into the given `encoding` and writes the
292/// encoded output into `writer.` As a `Rope` can only contain valid UTF-8,
293/// replacement characters may appear in the encoded text.
294pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
295 writer: &'a mut W,
296 encoding: &'static encoding::Encoding,
297 rope: &'a Rope,
298) -> Result<(), Error> {
299 // Text inside a `Rope` is stored as non-contiguous blocks of data called
300 // chunks. The absolute size of each chunk is unknown, thus it is impossible
301 // to predict the end of the chunk iterator ahead of time. Instead, it is
302 // determined by filtering the iterator to remove all empty chunks and then
303 // appending an empty chunk to it. This is valuable for detecting when all
304 // chunks in the `Rope` have been iterated over in the subsequent loop.
305 let iter = rope
306 .chunks()
307 .filter(|c| !c.is_empty())
308 .chain(std::iter::once(""));
309 let mut buf = [0u8; BUF_SIZE];
310 let mut encoder = encoding.new_encoder();
311 let mut total_written = 0usize;
312 for chunk in iter {
313 let is_empty = chunk.is_empty();
314 let mut total_read = 0usize;
316 // An inner loop is necessary as it is possible that the input buffer
317 // may not be completely encoded on the first `encode_from_utf8()` call
318 // which would happen in cases where the output buffer is filled to
319 // capacity.
320 loop {
321 let (result, read, written, ..) =
322 encoder.encode_from_utf8(&chunk[total_read..], &mut buf[total_written..], is_empty);
324 // These variables act as the read and write cursors of `chunk` and `buf` respectively.
325 // They are necessary in case the output buffer fills before encoding of the entire input
326 // loop is complete. Otherwise, the loop would endlessly iterate over the same `chunk` and
327 // the data inside the output buffer would be overwritten.
328 total_read += read;
329 total_written += written;
330 match result {
331 encoding::CoderResult::InputEmpty => {
332 debug_assert_eq!(chunk.len(), total_read);
333 debug_assert!(buf.len() >= total_written);
334 break;
335 }
336 encoding::CoderResult::OutputFull => {
337 debug_assert!(chunk.len() > total_read);
338 writer.write_all(&buf[..total_written]).await?;
339 total_written = 0;
340 }
341 }
342 }
344 // Once the end of the iterator is reached, the output buffer is
345 // flushed and the outer loop terminates.
346 if is_empty {
347 writer.write_all(&buf[..total_written]).await?;
348 writer.flush().await?;
349 break;
350 }
351 }
352 Ok(())
355fn take_with<T, F>(mut_ref: &mut T, f: F)
356where
357 T: Default,
358 F: FnOnce(T) -> T,
360 *mut_ref = f(mem::take(mut_ref));
363use helix_lsp::lsp;
364use url::Url;
366impl Document {
367 pub fn from(
368 text: Rope,
369 encoding: Option<&'static encoding::Encoding>,
370 config: Arc<dyn DynAccess<Config>>,
371 ) -> Self {
372 let encoding = encoding.unwrap_or(encoding::UTF_8);
373 let changes = ChangeSet::new(&text);
374 let old_state = None;
376 Self {
377 id: DocumentId::default(),
378 path: None,
379 encoding,
380 text,
381 selections: HashMap::default(),
382 indent_style: DEFAULT_INDENT,
383 line_ending: DEFAULT_LINE_ENDING,
384 restore_cursor: false,
385 syntax: None,
386 language: None,
387 changes,
388 old_state,
389 diagnostics: Vec::new(),
390 version: 0,
391 history: Cell::new(History::default()),
392 savepoint: None,
393 last_saved_time: SystemTime::now(),
394 last_saved_revision: 0,
395 modified_since_accessed: false,
396 language_server: None,
397 diff_handle: None,
398 config,
399 }
400 }
401 pub fn default(config: Arc<dyn DynAccess<Config>>) -> Self {
402 let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
403 Self::from(text, None, config)
404 }
405 // TODO: async fn?
406 /// Create a new document from `path`. Encoding is auto-detected, but it can be manually
407 /// overwritten with the `encoding` parameter.
408 pub fn open(
409 path: &Path,
410 encoding: Option<&'static encoding::Encoding>,
411 config_loader: Option<Arc<syntax::Loader>>,
412 config: Arc<dyn DynAccess<Config>>,
413 ) -> Result<Self, Error> {
414 // Open the file if it exists, otherwise assume it is a new file (and thus empty).
415 let (rope, encoding) = if path.exists() {
416 let mut file =
417 std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
418 from_reader(&mut file, encoding)?
419 } else {
420 let encoding = encoding.unwrap_or(encoding::UTF_8);
421 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
422 };
424 let mut doc = Self::from(rope, Some(encoding), config);
426 // set the path and try detecting the language
427 doc.set_path(Some(path))?;
428 if let Some(loader) = config_loader {
429 doc.detect_language(loader);
430 }
432 doc.detect_indent_and_line_ending();
434 Ok(doc)
435 }
437 /// The same as [`format`], but only returns formatting changes if auto-formatting
438 /// is configured.
439 pub fn auto_format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
440 if self.language_config()?.auto_format {
441 self.format()
442 } else {
443 None
444 }
445 }
447 /// If supported, returns the changes that should be applied to this document in order
448 /// to format it nicely.
449 // We can't use anyhow::Result here since the output of the future has to be
450 // clonable to be used as shared future. So use a custom error type.
451 pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
452 if let Some(formatter) = self
453 .language_config()
454 .and_then(|c| c.formatter.clone())
455 .filter(|formatter| which::which(&formatter.command).is_ok())
456 {
457 use std::process::Stdio;
458 let text = self.text().clone();
459 let mut process = tokio::process::Command::new(&formatter.command);
460 process
461 .args(&formatter.args)
462 .stdin(Stdio::piped())
463 .stdout(Stdio::piped())
464 .stderr(Stdio::piped());
466 let formatting_future = async move {
467 let mut process = process
468 .spawn()
469 .map_err(|e| FormatterError::SpawningFailed {
470 command: formatter.command.clone(),
471 error: e.kind(),
472 })?;
473 {
474 let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
475 to_writer(&mut stdin, encoding::UTF_8, &text)
476 .await
477 .map_err(|_| FormatterError::BrokenStdin)?;
478 }
480 let output = process
481 .wait_with_output()
482 .await
483 .map_err(|_| FormatterError::WaitForOutputFailed)?;
485 if !output.status.success() {
486 if !output.stderr.is_empty() {
487 let err = String::from_utf8_lossy(&output.stderr).to_string();
488 log::error!("Formatter error: {}", err);
489 return Err(FormatterError::NonZeroExitStatus(Some(err)));
490 }
492 return Err(FormatterError::NonZeroExitStatus(None));
493 } else if !output.stderr.is_empty() {
494 log::debug!(
495 "Formatter printed to stderr: {}",
496 String::from_utf8_lossy(&output.stderr).to_string()
497 );
498 }
500 let str = std::str::from_utf8(&output.stdout)
501 .map_err(|_| FormatterError::InvalidUtf8Output)?;
503 Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
504 };
505 return Some(formatting_future.boxed());
506 };
508 let language_server = self.language_server()?;
509 let text = self.text.clone();
510 let offset_encoding = language_server.offset_encoding();
512 let request = language_server.text_document_formatting(
513 self.identifier(),
514 lsp::FormattingOptions {
515 tab_size: self.tab_width() as u32,
516 insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
517 ..Default::default()
518 },
519 None,
520 )?;
522 let fut = async move {
523 let edits = request.await.unwrap_or_else(|e| {
524 log::warn!("LSP formatting failed: {}", e);
525 Default::default()
526 });
527 Ok(helix_lsp::util::generate_transaction_from_edits(
528 &text,
529 edits,
530 offset_encoding,
531 ))
532 };
533 Some(fut.boxed())
534 }
536 pub fn save<P: Into<PathBuf>>(
537 &mut self,
538 path: Option<P>,
539 force: bool,
540 ) -> Result<
541 impl Future<Output = Result<DocumentSavedEvent, anyhow::Error>> + 'static + Send,
542 anyhow::Error,
543 > {
544 let path = path.map(|path| path.into());
545 self.save_impl(path, force)
547 // futures_util::future::Ready<_>,
548 }
550 /// The `Document`'s text is encoded according to its encoding and written to the file located
551 /// at its `path()`.
552 fn save_impl(
553 &mut self,
554 path: Option<PathBuf>,
555 force: bool,
556 ) -> Result<
557 impl Future<Output = Result<DocumentSavedEvent, anyhow::Error>> + 'static + Send,
558 anyhow::Error,
559 > {
560 log::debug!(
561 "submitting save of doc '{:?}'",
562 self.path().map(|path| path.to_string_lossy())
563 );
565 // we clone and move text + path into the future so that we asynchronously save the current
566 // state without blocking any further edits.
567 let text = self.text().clone();
569 let path = match path {
570 Some(path) => helix_core::path::get_canonicalized_path(&path)?,
571 None => {
572 if self.path.is_none() {
573 bail!("Can't save with no path set!");
574 }
576 self.path.as_ref().unwrap().clone()
577 }
578 };
580 let identifier = self.path().map(|_| self.identifier());
581 let language_server = self.language_server.clone();
583 // mark changes up to now as saved
584 let current_rev = self.get_current_revision();
585 let doc_id = self.id();
587 let encoding = self.encoding;
589 let last_saved_time = self.last_saved_time;
591 // We encode the file according to the `Document`'s encoding.
592 let future = async move {
593 use tokio::{fs, fs::File};
594 if let Some(parent) = path.parent() {
595 // TODO: display a prompt asking the user if the directories should be created
596 if !parent.exists() {
597 if force {
598 std::fs::DirBuilder::new().recursive(true).create(parent)?;
599 } else {
600 bail!("can't save file, parent directory does not exist");
601 }
602 }
603 }
605 // Protect against overwriting changes made externally
606 if !force {
607 if let Ok(metadata) = fs::metadata(&path).await {
608 if let Ok(mtime) = metadata.modified() {
609 if last_saved_time < mtime {
610 bail!("file modified by an external process, use :w! to overwrite");
611 }
612 }
613 }
614 }
616 let mut file = File::create(&path).await?;
617 to_writer(&mut file, encoding, &text).await?;
619 let event = DocumentSavedEvent {
620 revision: current_rev,
621 doc_id,
622 path,
623 text: text.clone(),
624 };
626 if let Some(language_server) = language_server {
627 if !language_server.is_initialized() {
628 return Ok(event);
629 }
631 if let Some(identifier) = identifier {
632 if let Some(notification) =
633 language_server.text_document_did_save(identifier, &text)
634 {
635 notification.await?;
636 }
637 }
638 }
640 Ok(event)
641 };
643 Ok(future)
644 }
646 /// Detect the programming language based on the file type.
647 pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
648 if let Some(path) = &self.path {
649 let language_config = config_loader
650 .language_config_for_file_name(path)
651 .or_else(|| config_loader.language_config_for_shebang(self.text()));
652 self.set_language(language_config, Some(config_loader));
653 }
654 }
656 /// Detect the indentation used in the file, or otherwise defaults to the language indentation
657 /// configured in `languages.toml`, with a fallback to tabs if it isn't specified. Line ending
658 /// is likewise auto-detected, and will fallback to the default OS line ending.
659 pub fn detect_indent_and_line_ending(&mut self) {
660 self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
661 self.language_config()
662 .and_then(|config| config.indent.as_ref())
663 .map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
664 });
665 self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
666 }
668 /// Reload the document from its path.
669 pub fn reload(
670 &mut self,
671 view: &mut View,
672 provider_registry: &DiffProviderRegistry,
673 redraw_handle: RedrawHandle,
674 ) -> Result<(), Error> {
675 let encoding = &self.encoding;
676 let path = self
677 .path()
678 .filter(|path| path.exists())
679 .ok_or_else(|| anyhow!("can't find file to reload from"))?
680 .to_owned();
682 let mut file = std::fs::File::open(&path)?;
683 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
685 // Calculate the difference between the buffer and source text, and apply it.
686 // This is not considered a modification of the contents of the file regardless
687 // of the encoding.
688 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
689 self.apply(&transaction, view.id);
690 self.append_changes_to_history(view);
691 self.reset_modified();
693 self.last_saved_time = SystemTime::now();
695 self.detect_indent_and_line_ending();
697 match provider_registry.get_diff_base(&path) {
698 Some(diff_base) => self.set_diff_base(diff_base, redraw_handle),
699 None => self.diff_handle = None,
700 }
702 Ok(())
703 }
705 /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
706 pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
707 self.encoding = encoding::Encoding::for_label(label.as_bytes())
708 .ok_or_else(|| anyhow!("unknown encoding"))?;
709 Ok(())
710 }
712 /// Returns the [`Document`]'s current encoding.
713 pub fn encoding(&self) -> &'static encoding::Encoding {
714 self.encoding
715 }
717 pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> {
718 let path = path
719 .map(helix_core::path::get_canonicalized_path)
720 .transpose()?;
722 // if parent doesn't exist we still want to open the document
723 // and error out when document is saved
724 self.path = path;
726 Ok(())
727 }
729 /// Set the programming language for the file and load associated data (e.g. highlighting)
730 /// if it exists.
731 pub fn set_language(
732 &mut self,
733 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
734 loader: Option<Arc<helix_core::syntax::Loader>>,
735 ) {
736 if let (Some(language_config), Some(loader)) = (language_config, loader) {
737 if let Some(highlight_config) = language_config.highlight_config(&loader.scopes()) {
738 let syntax = Syntax::new(&self.text, highlight_config, loader);
739 self.syntax = Some(syntax);
740 }
742 self.language = Some(language_config);
743 } else {
744 self.syntax = None;
745 self.language = None;
746 };
747 }
749 /// Set the programming language for the file if you know the name (scope) but don't have the
750 /// [`syntax::LanguageConfiguration`] for it.
751 pub fn set_language2(&mut self, scope: &str, config_loader: Arc<syntax::Loader>) {
752 let language_config = config_loader.language_config_for_scope(scope);
754 self.set_language(language_config, Some(config_loader));
755 }
757 /// Set the programming language for the file if you know the language but don't have the
758 /// [`syntax::LanguageConfiguration`] for it.
759 pub fn set_language_by_language_id(
760 &mut self,
761 language_id: &str,
762 config_loader: Arc<syntax::Loader>,
763 ) -> anyhow::Result<()> {
764 let language_config = config_loader
765 .language_config_for_language_id(language_id)
766 .ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
767 self.set_language(Some(language_config), Some(config_loader));
768 Ok(())
769 }
771 /// Set the LSP.
772 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
773 self.language_server = language_server;
774 }
776 /// Select text within the [`Document`].
777 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
778 // TODO: use a transaction?
779 self.selections
780 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
781 }
783 /// Find the origin selection of the text in a document, i.e. where
784 /// a single cursor would go if it were on the first grapheme. If
785 /// the text is empty, returns (0, 0).
786 pub fn origin(&self) -> Range {
787 if self.text().len_chars() == 0 {
788 return Range::new(0, 0);
789 }
791 Range::new(0, 1).grapheme_aligned(self.text().slice(..))
792 }
794 /// Reset the view's selection on this document to the
795 /// [origin](Document::origin) cursor.
796 pub fn reset_selection(&mut self, view_id: ViewId) {
797 let origin = self.origin();
798 self.set_selection(view_id, Selection::single(origin.anchor, origin.head));
799 }
801 /// Initializes a new selection for the given view if it does not
802 /// already have one.
803 pub fn ensure_view_init(&mut self, view_id: ViewId) {
804 if self.selections.get(&view_id).is_none() {
805 self.reset_selection(view_id);
806 }
807 }
809 /// Remove a view's selection from this document.
810 pub fn remove_view(&mut self, view_id: ViewId) {
811 self.selections.remove(&view_id);
812 }
814 /// Apply a [`Transaction`] to the [`Document`] to change its text.
815 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
816 let old_doc = self.text().clone();
818 let success = transaction.changes().apply(&mut self.text);
820 if success {
821 for selection in self.selections.values_mut() {
822 *selection = selection
823 .clone()
824 // Map through changes
825 .map(transaction.changes())
826 // Ensure all selections across all views still adhere to invariants.
827 .ensure_invariants(self.text.slice(..));
828 }
830 // if specified, the current selection should instead be replaced by transaction.selection
831 if let Some(selection) = transaction.selection() {
832 self.selections.insert(
833 view_id,
834 selection.clone().ensure_invariants(self.text.slice(..)),
835 );
836 }
838 self.modified_since_accessed = true;
839 }
841 if !transaction.changes().is_empty() {
842 self.version += 1;
843 // start computing the diff in parallel
844 if let Some(diff_handle) = &self.diff_handle {
845 diff_handle.update_document(self.text.clone(), false);
846 }
848 // generate revert to savepoint
849 if self.savepoint.is_some() {
850 take_with(&mut self.savepoint, |prev_revert| {
851 let revert = transaction.invert(&old_doc);
852 Some(revert.compose(prev_revert.unwrap()))
853 });
854 }
856 // update tree-sitter syntax tree
857 if let Some(syntax) = &mut self.syntax {
858 // TODO: no unwrap
859 syntax
860 .update(&old_doc, &self.text, transaction.changes())
861 .unwrap();
862 }
864 // map state.diagnostics over changes::map_pos too
865 for diagnostic in &mut self.diagnostics {
866 use helix_core::Assoc;
867 let changes = transaction.changes();
868 diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
869 diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
870 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
871 }
872 self.diagnostics
873 .sort_unstable_by_key(|diagnostic| diagnostic.range);
875 // emit lsp notification
876 if let Some(language_server) = self.language_server() {
877 let notify = language_server.text_document_did_change(
878 self.versioned_identifier(),
879 &old_doc,
880 self.text(),
881 transaction.changes(),
882 );
884 if let Some(notify) = notify {
885 tokio::spawn(notify);
886 }
887 }
888 }
889 success
890 }
892 /// Apply a [`Transaction`] to the [`Document`] to change its text.
893 pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
894 // store the state just before any changes are made. This allows us to undo to the
895 // state just before a transaction was applied.
896 if self.changes.is_empty() && !transaction.changes().is_empty() {
897 self.old_state = Some(State {
898 doc: self.text.clone(),
899 selection: self.selection(view_id).clone(),
900 });
901 }
903 let success = self.apply_impl(transaction, view_id);
905 if !transaction.changes().is_empty() {
906 // Compose this transaction with the previous one
907 take_with(&mut self.changes, |changes| {
908 changes.compose(transaction.changes().clone())
909 });
910 }
911 success
912 }
914 fn undo_redo_impl(&mut self, view: &mut View, undo: bool) -> bool {
915 let mut history = self.history.take();
916 let txn = if undo { history.undo() } else { history.redo() };
917 let success = if let Some(txn) = txn {
918 self.apply_impl(txn, view.id)
919 } else {
920 false
921 };
922 self.history.set(history);
924 if success {
925 // reset changeset to fix len
926 self.changes = ChangeSet::new(self.text());
927 // Sync with changes with the jumplist selections.
928 view.sync_changes(self);
929 }
930 success
931 }
933 /// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
934 pub fn undo(&mut self, view: &mut View) -> bool {
935 self.undo_redo_impl(view, true)
936 }
938 /// Redo the last modification to the [`Document`]. Returns whether the redo was successful.
939 pub fn redo(&mut self, view: &mut View) -> bool {
940 self.undo_redo_impl(view, false)
941 }
943 pub fn savepoint(&mut self) {
944 self.savepoint = Some(Transaction::new(self.text()));
945 }
947 pub fn restore(&mut self, view: &mut View) {
948 if let Some(revert) = self.savepoint.take() {
949 self.apply(&revert, view.id);
950 }
951 }
953 fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool {
954 let txns = if earlier {
955 self.history.get_mut().earlier(uk)
956 } else {
957 self.history.get_mut().later(uk)
958 };
959 let mut success = false;
960 for txn in txns {
961 if self.apply_impl(&txn, view.id) {
962 success = true;
963 }
964 }
965 if success {
966 // reset changeset to fix len
967 self.changes = ChangeSet::new(self.text());
968 // Sync with changes with the jumplist selections.
969 view.sync_changes(self);
970 }
971 success
972 }
974 /// Undo modifications to the [`Document`] according to `uk`.
975 pub fn earlier(&mut self, view: &mut View, uk: UndoKind) -> bool {
976 self.earlier_later_impl(view, uk, true)
977 }
979 /// Redo modifications to the [`Document`] according to `uk`.
980 pub fn later(&mut self, view: &mut View, uk: UndoKind) -> bool {
981 self.earlier_later_impl(view, uk, false)
982 }
984 /// Commit pending changes to history
985 pub fn append_changes_to_history(&mut self, view: &mut View) {
986 if self.changes.is_empty() {
987 return;
988 }
990 let new_changeset = ChangeSet::new(self.text());
991 let changes = std::mem::replace(&mut self.changes, new_changeset);
992 // Instead of doing this messy merge we could always commit, and based on transaction
993 // annotations either add a new layer or compose into the previous one.
994 let transaction =
995 Transaction::from(changes).with_selection(self.selection(view.id).clone());
997 // HAXX: we need to reconstruct the state as it was before the changes..
998 let old_state = self.old_state.take().expect("no old_state available");
1000 let mut history = self.history.take();
1001 history.commit_revision(&transaction, &old_state);
1002 self.history.set(history);
1004 // Update jumplist entries in the view.
1005 view.apply(&transaction, self);
1008 pub fn id(&self) -> DocumentId {
1009 self.id
1012 /// If there are unsaved modifications.
1013 pub fn is_modified(&self) -> bool {
1014 let history = self.history.take();
1015 let current_revision = history.current_revision();
1016 self.history.set(history);
1017 log::debug!(
1018 "id {} modified - last saved: {}, current: {}",
1019 self.id,
1020 self.last_saved_revision,
1021 current_revision
1022 );
1023 current_revision != self.last_saved_revision || !self.changes.is_empty()
1026 /// Save modifications to history, and so [`Self::is_modified`] will return false.
1027 pub fn reset_modified(&mut self) {
1028 let history = self.history.take();
1029 let current_revision = history.current_revision();
1030 self.history.set(history);
1031 self.last_saved_revision = current_revision;
1034 /// Set the document's latest saved revision to the given one.
1035 pub fn set_last_saved_revision(&mut self, rev: usize) {
1036 log::debug!(
1037 "doc {} revision updated {} -> {}",
1038 self.id,
1039 self.last_saved_revision,
1040 rev
1041 );
1042 self.last_saved_revision = rev;
1043 self.last_saved_time = SystemTime::now();
1046 /// Get the document's latest saved revision.
1047 pub fn get_last_saved_revision(&mut self) -> usize {
1048 self.last_saved_revision
1051 /// Get the current revision number
1052 pub fn get_current_revision(&mut self) -> usize {
1053 let history = self.history.take();
1054 let current_revision = history.current_revision();
1055 self.history.set(history);
1056 current_revision
1059 /// Corresponding language scope name. Usually `source.<lang>`.
1060 pub fn language_scope(&self) -> Option<&str> {
1061 self.language
1062 .as_ref()
1063 .map(|language| language.scope.as_str())
1066 /// Language name for the document. Corresponds to the `name` key in
1067 /// `languages.toml` configuration.
1068 pub fn language_name(&self) -> Option<&str> {
1069 self.language
1070 .as_ref()
1071 .map(|language| language.language_id.as_str())
1074 /// Language ID for the document. Either the `language-id` from the
1075 /// `language-server` configuration, or the document language if no
1076 /// `language-id` has been specified.
1077 pub fn language_id(&self) -> Option<&str> {
1078 let language_config = self.language.as_deref()?;
1080 language_config
1081 .language_server
1082 .as_ref()?
1083 .language_id
1084 .as_deref()
1085 .or(Some(language_config.language_id.as_str()))
1088 /// Corresponding [`LanguageConfiguration`].
1089 pub fn language_config(&self) -> Option<&LanguageConfiguration> {
1090 self.language.as_deref()
1093 /// Current document version, incremented at each change.
1094 pub fn version(&self) -> i32 {
1095 self.version
1098 /// Language server if it has been initialized.
1099 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
1100 let server = self.language_server.as_deref()?;
1101 server.is_initialized().then_some(server)
1104 pub fn diff_handle(&self) -> Option<&DiffHandle> {
1105 self.diff_handle.as_ref()
1108 /// Intialize/updates the differ for this document with a new base.
1109 pub fn set_diff_base(&mut self, diff_base: Vec<u8>, redraw_handle: RedrawHandle) {
1110 if let Ok((diff_base, _)) = from_reader(&mut diff_base.as_slice(), Some(self.encoding)) {
1111 if let Some(differ) = &self.diff_handle {
1112 differ.update_diff_base(diff_base);
1113 return;
1115 self.diff_handle = Some(DiffHandle::new(diff_base, self.text.clone(), redraw_handle))
1116 } else {
1117 self.diff_handle = None;
1121 #[inline]
1122 /// Tree-sitter AST tree
1123 pub fn syntax(&self) -> Option<&Syntax> {
1124 self.syntax.as_ref()
1127 /// The width that the tab character is rendered at
1128 pub fn tab_width(&self) -> usize {
1129 self.language_config()
1130 .and_then(|config| config.indent.as_ref())
1131 .map_or(4, |config| config.tab_width) // fallback to 4 columns
1134 // The width (in spaces) of a level of indentation.
1135 pub fn indent_width(&self) -> usize {
1136 self.indent_style.indent_width(self.tab_width())
1139 pub fn changes(&self) -> &ChangeSet {
1140 &self.changes
1143 #[inline]
1144 /// File path on disk.
1145 pub fn path(&self) -> Option<&PathBuf> {
1146 self.path.as_ref()
1149 /// File path as a URL.
1150 pub fn url(&self) -> Option<Url> {
1151 Url::from_file_path(self.path()?).ok()
1154 #[inline]
1155 pub fn text(&self) -> &Rope {
1156 &self.text
1159 #[inline]
1160 pub fn selection(&self, view_id: ViewId) -> &Selection {
1161 &self.selections[&view_id]
1164 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
1165 &self.selections
1168 pub fn relative_path(&self) -> Option<PathBuf> {
1169 self.path
1170 .as_deref()
1171 .map(helix_core::path::get_relative_path)
1174 pub fn display_name(&self) -> Cow<'static, str> {
1175 self.relative_path()
1176 .map(|path| path.to_string_lossy().to_string().into())
1177 .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
1180 // transact(Fn) ?
1182 // -- LSP methods
1184 #[inline]
1185 pub fn identifier(&self) -> lsp::TextDocumentIdentifier {
1186 lsp::TextDocumentIdentifier::new(self.url().unwrap())
1189 pub fn versioned_identifier(&self) -> lsp::VersionedTextDocumentIdentifier {
1190 lsp::VersionedTextDocumentIdentifier::new(self.url().unwrap(), self.version)
1193 pub fn position(
1194 &self,
1195 view_id: ViewId,
1196 offset_encoding: helix_lsp::OffsetEncoding,
1197 ) -> lsp::Position {
1198 let text = self.text();
1200 helix_lsp::util::pos_to_lsp_pos(
1201 text,
1202 self.selection(view_id).primary().cursor(text.slice(..)),
1203 offset_encoding,
1207 #[inline]
1208 pub fn diagnostics(&self) -> &[Diagnostic] {
1209 &self.diagnostics
1212 pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
1213 self.diagnostics = diagnostics;
1214 self.diagnostics
1215 .sort_unstable_by_key(|diagnostic| diagnostic.range);
1218 /// Get the document's auto pairs. If the document has a recognized
1219 /// language config with auto pairs configured, returns that;
1220 /// otherwise, falls back to the global auto pairs config. If the global
1221 /// config is false, then ignore language settings.
1222 pub fn auto_pairs<'a>(&'a self, editor: &'a Editor) -> Option<&'a AutoPairs> {
1223 let global_config = (editor.auto_pairs).as_ref();
1225 // NOTE: If the user specifies the global auto pairs config as false, then
1226 // we want to disable it globally regardless of language settings
1227 #[allow(clippy::question_mark)]
1229 if global_config.is_none() {
1230 return None;
1234 match &self.language {
1235 Some(lang) => lang.as_ref().auto_pairs.as_ref().or(global_config),
1236 None => global_config,
1240 pub fn text_format(&self, mut viewport_width: u16, theme: Option<&Theme>) -> TextFormat {
1241 if let Some(max_line_len) = self
1242 .language_config()
1243 .and_then(|config| config.max_line_length)
1245 viewport_width = viewport_width.min(max_line_len as u16)
1247 let config = self.config.load();
1248 let soft_wrap = &config.soft_wrap;
1249 let tab_width = self.tab_width() as u16;
1250 TextFormat {
1251 soft_wrap: soft_wrap.enable && viewport_width > 10,
1252 tab_width,
1253 max_wrap: soft_wrap.max_wrap.min(viewport_width / 4),
1254 max_indent_retain: soft_wrap.max_indent_retain.min(viewport_width * 2 / 5),
1255 // avoid spinning forever when the window manager
1256 // sets the size to something tiny
1257 viewport_width,
1258 wrap_indicator: soft_wrap.wrap_indicator.clone().into_boxed_str(),
1259 wrap_indicator_highlight: theme
1260 .and_then(|theme| theme.find_scope_index("ui.virtual.wrap"))
1261 .map(Highlight),
1265 pub fn text_annotations(&self, _theme: Option<&Theme>) -> TextAnnotations {
1266 TextAnnotations::default()
1270#[derive(Clone, Debug)]
1271pub enum FormatterError {
1272 SpawningFailed {
1273 command: String,
1274 error: std::io::ErrorKind,
1275 },
1276 BrokenStdin,
1277 WaitForOutputFailed,
1278 InvalidUtf8Output,
1279 DiskReloadError(String),
1280 NonZeroExitStatus(Option<String>),
1283impl std::error::Error for FormatterError {}
1285impl Display for FormatterError {
1286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287 match self {
1288 Self::SpawningFailed { command, error } => {
1289 write!(f, "Failed to spawn formatter {}: {:?}", command, error)
1291 Self::BrokenStdin => write!(f, "Could not write to formatter stdin"),
1292 Self::WaitForOutputFailed => write!(f, "Waiting for formatter output failed"),
1293 Self::InvalidUtf8Output => write!(f, "Invalid UTF-8 formatter output"),
1294 Self::DiskReloadError(error) => write!(f, "Error reloading file from disk: {}", error),
1295 Self::NonZeroExitStatus(Some(output)) => write!(f, "Formatter error: {}", output),
1296 Self::NonZeroExitStatus(None) => {
1297 write!(f, "Formatter exited with non zero exit status")
1303#[cfg(test)]
1304mod test {
1305 use arc_swap::ArcSwap;
1307 use super::*;
1309 #[test]
1310 fn changeset_to_changes_ignore_line_endings() {
1311 use helix_lsp::{lsp, Client, OffsetEncoding};
1312 let text = Rope::from("hello\r\nworld");
1313 let mut doc = Document::from(
1314 text,
1315 None,
1316 Arc::new(ArcSwap::new(Arc::new(Config::default()))),
1317 );
1318 let view = ViewId::default();
1319 doc.set_selection(view, Selection::single(0, 0));
1321 let transaction =
1322 Transaction::change(doc.text(), vec![(5, 7, Some("\n".into()))].into_iter());
1323 let old_doc = doc.text().clone();
1324 doc.apply(&transaction, view);
1325 let changes = Client::changeset_to_changes(
1326 &old_doc,
1327 doc.text(),
1328 transaction.changes(),
1329 OffsetEncoding::Utf8,
1330 );
1332 assert_eq!(doc.text(), "hello\nworld");
1334 assert_eq!(
1335 changes,
1336 &[lsp::TextDocumentContentChangeEvent {
1337 range: Some(lsp::Range::new(
1338 lsp::Position::new(0, 5),
1339 lsp::Position::new(1, 0)
1340 )),
1341 text: "\n".into(),
1342 range_length: None,
1343 }]
1344 );
1347 #[test]
1348 fn changeset_to_changes() {
1349 use helix_lsp::{lsp, Client, OffsetEncoding};
1350 let text = Rope::from("hello");
1351 let mut doc = Document::from(
1352 text,
1353 None,
1354 Arc::new(ArcSwap::new(Arc::new(Config::default()))),
1355 );
1356 let view = ViewId::default();
1357 doc.set_selection(view, Selection::single(5, 5));
1359 // insert
1361 let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
1362 let old_doc = doc.text().clone();
1363 doc.apply(&transaction, view);
1364 let changes = Client::changeset_to_changes(
1365 &old_doc,
1366 doc.text(),
1367 transaction.changes(),
1368 OffsetEncoding::Utf8,
1369 );
1371 assert_eq!(
1372 changes,
1373 &[lsp::TextDocumentContentChangeEvent {
1374 range: Some(lsp::Range::new(
1375 lsp::Position::new(0, 5),
1376 lsp::Position::new(0, 5)
1377 )),
1378 text: " world".into(),
1379 range_length: None,
1380 }]
1381 );
1383 // delete
1385 let transaction = transaction.invert(&old_doc);
1386 let old_doc = doc.text().clone();
1387 doc.apply(&transaction, view);
1388 let changes = Client::changeset_to_changes(
1389 &old_doc,
1390 doc.text(),
1391 transaction.changes(),
1392 OffsetEncoding::Utf8,
1393 );
1395 // line: 0-based.
1396 // col: 0-based, gaps between chars.
1397 // 0 1 2 3 4 5 6 7 8 9 0 1
1398 // |h|e|l|l|o| |w|o|r|l|d|
1399 // -------------
1400 // (0, 5)-(0, 11)
1401 assert_eq!(
1402 changes,
1403 &[lsp::TextDocumentContentChangeEvent {
1404 range: Some(lsp::Range::new(
1405 lsp::Position::new(0, 5),
1406 lsp::Position::new(0, 11)
1407 )),
1408 text: "".into(),
1409 range_length: None,
1410 }]
1411 );
1413 // replace
1415 // also tests that changes are layered, positions depend on previous changes.
1417 doc.set_selection(view, Selection::single(0, 5));
1418 let transaction = Transaction::change(
1419 doc.text(),
1420 vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
1421 );
1422 // aeilou
1423 let old_doc = doc.text().clone();
1424 doc.apply(&transaction, view);
1425 let changes = Client::changeset_to_changes(
1426 &old_doc,
1427 doc.text(),
1428 transaction.changes(),
1429 OffsetEncoding::Utf8,
1430 );
1432 assert_eq!(
1433 changes,
1434 &[
1435 // 0 1 2 3 4 5
1436 // |h|e|l|l|o|
1437 // ----
1438 //
1439 // aeillo
1440 lsp::TextDocumentContentChangeEvent {
1441 range: Some(lsp::Range::new(
1442 lsp::Position::new(0, 0),
1443 lsp::Position::new(0, 2)
1444 )),
1445 text: "aei".into(),
1446 range_length: None,
1447 },
1448 // 0 1 2 3 4 5 6
1449 // |a|e|i|l|l|o|
1450 // -----
1451 //
1452 // aeilou
1453 lsp::TextDocumentContentChangeEvent {
1454 range: Some(lsp::Range::new(
1455 lsp::Position::new(0, 4),
1456 lsp::Position::new(0, 6)
1457 )),
1458 text: "ou".into(),
1459 range_length: None,
1462 );
1465 #[test]
1466 fn test_line_ending() {
1467 assert_eq!(
1468 Document::default(Arc::new(ArcSwap::new(Arc::new(Config::default()))))
1469 .text()
1470 .to_string(),
1471 DEFAULT_LINE_ENDING.as_str()
1472 );
1475 macro_rules! decode {
1476 ($name:ident, $label:expr, $label_override:expr) => {
1477 #[test]
1478 fn $name() {
1479 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1480 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1481 let path = base_path.join(format!("{}_in.txt", $label));
1482 let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
1483 assert!(path.exists());
1484 assert!(ref_path.exists());
1486 let mut file = std::fs::File::open(path).unwrap();
1487 let text = from_reader(&mut file, Some(encoding))
1488 .unwrap()
1489 .0
1490 .to_string();
1491 let expectation = std::fs::read_to_string(ref_path).unwrap();
1492 assert_eq!(text[..], expectation[..]);
1494 };
1495 ($name:ident, $label:expr) => {
1496 decode!($name, $label, $label);
1497 };
1500 macro_rules! encode {
1501 ($name:ident, $label:expr, $label_override:expr) => {
1502 #[test]
1503 fn $name() {
1504 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1505 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1506 let path = base_path.join(format!("{}_out.txt", $label));
1507 let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
1508 assert!(path.exists());
1509 assert!(ref_path.exists());
1511 let text = Rope::from_str(&std::fs::read_to_string(path).unwrap());
1512 let mut buf: Vec<u8> = Vec::new();
1513 helix_lsp::block_on(to_writer(&mut buf, encoding, &text)).unwrap();
1515 let expectation = std::fs::read(ref_path).unwrap();
1516 assert_eq!(buf, expectation);
1518 };
1519 ($name:ident, $label:expr) => {
1520 encode!($name, $label, $label);
1521 };
1524 decode!(big5_decode, "big5");
1525 encode!(big5_encode, "big5");
1526 decode!(euc_kr_decode, "euc_kr", "EUC-KR");
1527 encode!(euc_kr_encode, "euc_kr", "EUC-KR");
1528 decode!(gb18030_decode, "gb18030");
1529 encode!(gb18030_encode, "gb18030");
1530 decode!(iso_2022_jp_decode, "iso_2022_jp", "ISO-2022-JP");
1531 encode!(iso_2022_jp_encode, "iso_2022_jp", "ISO-2022-JP");
1532 decode!(jis0208_decode, "jis0208", "EUC-JP");
1533 encode!(jis0208_encode, "jis0208", "EUC-JP");
1534 decode!(jis0212_decode, "jis0212", "EUC-JP");
1535 decode!(shift_jis_decode, "shift_jis");
1536 encode!(shift_jis_encode, "shift_jis");