on aef00a3 help sign in
helix/helix-view/src/document.rs
1359 lines 49 kB history raw
1use anyhow::{anyhow, bail, Context, Error};
2use futures_util::future::BoxFuture;
3use futures_util::FutureExt;
4use helix_core::auto_pairs::AutoPairs;
5use helix_core::Range;
6use serde::de::{self, Deserialize, Deserializer};
7use serde::Serialize;
8use std::cell::Cell;
9use std::collections::HashMap;
10use std::fmt::Display;
11use std::future::Future;
12use std::path::{Path, PathBuf};
13use std::str::FromStr;
14use std::sync::Arc;
16use helix_core::{
17 encoding,
18 history::{History, UndoKind},
19 indent::{auto_detect_indent_style, IndentStyle},
20 line_ending::auto_detect_line_ending,
21 syntax::{self, LanguageConfiguration},
22 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
23 DEFAULT_LINE_ENDING,
24};
26use crate::{DocumentId, Editor, ViewId};
28/// 8kB of buffer space for encoding and decoding `Rope`s.
29const BUF_SIZE: usize = 8192;
31const DEFAULT_INDENT: IndentStyle = IndentStyle::Tabs;
33pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";
35#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
36pub enum Mode {
37 Normal = 0,
38 Select = 1,
39 Insert = 2,
42impl Display for Mode {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Mode::Normal => f.write_str("normal"),
46 Mode::Select => f.write_str("select"),
47 Mode::Insert => f.write_str("insert"),
48 }
49 }
52impl FromStr for Mode {
53 type Err = Error;
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 match s {
57 "normal" => Ok(Mode::Normal),
58 "select" => Ok(Mode::Select),
59 "insert" => Ok(Mode::Insert),
60 _ => bail!("Invalid mode '{}'", s),
61 }
62 }
65// toml deserializer doesn't seem to recognize string as enum
66impl<'de> Deserialize<'de> for Mode {
67 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68 where
69 D: Deserializer<'de>,
70 {
71 let s = String::deserialize(deserializer)?;
72 s.parse().map_err(de::Error::custom)
73 }
76impl Serialize for Mode {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: serde::Serializer,
80 {
81 serializer.collect_str(self)
82 }
85pub struct Document {
86 pub(crate) id: DocumentId,
87 text: Rope,
88 selections: HashMap<ViewId, Selection>,
90 path: Option<PathBuf>,
91 encoding: &'static encoding::Encoding,
93 /// Current editing mode.
94 pub mode: Mode,
95 pub restore_cursor: bool,
97 /// Current indent style.
98 pub indent_style: IndentStyle,
100 /// The document's default line ending.
101 pub line_ending: LineEnding,
103 syntax: Option<Syntax>,
104 /// Corresponding language scope name. Usually `source.<lang>`.
105 pub(crate) language: Option<Arc<LanguageConfiguration>>,
107 /// Pending changes since last history commit.
108 changes: ChangeSet,
109 /// State at last commit. Used for calculating reverts.
110 old_state: Option<State>,
111 /// Undo tree.
112 // It can be used as a cell where we will take it out to get some parts of the history and put
113 // it back as it separated from the edits. We could split out the parts manually but that will
114 // be more troublesome.
115 pub history: Cell<History>,
117 pub savepoint: Option<Transaction>,
119 last_saved_revision: usize,
120 version: i32, // should be usize?
121 pub(crate) modified_since_accessed: bool,
123 diagnostics: Vec<Diagnostic>,
124 language_server: Option<Arc<helix_lsp::Client>>,
127use std::{fmt, mem};
128impl fmt::Debug for Document {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.debug_struct("Document")
131 .field("id", &self.id)
132 .field("text", &self.text)
133 .field("selections", &self.selections)
134 .field("path", &self.path)
135 .field("encoding", &self.encoding)
136 .field("mode", &self.mode)
137 .field("restore_cursor", &self.restore_cursor)
138 .field("syntax", &self.syntax)
139 .field("language", &self.language)
140 .field("changes", &self.changes)
141 .field("old_state", &self.old_state)
142 // .field("history", &self.history)
143 .field("last_saved_revision", &self.last_saved_revision)
144 .field("version", &self.version)
145 .field("modified_since_accessed", &self.modified_since_accessed)
146 .field("diagnostics", &self.diagnostics)
147 // .field("language_server", &self.language_server)
148 .finish()
149 }
152// The documentation and implementation of this function should be up-to-date with
153// its sibling function, `to_writer()`.
154//
155/// Decodes a stream of bytes into UTF-8, returning a `Rope` and the
156/// encoding it was decoded as. The optional `encoding` parameter can
157/// be used to override encoding auto-detection.
158pub fn from_reader<R: std::io::Read + ?Sized>(
159 reader: &mut R,
160 encoding: Option<&'static encoding::Encoding>,
161) -> Result<(Rope, &'static encoding::Encoding), Error> {
162 // These two buffers are 8192 bytes in size each and are used as
163 // intermediaries during the decoding process. Text read into `buf`
164 // from `reader` is decoded into `buf_out` as UTF-8. Once either
165 // `buf_out` is full or the end of the reader was reached, the
166 // contents are appended to `builder`.
167 let mut buf = [0u8; BUF_SIZE];
168 let mut buf_out = [0u8; BUF_SIZE];
169 let mut builder = RopeBuilder::new();
171 // By default, the encoding of the text is auto-detected via the
172 // `chardetng` crate which requires sample data from the reader.
173 // As a manual override to this auto-detection is possible, the
174 // same data is read into `buf` to ensure symmetry in the upcoming
175 // loop.
176 let (encoding, mut decoder, mut slice, mut is_empty) = {
177 let read = reader.read(&mut buf)?;
178 let is_empty = read == 0;
179 let encoding = encoding.unwrap_or_else(|| {
180 let mut encoding_detector = chardetng::EncodingDetector::new();
181 encoding_detector.feed(&buf, is_empty);
182 encoding_detector.guess(None, true)
183 });
184 let decoder = encoding.new_decoder();
186 // If the amount of bytes read from the reader is less than
187 // `buf.len()`, it is undesirable to read the bytes afterwards.
188 let slice = &buf[..read];
189 (encoding, decoder, slice, is_empty)
190 };
192 // `RopeBuilder::append()` expects a `&str`, so this is the "real"
193 // output buffer. When decoding, the number of bytes in the output
194 // buffer will often exceed the number of bytes in the input buffer.
195 // The `result` returned by `decode_to_str()` will state whether or
196 // not that happened. The contents of `buf_str` is appended to
197 // `builder` and it is reused for the next iteration of the decoding
198 // loop.
199 //
200 // As it is possible to read less than the buffer's maximum from `read()`
201 // even when the end of the reader has yet to be reached, the end of
202 // the reader is determined only when a `read()` call returns `0`.
203 //
204 // SAFETY: `buf_out` is a zero-initialized array, thus it will always
205 // contain valid UTF-8.
206 let buf_str = unsafe { std::str::from_utf8_unchecked_mut(&mut buf_out[..]) };
207 let mut total_written = 0usize;
208 loop {
209 let mut total_read = 0usize;
211 // An inner loop is necessary as it is possible that the input buffer
212 // may not be completely decoded on the first `decode_to_str()` call
213 // which would happen in cases where the output buffer is filled to
214 // capacity.
215 loop {
216 let (result, read, written, ..) = decoder.decode_to_str(
217 &slice[total_read..],
218 &mut buf_str[total_written..],
219 is_empty,
220 );
222 // These variables act as the read and write cursors of `buf` and `buf_str` respectively.
223 // They are necessary in case the output buffer fills before decoding of the entire input
224 // loop is complete. Otherwise, the loop would endlessly iterate over the same `buf` and
225 // the data inside the output buffer would be overwritten.
226 total_read += read;
227 total_written += written;
228 match result {
229 encoding::CoderResult::InputEmpty => {
230 debug_assert_eq!(slice.len(), total_read);
231 break;
232 }
233 encoding::CoderResult::OutputFull => {
234 debug_assert!(slice.len() > total_read);
235 builder.append(&buf_str[..total_written]);
236 total_written = 0;
237 }
238 }
239 }
240 // Once the end of the stream is reached, the output buffer is
241 // flushed and the loop terminates.
242 if is_empty {
243 debug_assert_eq!(reader.read(&mut buf)?, 0);
244 builder.append(&buf_str[..total_written]);
245 break;
246 }
248 // Once the previous input has been processed and decoded, the next set of
249 // data is fetched from the reader. The end of the reader is determined to
250 // be when exactly `0` bytes were read from the reader, as per the invariants
251 // of the `Read` trait.
252 let read = reader.read(&mut buf)?;
253 slice = &buf[..read];
254 is_empty = read == 0;
255 }
256 let rope = builder.finish();
257 Ok((rope, encoding))
260// The documentation and implementation of this function should be up-to-date with
261// its sibling function, `from_reader()`.
262//
263/// Encodes the text inside `rope` into the given `encoding` and writes the
264/// encoded output into `writer.` As a `Rope` can only contain valid UTF-8,
265/// replacement characters may appear in the encoded text.
266pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
267 writer: &'a mut W,
268 encoding: &'static encoding::Encoding,
269 rope: &'a Rope,
270) -> Result<(), Error> {
271 // Text inside a `Rope` is stored as non-contiguous blocks of data called
272 // chunks. The absolute size of each chunk is unknown, thus it is impossible
273 // to predict the end of the chunk iterator ahead of time. Instead, it is
274 // determined by filtering the iterator to remove all empty chunks and then
275 // appending an empty chunk to it. This is valuable for detecting when all
276 // chunks in the `Rope` have been iterated over in the subsequent loop.
277 let iter = rope
278 .chunks()
279 .filter(|c| !c.is_empty())
280 .chain(std::iter::once(""));
281 let mut buf = [0u8; BUF_SIZE];
282 let mut encoder = encoding.new_encoder();
283 let mut total_written = 0usize;
284 for chunk in iter {
285 let is_empty = chunk.is_empty();
286 let mut total_read = 0usize;
288 // An inner loop is necessary as it is possible that the input buffer
289 // may not be completely encoded on the first `encode_from_utf8()` call
290 // which would happen in cases where the output buffer is filled to
291 // capacity.
292 loop {
293 let (result, read, written, ..) =
294 encoder.encode_from_utf8(&chunk[total_read..], &mut buf[total_written..], is_empty);
296 // These variables act as the read and write cursors of `chunk` and `buf` respectively.
297 // They are necessary in case the output buffer fills before encoding of the entire input
298 // loop is complete. Otherwise, the loop would endlessly iterate over the same `chunk` and
299 // the data inside the output buffer would be overwritten.
300 total_read += read;
301 total_written += written;
302 match result {
303 encoding::CoderResult::InputEmpty => {
304 debug_assert_eq!(chunk.len(), total_read);
305 debug_assert!(buf.len() >= total_written);
306 break;
307 }
308 encoding::CoderResult::OutputFull => {
309 debug_assert!(chunk.len() > total_read);
310 writer.write_all(&buf[..total_written]).await?;
311 total_written = 0;
312 }
313 }
314 }
316 // Once the end of the iterator is reached, the output buffer is
317 // flushed and the outer loop terminates.
318 if is_empty {
319 writer.write_all(&buf[..total_written]).await?;
320 writer.flush().await?;
321 break;
322 }
323 }
324 Ok(())
327fn take_with<T, F>(mut_ref: &mut T, f: F)
328where
329 T: Default,
330 F: FnOnce(T) -> T,
332 *mut_ref = f(mem::take(mut_ref));
335use helix_lsp::lsp;
336use url::Url;
338impl Document {
339 pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
340 let encoding = encoding.unwrap_or(encoding::UTF_8);
341 let changes = ChangeSet::new(&text);
342 let old_state = None;
344 Self {
345 id: DocumentId::default(),
346 path: None,
347 encoding,
348 text,
349 selections: HashMap::default(),
350 indent_style: DEFAULT_INDENT,
351 line_ending: DEFAULT_LINE_ENDING,
352 mode: Mode::Normal,
353 restore_cursor: false,
354 syntax: None,
355 language: None,
356 changes,
357 old_state,
358 diagnostics: Vec::new(),
359 version: 0,
360 history: Cell::new(History::default()),
361 savepoint: None,
362 last_saved_revision: 0,
363 modified_since_accessed: false,
364 language_server: None,
365 }
366 }
368 // TODO: async fn?
369 /// Create a new document from `path`. Encoding is auto-detected, but it can be manually
370 /// overwritten with the `encoding` parameter.
371 pub fn open(
372 path: &Path,
373 encoding: Option<&'static encoding::Encoding>,
374 config_loader: Option<Arc<syntax::Loader>>,
375 ) -> Result<Self, Error> {
376 // Open the file if it exists, otherwise assume it is a new file (and thus empty).
377 let (rope, encoding) = if path.exists() {
378 let mut file =
379 std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
380 from_reader(&mut file, encoding)?
381 } else {
382 let encoding = encoding.unwrap_or(encoding::UTF_8);
383 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
384 };
386 let mut doc = Self::from(rope, Some(encoding));
388 // set the path and try detecting the language
389 doc.set_path(Some(path))?;
390 if let Some(loader) = config_loader {
391 doc.detect_language(loader);
392 }
394 doc.detect_indent_and_line_ending();
396 Ok(doc)
397 }
399 /// The same as [`format`], but only returns formatting changes if auto-formatting
400 /// is configured.
401 pub fn auto_format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
402 if self.language_config()?.auto_format {
403 self.format()
404 } else {
405 None
406 }
407 }
409 /// If supported, returns the changes that should be applied to this document in order
410 /// to format it nicely.
411 // We can't use anyhow::Result here since the output of the future has to be
412 // clonable to be used as shared future. So use a custom error type.
413 pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
414 if let Some(formatter) = self.language_config().and_then(|c| c.formatter.clone()) {
415 use std::process::Stdio;
416 let text = self.text().clone();
417 let mut process = tokio::process::Command::new(&formatter.command);
418 process
419 .args(&formatter.args)
420 .stdin(Stdio::piped())
421 .stdout(Stdio::piped())
422 .stderr(Stdio::piped());
424 let formatting_future = async move {
425 let mut process = process
426 .spawn()
427 .map_err(|e| FormatterError::SpawningFailed {
428 command: formatter.command.clone(),
429 error: e.kind(),
430 })?;
431 {
432 let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
433 to_writer(&mut stdin, encoding::UTF_8, &text)
434 .await
435 .map_err(|_| FormatterError::BrokenStdin)?;
436 }
438 let output = process
439 .wait_with_output()
440 .await
441 .map_err(|_| FormatterError::WaitForOutputFailed)?;
443 if !output.stderr.is_empty() {
444 return Err(FormatterError::Stderr(
445 String::from_utf8_lossy(&output.stderr).to_string(),
446 ));
447 }
449 if !output.status.success() {
450 return Err(FormatterError::NonZeroExitStatus);
451 }
453 let str = String::from_utf8(output.stdout)
454 .map_err(|_| FormatterError::InvalidUtf8Output)?;
456 Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
457 };
458 return Some(formatting_future.boxed());
459 };
461 let language_server = self.language_server()?;
462 let text = self.text.clone();
463 let offset_encoding = language_server.offset_encoding();
465 let request = language_server.text_document_formatting(
466 self.identifier(),
467 lsp::FormattingOptions {
468 tab_size: self.tab_width() as u32,
469 insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)),
470 ..Default::default()
471 },
472 None,
473 )?;
475 let fut = async move {
476 let edits = request.await.unwrap_or_else(|e| {
477 log::warn!("LSP formatting failed: {}", e);
478 Default::default()
479 });
480 Ok(helix_lsp::util::generate_transaction_from_edits(
481 &text,
482 edits,
483 offset_encoding,
484 ))
485 };
486 Some(fut.boxed())
487 }
489 pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
490 self.save_impl::<futures_util::future::Ready<_>>(None, force)
491 }
493 pub fn format_and_save(
494 &mut self,
495 formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
496 force: bool,
497 ) -> impl Future<Output = anyhow::Result<()>> {
498 self.save_impl(formatting, force)
499 }
501 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
502 // or is that handled by the OS/async layer
503 /// The `Document`'s text is encoded according to its encoding and written to the file located
504 /// at its `path()`.
505 ///
506 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
507 fn save_impl<F: Future<Output = Result<Transaction, FormatterError>>>(
508 &mut self,
509 formatting: Option<F>,
510 force: bool,
511 ) -> impl Future<Output = Result<(), anyhow::Error>> {
512 // we clone and move text + path into the future so that we asynchronously save the current
513 // state without blocking any further edits.
515 let mut text = self.text().clone();
516 let path = self.path.clone().expect("Can't save with no path set!");
517 let identifier = self.identifier();
519 let language_server = self.language_server.clone();
521 // mark changes up to now as saved
522 self.reset_modified();
524 let encoding = self.encoding;
526 // We encode the file according to the `Document`'s encoding.
527 async move {
528 use tokio::fs::File;
529 if let Some(parent) = path.parent() {
530 // TODO: display a prompt asking the user if the directories should be created
531 if !parent.exists() {
532 if force {
533 std::fs::DirBuilder::new().recursive(true).create(parent)?;
534 } else {
535 bail!("can't save file, parent directory does not exist");
536 }
537 }
538 }
540 if let Some(fmt) = formatting {
541 let transaction = fmt.await?;
542 let success = transaction.changes().apply(&mut text);
543 if !success {
544 // This shouldn't happen, because the transaction changes were generated
545 // from the same text we're saving.
546 log::error!("failed to apply format changes before saving");
547 }
548 }
550 let mut file = File::create(path).await?;
551 to_writer(&mut file, encoding, &text).await?;
553 if let Some(language_server) = language_server {
554 if !language_server.is_initialized() {
555 return Ok(());
556 }
557 if let Some(notification) =
558 language_server.text_document_did_save(identifier, &text)
559 {
560 notification.await?;
561 }
562 }
564 Ok(())
565 }
566 }
568 /// Detect the programming language based on the file type.
569 pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
570 if let Some(path) = &self.path {
571 let language_config = config_loader
572 .language_config_for_file_name(path)
573 .or_else(|| config_loader.language_config_for_shebang(self.text()));
574 self.set_language(language_config, Some(config_loader));
575 }
576 }
578 /// Detect the indentation used in the file, or otherwise defaults to the language indentation
579 /// configured in `languages.toml`, with a fallback to tabs if it isn't specified. Line ending
580 /// is likewise auto-detected, and will fallback to the default OS line ending.
581 pub fn detect_indent_and_line_ending(&mut self) {
582 self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
583 self.language_config()
584 .and_then(|config| config.indent.as_ref())
585 .map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
586 });
587 self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
588 }
590 /// Reload the document from its path.
591 pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
592 let encoding = &self.encoding;
593 let path = self.path().filter(|path| path.exists());
595 // If there is no path or the path no longer exists.
596 if path.is_none() {
597 bail!("can't find file to reload from");
598 }
600 let mut file = std::fs::File::open(path.unwrap())?;
601 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
603 // Calculate the difference between the buffer and source text, and apply it.
604 // This is not considered a modification of the contents of the file regardless
605 // of the encoding.
606 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
607 self.apply(&transaction, view_id);
608 self.append_changes_to_history(view_id);
609 self.reset_modified();
611 self.detect_indent_and_line_ending();
613 Ok(())
614 }
616 /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
617 pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
618 self.encoding = encoding::Encoding::for_label(label.as_bytes())
619 .ok_or_else(|| anyhow!("unknown encoding"))?;
620 Ok(())
621 }
623 /// Returns the [`Document`]'s current encoding.
624 pub fn encoding(&self) -> &'static encoding::Encoding {
625 self.encoding
626 }
628 pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> {
629 let path = path
630 .map(helix_core::path::get_canonicalized_path)
631 .transpose()?;
633 // if parent doesn't exist we still want to open the document
634 // and error out when document is saved
635 self.path = path;
637 Ok(())
638 }
640 /// Set the programming language for the file and load associated data (e.g. highlighting)
641 /// if it exists.
642 pub fn set_language(
643 &mut self,
644 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
645 loader: Option<Arc<helix_core::syntax::Loader>>,
646 ) {
647 if let (Some(language_config), Some(loader)) = (language_config, loader) {
648 if let Some(highlight_config) = language_config.highlight_config(&loader.scopes()) {
649 let syntax = Syntax::new(&self.text, highlight_config, loader);
650 self.syntax = Some(syntax);
651 }
653 self.language = Some(language_config);
654 } else {
655 self.syntax = None;
656 self.language = None;
657 };
658 }
660 /// Set the programming language for the file if you know the name (scope) but don't have the
661 /// [`syntax::LanguageConfiguration`] for it.
662 pub fn set_language2(&mut self, scope: &str, config_loader: Arc<syntax::Loader>) {
663 let language_config = config_loader.language_config_for_scope(scope);
665 self.set_language(language_config, Some(config_loader));
666 }
668 /// Set the programming language for the file if you know the language but don't have the
669 /// [`syntax::LanguageConfiguration`] for it.
670 pub fn set_language_by_language_id(
671 &mut self,
672 language_id: &str,
673 config_loader: Arc<syntax::Loader>,
674 ) {
675 let language_config = config_loader.language_config_for_language_id(language_id);
676 self.set_language(language_config, Some(config_loader));
677 }
679 /// Set the LSP.
680 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
681 self.language_server = language_server;
682 }
684 /// Select text within the [`Document`].
685 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
686 // TODO: use a transaction?
687 self.selections
688 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
689 }
691 /// Find the origin selection of the text in a document, i.e. where
692 /// a single cursor would go if it were on the first grapheme. If
693 /// the text is empty, returns (0, 0).
694 pub fn origin(&self) -> Range {
695 if self.text().len_chars() == 0 {
696 return Range::new(0, 0);
697 }
699 Range::new(0, 1).grapheme_aligned(self.text().slice(..))
700 }
702 /// Reset the view's selection on this document to the
703 /// [origin](Document::origin) cursor.
704 pub fn reset_selection(&mut self, view_id: ViewId) {
705 let origin = self.origin();
706 self.set_selection(view_id, Selection::single(origin.anchor, origin.head));
707 }
709 /// Initializes a new selection for the given view if it does not
710 /// already have one.
711 pub fn ensure_view_init(&mut self, view_id: ViewId) {
712 if self.selections.get(&view_id).is_none() {
713 self.reset_selection(view_id);
714 }
715 }
717 /// Remove a view's selection from this document.
718 pub fn remove_view(&mut self, view_id: ViewId) {
719 self.selections.remove(&view_id);
720 }
722 /// Apply a [`Transaction`] to the [`Document`] to change its text.
723 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
724 let old_doc = self.text().clone();
726 let success = transaction.changes().apply(&mut self.text);
728 if success {
729 for selection in self.selections.values_mut() {
730 *selection = selection
731 .clone()
732 // Map through changes
733 .map(transaction.changes())
734 // Ensure all selections across all views still adhere to invariants.
735 .ensure_invariants(self.text.slice(..));
736 }
738 // if specified, the current selection should instead be replaced by transaction.selection
739 if let Some(selection) = transaction.selection() {
740 self.selections.insert(
741 view_id,
742 selection.clone().ensure_invariants(self.text.slice(..)),
743 );
744 }
746 self.modified_since_accessed = true;
747 }
749 if !transaction.changes().is_empty() {
750 self.version += 1;
752 // generate revert to savepoint
753 if self.savepoint.is_some() {
754 take_with(&mut self.savepoint, |prev_revert| {
755 let revert = transaction.invert(&old_doc);
756 Some(revert.compose(prev_revert.unwrap()))
757 });
758 }
760 // update tree-sitter syntax tree
761 if let Some(syntax) = &mut self.syntax {
762 // TODO: no unwrap
763 syntax
764 .update(&old_doc, &self.text, transaction.changes())
765 .unwrap();
766 }
768 // map state.diagnostics over changes::map_pos too
769 for diagnostic in &mut self.diagnostics {
770 use helix_core::Assoc;
771 let changes = transaction.changes();
772 diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
773 diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
774 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
775 }
777 // emit lsp notification
778 if let Some(language_server) = self.language_server() {
779 let notify = language_server.text_document_did_change(
780 self.versioned_identifier(),
781 &old_doc,
782 self.text(),
783 transaction.changes(),
784 );
786 if let Some(notify) = notify {
787 tokio::spawn(notify);
788 }
789 }
790 }
791 success
792 }
794 /// Apply a [`Transaction`] to the [`Document`] to change its text.
795 pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
796 // store the state just before any changes are made. This allows us to undo to the
797 // state just before a transaction was applied.
798 if self.changes.is_empty() && !transaction.changes().is_empty() {
799 self.old_state = Some(State {
800 doc: self.text.clone(),
801 selection: self.selection(view_id).clone(),
802 });
803 }
805 let success = self.apply_impl(transaction, view_id);
807 if !transaction.changes().is_empty() {
808 // Compose this transaction with the previous one
809 take_with(&mut self.changes, |changes| {
810 changes.compose(transaction.changes().clone())
811 });
812 }
813 success
814 }
816 fn undo_redo_impl(&mut self, view_id: ViewId, undo: bool) -> bool {
817 let mut history = self.history.take();
818 let txn = if undo { history.undo() } else { history.redo() };
819 let success = if let Some(txn) = txn {
820 self.apply_impl(txn, view_id)
821 } else {
822 false
823 };
824 self.history.set(history);
826 if success {
827 // reset changeset to fix len
828 self.changes = ChangeSet::new(self.text());
829 }
830 success
831 }
833 /// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
834 pub fn undo(&mut self, view_id: ViewId) -> bool {
835 self.undo_redo_impl(view_id, true)
836 }
838 /// Redo the last modification to the [`Document`]. Returns whether the redo was successful.
839 pub fn redo(&mut self, view_id: ViewId) -> bool {
840 self.undo_redo_impl(view_id, false)
841 }
843 pub fn savepoint(&mut self) {
844 self.savepoint = Some(Transaction::new(self.text()));
845 }
847 pub fn restore(&mut self, view_id: ViewId) {
848 if let Some(revert) = self.savepoint.take() {
849 self.apply(&revert, view_id);
850 }
851 }
853 fn earlier_later_impl(&mut self, view_id: ViewId, uk: UndoKind, earlier: bool) -> bool {
854 let txns = if earlier {
855 self.history.get_mut().earlier(uk)
856 } else {
857 self.history.get_mut().later(uk)
858 };
859 let mut success = false;
860 for txn in txns {
861 if self.apply_impl(&txn, view_id) {
862 success = true;
863 }
864 }
865 if success {
866 // reset changeset to fix len
867 self.changes = ChangeSet::new(self.text());
868 }
869 success
870 }
872 /// Undo modifications to the [`Document`] according to `uk`.
873 pub fn earlier(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
874 self.earlier_later_impl(view_id, uk, true)
875 }
877 /// Redo modifications to the [`Document`] according to `uk`.
878 pub fn later(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
879 self.earlier_later_impl(view_id, uk, false)
880 }
882 /// Commit pending changes to history
883 pub fn append_changes_to_history(&mut self, view_id: ViewId) {
884 if self.changes.is_empty() {
885 return;
886 }
888 let new_changeset = ChangeSet::new(self.text());
889 let changes = std::mem::replace(&mut self.changes, new_changeset);
890 // Instead of doing this messy merge we could always commit, and based on transaction
891 // annotations either add a new layer or compose into the previous one.
892 let transaction =
893 Transaction::from(changes).with_selection(self.selection(view_id).clone());
895 // HAXX: we need to reconstruct the state as it was before the changes..
896 let old_state = self.old_state.take().expect("no old_state available");
898 let mut history = self.history.take();
899 history.commit_revision(&transaction, &old_state);
900 self.history.set(history);
901 }
903 pub fn id(&self) -> DocumentId {
904 self.id
905 }
907 /// If there are unsaved modifications.
908 pub fn is_modified(&self) -> bool {
909 let history = self.history.take();
910 let current_revision = history.current_revision();
911 self.history.set(history);
912 current_revision != self.last_saved_revision || !self.changes.is_empty()
913 }
915 /// Save modifications to history, and so [`Self::is_modified`] will return false.
916 pub fn reset_modified(&mut self) {
917 let history = self.history.take();
918 let current_revision = history.current_revision();
919 self.history.set(history);
920 self.last_saved_revision = current_revision;
921 }
923 /// Current editing mode for the [`Document`].
924 pub fn mode(&self) -> Mode {
925 self.mode
926 }
928 /// Corresponding language scope name. Usually `source.<lang>`.
929 pub fn language(&self) -> Option<&str> {
930 self.language
931 .as_ref()
932 .map(|language| language.scope.as_str())
933 }
935 /// Language ID for the document. Either the `language-id` from the
936 /// `language-server` configuration, or the document language if no
937 /// `language-id` has been specified.
938 pub fn language_id(&self) -> Option<&str> {
939 self.language_config()?
940 .language_server
941 .as_ref()?
942 .language_id
943 .as_deref()
944 .or_else(|| Some(self.language()?.rsplit_once('.')?.1))
945 }
947 /// Corresponding [`LanguageConfiguration`].
948 pub fn language_config(&self) -> Option<&LanguageConfiguration> {
949 self.language.as_deref()
950 }
952 /// Current document version, incremented at each change.
953 pub fn version(&self) -> i32 {
954 self.version
955 }
957 /// Language server if it has been initialized.
958 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
959 let server = self.language_server.as_deref()?;
960 server.is_initialized().then(|| server)
961 }
963 #[inline]
964 /// Tree-sitter AST tree
965 pub fn syntax(&self) -> Option<&Syntax> {
966 self.syntax.as_ref()
967 }
969 /// Tab size in columns.
970 pub fn tab_width(&self) -> usize {
971 self.language_config()
972 .and_then(|config| config.indent.as_ref())
973 .map_or(4, |config| config.tab_width) // fallback to 4 columns
974 }
976 /// Returns a string containing a single level of indentation.
977 ///
978 /// TODO: we might not need this function anymore, since the information
979 /// is conveniently available in `Document::indent_style` now.
980 pub fn indent_unit(&self) -> &'static str {
981 self.indent_style.as_str()
982 }
984 pub fn changes(&self) -> &ChangeSet {
985 &self.changes
986 }
988 #[inline]
989 /// File path on disk.
990 pub fn path(&self) -> Option<&PathBuf> {
991 self.path.as_ref()
992 }
994 /// File path as a URL.
995 pub fn url(&self) -> Option<Url> {
996 Url::from_file_path(self.path()?).ok()
997 }
999 #[inline]
1000 pub fn text(&self) -> &Rope {
1001 &self.text
1004 #[inline]
1005 pub fn selection(&self, view_id: ViewId) -> &Selection {
1006 &self.selections[&view_id]
1009 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
1010 &self.selections
1013 pub fn relative_path(&self) -> Option<PathBuf> {
1014 self.path
1015 .as_deref()
1016 .map(helix_core::path::get_relative_path)
1019 // transact(Fn) ?
1021 // -- LSP methods
1023 #[inline]
1024 pub fn identifier(&self) -> lsp::TextDocumentIdentifier {
1025 lsp::TextDocumentIdentifier::new(self.url().unwrap())
1028 pub fn versioned_identifier(&self) -> lsp::VersionedTextDocumentIdentifier {
1029 lsp::VersionedTextDocumentIdentifier::new(self.url().unwrap(), self.version)
1032 pub fn position(
1033 &self,
1034 view_id: ViewId,
1035 offset_encoding: helix_lsp::OffsetEncoding,
1036 ) -> lsp::Position {
1037 let text = self.text();
1039 helix_lsp::util::pos_to_lsp_pos(
1040 text,
1041 self.selection(view_id).primary().cursor(text.slice(..)),
1042 offset_encoding,
1046 #[inline]
1047 pub fn diagnostics(&self) -> &[Diagnostic] {
1048 &self.diagnostics
1051 pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
1052 self.diagnostics = diagnostics;
1053 self.diagnostics
1054 .sort_unstable_by_key(|diagnostic| diagnostic.range);
1057 /// Get the document's auto pairs. If the document has a recognized
1058 /// language config with auto pairs configured, returns that;
1059 /// otherwise, falls back to the global auto pairs config. If the global
1060 /// config is false, then ignore language settings.
1061 pub fn auto_pairs<'a>(&'a self, editor: &'a Editor) -> Option<&'a AutoPairs> {
1062 let global_config = (editor.auto_pairs).as_ref();
1064 // NOTE: If the user specifies the global auto pairs config as false, then
1065 // we want to disable it globally regardless of language settings
1066 #[allow(clippy::question_mark)]
1068 if global_config.is_none() {
1069 return None;
1073 match &self.language {
1074 Some(lang) => lang.as_ref().auto_pairs.as_ref().or(global_config),
1075 None => global_config,
1080impl Default for Document {
1081 fn default() -> Self {
1082 let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
1083 Self::from(text, None)
1087#[derive(Clone, Debug)]
1088pub enum FormatterError {
1089 SpawningFailed {
1090 command: String,
1091 error: std::io::ErrorKind,
1092 },
1093 BrokenStdin,
1094 WaitForOutputFailed,
1095 Stderr(String),
1096 InvalidUtf8Output,
1097 DiskReloadError(String),
1098 NonZeroExitStatus,
1101impl std::error::Error for FormatterError {}
1103impl Display for FormatterError {
1104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1105 match self {
1106 Self::SpawningFailed { command, error } => {
1107 write!(f, "Failed to spawn formatter {}: {:?}", command, error)
1109 Self::BrokenStdin => write!(f, "Could not write to formatter stdin"),
1110 Self::WaitForOutputFailed => write!(f, "Waiting for formatter output failed"),
1111 Self::Stderr(output) => write!(f, "Formatter error: {}", output),
1112 Self::InvalidUtf8Output => write!(f, "Invalid UTF-8 formatter output"),
1113 Self::DiskReloadError(error) => write!(f, "Error reloading file from disk: {}", error),
1114 Self::NonZeroExitStatus => write!(f, "Formatter exited with non zero exit status:"),
1119#[cfg(test)]
1120mod test {
1121 use super::*;
1123 #[test]
1124 fn changeset_to_changes_ignore_line_endings() {
1125 use helix_lsp::{lsp, Client, OffsetEncoding};
1126 let text = Rope::from("hello\r\nworld");
1127 let mut doc = Document::from(text, None);
1128 let view = ViewId::default();
1129 doc.set_selection(view, Selection::single(0, 0));
1131 let transaction =
1132 Transaction::change(doc.text(), vec![(5, 7, Some("\n".into()))].into_iter());
1133 let old_doc = doc.text().clone();
1134 doc.apply(&transaction, view);
1135 let changes = Client::changeset_to_changes(
1136 &old_doc,
1137 doc.text(),
1138 transaction.changes(),
1139 OffsetEncoding::Utf8,
1140 );
1142 assert_eq!(doc.text(), "hello\nworld");
1144 assert_eq!(
1145 changes,
1146 &[lsp::TextDocumentContentChangeEvent {
1147 range: Some(lsp::Range::new(
1148 lsp::Position::new(0, 5),
1149 lsp::Position::new(1, 0)
1150 )),
1151 text: "\n".into(),
1152 range_length: None,
1153 }]
1154 );
1157 #[test]
1158 fn changeset_to_changes() {
1159 use helix_lsp::{lsp, Client, OffsetEncoding};
1160 let text = Rope::from("hello");
1161 let mut doc = Document::from(text, None);
1162 let view = ViewId::default();
1163 doc.set_selection(view, Selection::single(5, 5));
1165 // insert
1167 let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
1168 let old_doc = doc.text().clone();
1169 doc.apply(&transaction, view);
1170 let changes = Client::changeset_to_changes(
1171 &old_doc,
1172 doc.text(),
1173 transaction.changes(),
1174 OffsetEncoding::Utf8,
1175 );
1177 assert_eq!(
1178 changes,
1179 &[lsp::TextDocumentContentChangeEvent {
1180 range: Some(lsp::Range::new(
1181 lsp::Position::new(0, 5),
1182 lsp::Position::new(0, 5)
1183 )),
1184 text: " world".into(),
1185 range_length: None,
1186 }]
1187 );
1189 // delete
1191 let transaction = transaction.invert(&old_doc);
1192 let old_doc = doc.text().clone();
1193 doc.apply(&transaction, view);
1194 let changes = Client::changeset_to_changes(
1195 &old_doc,
1196 doc.text(),
1197 transaction.changes(),
1198 OffsetEncoding::Utf8,
1199 );
1201 // line: 0-based.
1202 // col: 0-based, gaps between chars.
1203 // 0 1 2 3 4 5 6 7 8 9 0 1
1204 // |h|e|l|l|o| |w|o|r|l|d|
1205 // -------------
1206 // (0, 5)-(0, 11)
1207 assert_eq!(
1208 changes,
1209 &[lsp::TextDocumentContentChangeEvent {
1210 range: Some(lsp::Range::new(
1211 lsp::Position::new(0, 5),
1212 lsp::Position::new(0, 11)
1213 )),
1214 text: "".into(),
1215 range_length: None,
1216 }]
1217 );
1219 // replace
1221 // also tests that changes are layered, positions depend on previous changes.
1223 doc.set_selection(view, Selection::single(0, 5));
1224 let transaction = Transaction::change(
1225 doc.text(),
1226 vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
1227 );
1228 // aeilou
1229 let old_doc = doc.text().clone();
1230 doc.apply(&transaction, view);
1231 let changes = Client::changeset_to_changes(
1232 &old_doc,
1233 doc.text(),
1234 transaction.changes(),
1235 OffsetEncoding::Utf8,
1236 );
1238 assert_eq!(
1239 changes,
1240 &[
1241 // 0 1 2 3 4 5
1242 // |h|e|l|l|o|
1243 // ----
1244 //
1245 // aeillo
1246 lsp::TextDocumentContentChangeEvent {
1247 range: Some(lsp::Range::new(
1248 lsp::Position::new(0, 0),
1249 lsp::Position::new(0, 2)
1250 )),
1251 text: "aei".into(),
1252 range_length: None,
1253 },
1254 // 0 1 2 3 4 5 6
1255 // |a|e|i|l|l|o|
1256 // -----
1257 //
1258 // aeilou
1259 lsp::TextDocumentContentChangeEvent {
1260 range: Some(lsp::Range::new(
1261 lsp::Position::new(0, 4),
1262 lsp::Position::new(0, 6)
1263 )),
1264 text: "ou".into(),
1265 range_length: None,
1268 );
1271 #[test]
1272 fn test_line_ending() {
1273 assert_eq!(
1274 Document::default().text().to_string(),
1275 DEFAULT_LINE_ENDING.as_str()
1276 );
1279 macro_rules! test_decode {
1280 ($label:expr, $label_override:expr) => {
1281 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1282 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1283 let path = base_path.join(format!("{}_in.txt", $label));
1284 let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
1285 assert!(path.exists());
1286 assert!(ref_path.exists());
1288 let mut file = std::fs::File::open(path).unwrap();
1289 let text = from_reader(&mut file, Some(encoding))
1290 .unwrap()
1291 .0
1292 .to_string();
1293 let expectation = std::fs::read_to_string(ref_path).unwrap();
1294 assert_eq!(text[..], expectation[..]);
1295 };
1298 macro_rules! test_encode {
1299 ($label:expr, $label_override:expr) => {
1300 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1301 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1302 let path = base_path.join(format!("{}_out.txt", $label));
1303 let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
1304 assert!(path.exists());
1305 assert!(ref_path.exists());
1307 let text = Rope::from_str(&std::fs::read_to_string(path).unwrap());
1308 let mut buf: Vec<u8> = Vec::new();
1309 helix_lsp::block_on(to_writer(&mut buf, encoding, &text)).unwrap();
1311 let expectation = std::fs::read(ref_path).unwrap();
1312 assert_eq!(buf, expectation);
1313 };
1316 macro_rules! test_decode_fn {
1317 ($name:ident, $label:expr, $label_override:expr) => {
1318 #[test]
1319 fn $name() {
1320 test_decode!($label, $label_override);
1322 };
1323 ($name:ident, $label:expr) => {
1324 #[test]
1325 fn $name() {
1326 test_decode!($label, $label);
1328 };
1331 macro_rules! test_encode_fn {
1332 ($name:ident, $label:expr, $label_override:expr) => {
1333 #[test]
1334 fn $name() {
1335 test_encode!($label, $label_override);
1337 };
1338 ($name:ident, $label:expr) => {
1339 #[test]
1340 fn $name() {
1341 test_encode!($label, $label);
1343 };
1346 test_decode_fn!(test_big5_decode, "big5");
1347 test_encode_fn!(test_big5_encode, "big5");
1348 test_decode_fn!(test_euc_kr_decode, "euc_kr", "EUC-KR");
1349 test_encode_fn!(test_euc_kr_encode, "euc_kr", "EUC-KR");
1350 test_decode_fn!(test_gb18030_decode, "gb18030");
1351 test_encode_fn!(test_gb18030_encode, "gb18030");
1352 test_decode_fn!(test_iso_2022_jp_decode, "iso_2022_jp", "ISO-2022-JP");
1353 test_encode_fn!(test_iso_2022_jp_encode, "iso_2022_jp", "ISO-2022-JP");
1354 test_decode_fn!(test_jis0208_decode, "jis0208", "EUC-JP");
1355 test_encode_fn!(test_jis0208_encode, "jis0208", "EUC-JP");
1356 test_decode_fn!(test_jis0212_decode, "jis0212", "EUC-JP");
1357 test_decode_fn!(test_shift_jis_decode, "shift_jis");
1358 test_encode_fn!(test_shift_jis_encode, "shift_jis");