on 6c11708 help sign in
helix/helix-view/src/document.rs
1225 lines 43 kB history raw
1use anyhow::{anyhow, Context, Error};
2use serde::de::{self, Deserialize, Deserializer};
3use serde::Serialize;
4use std::cell::Cell;
5use std::collections::HashMap;
6use std::fmt::Display;
7use std::future::Future;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::sync::Arc;
12use helix_core::{
13 encoding,
14 history::History,
15 indent::{auto_detect_indent_style, IndentStyle},
16 line_ending::auto_detect_line_ending,
17 syntax::{self, LanguageConfiguration},
18 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
19 DEFAULT_LINE_ENDING,
20};
21use helix_lsp::util::LspFormatting;
23use crate::{DocumentId, ViewId};
25/// 8kB of buffer space for encoding and decoding `Rope`s.
26const BUF_SIZE: usize = 8192;
28const DEFAULT_INDENT: IndentStyle = IndentStyle::Spaces(4);
30pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";
32#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
33pub enum Mode {
34 Normal = 0,
35 Select = 1,
36 Insert = 2,
39impl Display for Mode {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Mode::Normal => f.write_str("normal"),
43 Mode::Select => f.write_str("select"),
44 Mode::Insert => f.write_str("insert"),
45 }
46 }
49impl FromStr for Mode {
50 type Err = Error;
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 match s {
54 "normal" => Ok(Mode::Normal),
55 "select" => Ok(Mode::Select),
56 "insert" => Ok(Mode::Insert),
57 _ => Err(anyhow!("Invalid mode '{}'", s)),
58 }
59 }
62// toml deserializer doesn't seem to recognize string as enum
63impl<'de> Deserialize<'de> for Mode {
64 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
65 where
66 D: Deserializer<'de>,
67 {
68 let s = String::deserialize(deserializer)?;
69 s.parse().map_err(de::Error::custom)
70 }
73impl Serialize for Mode {
74 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75 where
76 S: serde::Serializer,
77 {
78 serializer.collect_str(self)
79 }
82pub struct Document {
83 pub(crate) id: DocumentId,
84 text: Rope,
85 pub(crate) selections: HashMap<ViewId, Selection>,
87 path: Option<PathBuf>,
88 encoding: &'static encoding::Encoding,
90 /// Current editing mode.
91 pub mode: Mode,
92 pub restore_cursor: bool,
94 /// Current indent style.
95 pub indent_style: IndentStyle,
97 /// The document's default line ending.
98 pub line_ending: LineEnding,
100 syntax: Option<Syntax>,
101 // /// Corresponding language scope name. Usually `source.<lang>`.
102 pub(crate) language: Option<Arc<LanguageConfiguration>>,
104 /// Pending changes since last history commit.
105 changes: ChangeSet,
106 /// State at last commit. Used for calculating reverts.
107 old_state: Option<State>,
108 /// Undo tree.
109 // It can be used as a cell where we will take it out to get some parts of the history and put
110 // it back as it separated from the edits. We could split out the parts manually but that will
111 // be more troublesome.
112 pub history: Cell<History>,
114 pub savepoint: Option<Transaction>,
116 last_saved_revision: usize,
117 version: i32, // should be usize?
118 pub(crate) modified_since_accessed: bool,
120 diagnostics: Vec<Diagnostic>,
121 language_server: Option<Arc<helix_lsp::Client>>,
124use std::{fmt, mem};
125impl fmt::Debug for Document {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.debug_struct("Document")
128 .field("id", &self.id)
129 .field("text", &self.text)
130 .field("selections", &self.selections)
131 .field("path", &self.path)
132 .field("encoding", &self.encoding)
133 .field("mode", &self.mode)
134 .field("restore_cursor", &self.restore_cursor)
135 .field("syntax", &self.syntax)
136 .field("language", &self.language)
137 .field("changes", &self.changes)
138 .field("old_state", &self.old_state)
139 // .field("history", &self.history)
140 .field("last_saved_revision", &self.last_saved_revision)
141 .field("version", &self.version)
142 .field("modified_since_accessed", &self.modified_since_accessed)
143 .field("diagnostics", &self.diagnostics)
144 // .field("language_server", &self.language_server)
145 .finish()
146 }
149// The documentation and implementation of this function should be up-to-date with
150// its sibling function, `to_writer()`.
151//
152/// Decodes a stream of bytes into UTF-8, returning a `Rope` and the
153/// encoding it was decoded as. The optional `encoding` parameter can
154/// be used to override encoding auto-detection.
155pub fn from_reader<R: std::io::Read + ?Sized>(
156 reader: &mut R,
157 encoding: Option<&'static encoding::Encoding>,
158) -> Result<(Rope, &'static encoding::Encoding), Error> {
159 // These two buffers are 8192 bytes in size each and are used as
160 // intermediaries during the decoding process. Text read into `buf`
161 // from `reader` is decoded into `buf_out` as UTF-8. Once either
162 // `buf_out` is full or the end of the reader was reached, the
163 // contents are appended to `builder`.
164 let mut buf = [0u8; BUF_SIZE];
165 let mut buf_out = [0u8; BUF_SIZE];
166 let mut builder = RopeBuilder::new();
168 // By default, the encoding of the text is auto-detected via the
169 // `chardetng` crate which requires sample data from the reader.
170 // As a manual override to this auto-detection is possible, the
171 // same data is read into `buf` to ensure symmetry in the upcoming
172 // loop.
173 let (encoding, mut decoder, mut slice, mut is_empty) = {
174 let read = reader.read(&mut buf)?;
175 let is_empty = read == 0;
176 let encoding = encoding.unwrap_or_else(|| {
177 let mut encoding_detector = chardetng::EncodingDetector::new();
178 encoding_detector.feed(&buf, is_empty);
179 encoding_detector.guess(None, true)
180 });
181 let decoder = encoding.new_decoder();
183 // If the amount of bytes read from the reader is less than
184 // `buf.len()`, it is undesirable to read the bytes afterwards.
185 let slice = &buf[..read];
186 (encoding, decoder, slice, is_empty)
187 };
189 // `RopeBuilder::append()` expects a `&str`, so this is the "real"
190 // output buffer. When decoding, the number of bytes in the output
191 // buffer will often exceed the number of bytes in the input buffer.
192 // The `result` returned by `decode_to_str()` will state whether or
193 // not that happened. The contents of `buf_str` is appended to
194 // `builder` and it is reused for the next iteration of the decoding
195 // loop.
196 //
197 // As it is possible to read less than the buffer's maximum from `read()`
198 // even when the end of the reader has yet to be reached, the end of
199 // the reader is determined only when a `read()` call returns `0`.
200 //
201 // SAFETY: `buf_out` is a zero-initialized array, thus it will always
202 // contain valid UTF-8.
203 let buf_str = unsafe { std::str::from_utf8_unchecked_mut(&mut buf_out[..]) };
204 let mut total_written = 0usize;
205 loop {
206 let mut total_read = 0usize;
208 // An inner loop is necessary as it is possible that the input buffer
209 // may not be completely decoded on the first `decode_to_str()` call
210 // which would happen in cases where the output buffer is filled to
211 // capacity.
212 loop {
213 let (result, read, written, ..) = decoder.decode_to_str(
214 &slice[total_read..],
215 &mut buf_str[total_written..],
216 is_empty,
217 );
219 // These variables act as the read and write cursors of `buf` and `buf_str` respectively.
220 // They are necessary in case the output buffer fills before decoding of the entire input
221 // loop is complete. Otherwise, the loop would endlessly iterate over the same `buf` and
222 // the data inside the output buffer would be overwritten.
223 total_read += read;
224 total_written += written;
225 match result {
226 encoding::CoderResult::InputEmpty => {
227 debug_assert_eq!(slice.len(), total_read);
228 break;
229 }
230 encoding::CoderResult::OutputFull => {
231 debug_assert!(slice.len() > total_read);
232 builder.append(&buf_str[..total_written]);
233 total_written = 0;
234 }
235 }
236 }
237 // Once the end of the stream is reached, the output buffer is
238 // flushed and the loop terminates.
239 if is_empty {
240 debug_assert_eq!(reader.read(&mut buf)?, 0);
241 builder.append(&buf_str[..total_written]);
242 break;
243 }
245 // Once the previous input has been processed and decoded, the next set of
246 // data is fetched from the reader. The end of the reader is determined to
247 // be when exactly `0` bytes were read from the reader, as per the invariants
248 // of the `Read` trait.
249 let read = reader.read(&mut buf)?;
250 slice = &buf[..read];
251 is_empty = read == 0;
252 }
253 let rope = builder.finish();
254 Ok((rope, encoding))
257// The documentation and implementation of this function should be up-to-date with
258// its sibling function, `from_reader()`.
259//
260/// Encodes the text inside `rope` into the given `encoding` and writes the
261/// encoded output into `writer.` As a `Rope` can only contain valid UTF-8,
262/// replacement characters may appear in the encoded text.
263pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
264 writer: &'a mut W,
265 encoding: &'static encoding::Encoding,
266 rope: &'a Rope,
267) -> Result<(), Error> {
268 // Text inside a `Rope` is stored as non-contiguous blocks of data called
269 // chunks. The absolute size of each chunk is unknown, thus it is impossible
270 // to predict the end of the chunk iterator ahead of time. Instead, it is
271 // determined by filtering the iterator to remove all empty chunks and then
272 // appending an empty chunk to it. This is valuable for detecting when all
273 // chunks in the `Rope` have been iterated over in the subsequent loop.
274 let iter = rope
275 .chunks()
276 .filter(|c| !c.is_empty())
277 .chain(std::iter::once(""));
278 let mut buf = [0u8; BUF_SIZE];
279 let mut encoder = encoding.new_encoder();
280 let mut total_written = 0usize;
281 for chunk in iter {
282 let is_empty = chunk.is_empty();
283 let mut total_read = 0usize;
285 // An inner loop is necessary as it is possible that the input buffer
286 // may not be completely encoded on the first `encode_from_utf8()` call
287 // which would happen in cases where the output buffer is filled to
288 // capacity.
289 loop {
290 let (result, read, written, ..) =
291 encoder.encode_from_utf8(&chunk[total_read..], &mut buf[total_written..], is_empty);
293 // These variables act as the read and write cursors of `chunk` and `buf` respectively.
294 // They are necessary in case the output buffer fills before encoding of the entire input
295 // loop is complete. Otherwise, the loop would endlessly iterate over the same `chunk` and
296 // the data inside the output buffer would be overwritten.
297 total_read += read;
298 total_written += written;
299 match result {
300 encoding::CoderResult::InputEmpty => {
301 debug_assert_eq!(chunk.len(), total_read);
302 debug_assert!(buf.len() >= total_written);
303 break;
304 }
305 encoding::CoderResult::OutputFull => {
306 debug_assert!(chunk.len() > total_read);
307 writer.write_all(&buf[..total_written]).await?;
308 total_written = 0;
309 }
310 }
311 }
313 // Once the end of the iterator is reached, the output buffer is
314 // flushed and the outer loop terminates.
315 if is_empty {
316 writer.write_all(&buf[..total_written]).await?;
317 writer.flush().await?;
318 break;
319 }
320 }
321 Ok(())
324fn take_with<T, F>(mut_ref: &mut T, f: F)
325where
326 T: Default,
327 F: FnOnce(T) -> T,
329 *mut_ref = f(mem::take(mut_ref));
332use helix_lsp::lsp;
333use url::Url;
335impl Document {
336 pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
337 let encoding = encoding.unwrap_or(encoding::UTF_8);
338 let changes = ChangeSet::new(&text);
339 let old_state = None;
341 Self {
342 id: DocumentId::default(),
343 path: None,
344 encoding,
345 text,
346 selections: HashMap::default(),
347 indent_style: DEFAULT_INDENT,
348 line_ending: DEFAULT_LINE_ENDING,
349 mode: Mode::Normal,
350 restore_cursor: false,
351 syntax: None,
352 language: None,
353 changes,
354 old_state,
355 diagnostics: Vec::new(),
356 version: 0,
357 history: Cell::new(History::default()),
358 savepoint: None,
359 last_saved_revision: 0,
360 modified_since_accessed: false,
361 language_server: None,
362 }
363 }
365 // TODO: async fn?
366 /// Create a new document from `path`. Encoding is auto-detected, but it can be manually
367 /// overwritten with the `encoding` parameter.
368 pub fn open(
369 path: &Path,
370 encoding: Option<&'static encoding::Encoding>,
371 config_loader: Option<Arc<syntax::Loader>>,
372 ) -> Result<Self, Error> {
373 // Open the file if it exists, otherwise assume it is a new file (and thus empty).
374 let (rope, encoding) = if path.exists() {
375 let mut file =
376 std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
377 from_reader(&mut file, encoding)?
378 } else {
379 let encoding = encoding.unwrap_or(encoding::UTF_8);
380 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
381 };
383 let mut doc = Self::from(rope, Some(encoding));
385 // set the path and try detecting the language
386 doc.set_path(Some(path))?;
387 if let Some(loader) = config_loader {
388 doc.detect_language(loader);
389 }
391 doc.detect_indent_and_line_ending();
393 Ok(doc)
394 }
396 /// The same as [`format`], but only returns formatting changes if auto-formatting
397 /// is configured.
398 pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
399 if self.language_config().map(|c| c.auto_format) == Some(true) {
400 self.format()
401 } else {
402 None
403 }
404 }
406 /// If supported, returns the changes that should be applied to this document in order
407 /// to format it nicely.
408 pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
409 if let Some(language_server) = self.language_server() {
410 let text = self.text.clone();
411 let offset_encoding = language_server.offset_encoding();
412 let request = language_server.text_document_formatting(
413 self.identifier(),
414 lsp::FormattingOptions::default(),
415 None,
416 )?;
418 let fut = async move {
419 let edits = request.await.unwrap_or_else(|e| {
420 log::warn!("LSP formatting failed: {}", e);
421 Default::default()
422 });
423 LspFormatting {
424 doc: text,
425 edits,
426 offset_encoding,
427 }
428 };
429 Some(fut)
430 } else {
431 None
432 }
433 }
435 pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
436 self.save_impl::<futures_util::future::Ready<_>>(None)
437 }
439 pub fn format_and_save(
440 &mut self,
441 formatting: Option<impl Future<Output = LspFormatting>>,
442 ) -> impl Future<Output = anyhow::Result<()>> {
443 self.save_impl(formatting)
444 }
446 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
447 // or is that handled by the OS/async layer
448 /// The `Document`'s text is encoded according to its encoding and written to the file located
449 /// at its `path()`.
450 ///
451 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
452 fn save_impl<F: Future<Output = LspFormatting>>(
453 &mut self,
454 formatting: Option<F>,
455 ) -> impl Future<Output = Result<(), anyhow::Error>> {
456 // we clone and move text + path into the future so that we asynchronously save the current
457 // state without blocking any further edits.
459 let mut text = self.text().clone();
460 let path = self.path.clone().expect("Can't save with no path set!");
461 let identifier = self.identifier();
463 let language_server = self.language_server.clone();
465 // mark changes up to now as saved
466 self.reset_modified();
468 let encoding = self.encoding;
470 // We encode the file according to the `Document`'s encoding.
471 async move {
472 use tokio::fs::File;
473 if let Some(parent) = path.parent() {
474 // TODO: display a prompt asking the user if the directories should be created
475 if !parent.exists() {
476 return Err(Error::msg(
477 "can't save file, parent directory does not exist",
478 ));
479 }
480 }
482 if let Some(fmt) = formatting {
483 let success = Transaction::from(fmt.await).changes().apply(&mut text);
484 if !success {
485 // This shouldn't happen, because the transaction changes were generated
486 // from the same text we're saving.
487 log::error!("failed to apply format changes before saving");
488 }
489 }
491 let mut file = File::create(path).await?;
492 to_writer(&mut file, encoding, &text).await?;
494 if let Some(language_server) = language_server {
495 if !language_server.is_initialized() {
496 return Ok(());
497 }
498 if let Some(notification) =
499 language_server.text_document_did_save(identifier, &text)
500 {
501 notification.await?;
502 }
503 }
505 Ok(())
506 }
507 }
509 /// Detect the programming language based on the file type.
510 pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
511 if let Some(path) = &self.path {
512 let language_config = config_loader
513 .language_config_for_file_name(path)
514 .or_else(|| config_loader.language_config_for_shebang(self.text()));
515 self.set_language(language_config, Some(config_loader));
516 }
517 }
519 /// Detect the indentation used in the file, or otherwise defaults to the language indentation
520 /// configured in `languages.toml`, with a fallback to 4 space indentation if it isn't
521 /// specified. Line ending is likewise auto-detected, and will fallback to the default OS
522 /// line ending.
523 pub fn detect_indent_and_line_ending(&mut self) {
524 self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
525 self.language
526 .as_ref()
527 .and_then(|config| config.indent.as_ref())
528 .map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
529 });
530 self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
531 }
533 /// Reload the document from its path.
534 pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
535 let encoding = &self.encoding;
536 let path = self.path().filter(|path| path.exists());
538 // If there is no path or the path no longer exists.
539 if path.is_none() {
540 return Err(anyhow!("can't find file to reload from"));
541 }
543 let mut file = std::fs::File::open(path.unwrap())?;
544 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
546 // Calculate the difference between the buffer and source text, and apply it.
547 // This is not considered a modification of the contents of the file regardless
548 // of the encoding.
549 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
550 self.apply(&transaction, view_id);
551 self.append_changes_to_history(view_id);
552 self.reset_modified();
554 self.detect_indent_and_line_ending();
556 Ok(())
557 }
559 /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
560 pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
561 match encoding::Encoding::for_label(label.as_bytes()) {
562 Some(encoding) => self.encoding = encoding,
563 None => return Err(anyhow::anyhow!("unknown encoding")),
564 }
565 Ok(())
566 }
568 /// Returns the [`Document`]'s current encoding.
569 pub fn encoding(&self) -> &'static encoding::Encoding {
570 self.encoding
571 }
573 pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> {
574 let path = path
575 .map(helix_core::path::get_canonicalized_path)
576 .transpose()?;
578 // if parent doesn't exist we still want to open the document
579 // and error out when document is saved
580 self.path = path;
582 Ok(())
583 }
585 /// Set the programming language for the file and load associated data (e.g. highlighting)
586 /// if it exists.
587 pub fn set_language(
588 &mut self,
589 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
590 loader: Option<Arc<helix_core::syntax::Loader>>,
591 ) {
592 if let (Some(language_config), Some(loader)) = (language_config, loader) {
593 if let Some(highlight_config) = language_config.highlight_config(&loader.scopes()) {
594 let syntax = Syntax::new(&self.text, highlight_config, loader);
595 self.syntax = Some(syntax);
596 }
598 self.language = Some(language_config);
599 } else {
600 self.syntax = None;
601 self.language = None;
602 };
603 }
605 /// Set the programming language for the file if you know the name (scope) but don't have the
606 /// [`syntax::LanguageConfiguration`] for it.
607 pub fn set_language2(&mut self, scope: &str, config_loader: Arc<syntax::Loader>) {
608 let language_config = config_loader.language_config_for_scope(scope);
610 self.set_language(language_config, Some(config_loader));
611 }
613 /// Set the LSP.
614 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
615 self.language_server = language_server;
616 }
618 /// Select text within the [`Document`].
619 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
620 // TODO: use a transaction?
621 self.selections
622 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
623 }
625 /// Apply a [`Transaction`] to the [`Document`] to change its text.
626 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
627 let old_doc = self.text().clone();
629 let success = transaction.changes().apply(&mut self.text);
631 if success {
632 for selection in self.selections.values_mut() {
633 *selection = selection
634 .clone()
635 // Map through changes
636 .map(transaction.changes())
637 // Ensure all selections accross all views still adhere to invariants.
638 .ensure_invariants(self.text.slice(..));
639 }
641 // if specified, the current selection should instead be replaced by transaction.selection
642 if let Some(selection) = transaction.selection() {
643 self.selections.insert(
644 view_id,
645 selection.clone().ensure_invariants(self.text.slice(..)),
646 );
647 }
649 // set modified since accessed
650 self.modified_since_accessed = true;
651 }
653 if !transaction.changes().is_empty() {
654 self.version += 1;
656 // generate revert to savepoint
657 if self.savepoint.is_some() {
658 take_with(&mut self.savepoint, |prev_revert| {
659 let revert = transaction.invert(&old_doc);
660 Some(revert.compose(prev_revert.unwrap()))
661 });
662 }
664 // update tree-sitter syntax tree
665 if let Some(syntax) = &mut self.syntax {
666 // TODO: no unwrap
667 syntax
668 .update(&old_doc, &self.text, transaction.changes())
669 .unwrap();
670 }
672 // map state.diagnostics over changes::map_pos too
673 for diagnostic in &mut self.diagnostics {
674 use helix_core::Assoc;
675 let changes = transaction.changes();
676 diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
677 diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
678 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
679 }
681 // emit lsp notification
682 if let Some(language_server) = self.language_server() {
683 let notify = language_server.text_document_did_change(
684 self.versioned_identifier(),
685 &old_doc,
686 self.text(),
687 transaction.changes(),
688 );
690 if let Some(notify) = notify {
691 tokio::spawn(notify);
692 } //.expect("failed to emit textDocument/didChange");
693 }
694 }
695 success
696 }
698 /// Apply a [`Transaction`] to the [`Document`] to change its text.
699 pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
700 // store the state just before any changes are made. This allows us to undo to the
701 // state just before a transaction was applied.
702 if self.changes.is_empty() && !transaction.changes().is_empty() {
703 self.old_state = Some(State {
704 doc: self.text.clone(),
705 selection: self.selection(view_id).clone(),
706 });
707 }
709 let success = self.apply_impl(transaction, view_id);
711 if !transaction.changes().is_empty() {
712 // Compose this transaction with the previous one
713 take_with(&mut self.changes, |changes| {
714 changes.compose(transaction.changes().clone())
715 });
716 }
717 success
718 }
720 /// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
721 pub fn undo(&mut self, view_id: ViewId) -> bool {
722 let mut history = self.history.take();
723 let success = if let Some(transaction) = history.undo() {
724 self.apply_impl(transaction, view_id)
725 } else {
726 false
727 };
728 self.history.set(history);
730 if success {
731 // reset changeset to fix len
732 self.changes = ChangeSet::new(self.text());
733 }
734 success
735 }
737 /// Redo the last modification to the [`Document`]. Returns whether the redo was sucessful.
738 pub fn redo(&mut self, view_id: ViewId) -> bool {
739 let mut history = self.history.take();
740 let success = if let Some(transaction) = history.redo() {
741 self.apply_impl(transaction, view_id)
742 } else {
743 false
744 };
745 self.history.set(history);
747 if success {
748 // reset changeset to fix len
749 self.changes = ChangeSet::new(self.text());
750 }
751 success
752 }
754 pub fn savepoint(&mut self) {
755 self.savepoint = Some(Transaction::new(self.text()));
756 }
758 pub fn restore(&mut self, view_id: ViewId) {
759 if let Some(revert) = self.savepoint.take() {
760 self.apply(&revert, view_id);
761 }
762 }
764 /// Undo modifications to the [`Document`] according to `uk`.
765 pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool {
766 let txns = self.history.get_mut().earlier(uk);
767 let mut success = false;
768 for txn in txns {
769 if self.apply_impl(&txn, view_id) {
770 success = true;
771 }
772 }
773 if success {
774 // reset changeset to fix len
775 self.changes = ChangeSet::new(self.text());
776 }
777 success
778 }
780 /// Redo modifications to the [`Document`] according to `uk`.
781 pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool {
782 let txns = self.history.get_mut().later(uk);
783 let mut success = false;
784 for txn in txns {
785 if self.apply_impl(&txn, view_id) {
786 success = true;
787 }
788 }
789 if success {
790 // reset changeset to fix len
791 self.changes = ChangeSet::new(self.text());
792 }
793 success
794 }
796 /// Commit pending changes to history
797 pub fn append_changes_to_history(&mut self, view_id: ViewId) {
798 if self.changes.is_empty() {
799 return;
800 }
802 let new_changeset = ChangeSet::new(self.text());
803 let changes = std::mem::replace(&mut self.changes, new_changeset);
804 // Instead of doing this messy merge we could always commit, and based on transaction
805 // annotations either add a new layer or compose into the previous one.
806 let transaction =
807 Transaction::from(changes).with_selection(self.selection(view_id).clone());
809 // HAXX: we need to reconstruct the state as it was before the changes..
810 let old_state = self.old_state.take().expect("no old_state available");
812 let mut history = self.history.take();
813 history.commit_revision(&transaction, &old_state);
814 self.history.set(history);
815 }
817 pub fn id(&self) -> DocumentId {
818 self.id
819 }
821 /// If there are unsaved modifications.
822 pub fn is_modified(&self) -> bool {
823 let history = self.history.take();
824 let current_revision = history.current_revision();
825 self.history.set(history);
826 current_revision != self.last_saved_revision || !self.changes.is_empty()
827 }
829 /// Save modifications to history, and so [`Self::is_modified`] will return false.
830 pub fn reset_modified(&mut self) {
831 let history = self.history.take();
832 let current_revision = history.current_revision();
833 self.history.set(history);
834 self.last_saved_revision = current_revision;
835 }
837 /// Current editing mode for the [`Document`].
838 pub fn mode(&self) -> Mode {
839 self.mode
840 }
842 /// Corresponding language scope name. Usually `source.<lang>`.
843 pub fn language(&self) -> Option<&str> {
844 self.language
845 .as_ref()
846 .map(|language| language.scope.as_str())
847 }
849 /// Language ID for the document. Either the `language-id` from the
850 /// `language-server` configuration, or the document language if no
851 /// `language-id` has been specified.
852 pub fn language_id(&self) -> Option<&str> {
853 self.language
854 .as_ref()
855 .and_then(|config| config.language_server.as_ref())
856 .and_then(|lsp_config| lsp_config.language_id.as_ref())
857 .map_or_else(
858 || {
859 self.language()
860 .and_then(|s| s.rsplit_once('.'))
861 .map(|(_, language_id)| language_id)
862 },
863 |language_id| Some(language_id.as_str()),
864 )
865 }
867 /// Corresponding [`LanguageConfiguration`].
868 pub fn language_config(&self) -> Option<&LanguageConfiguration> {
869 self.language.as_deref()
870 }
872 /// Current document version, incremented at each change.
873 pub fn version(&self) -> i32 {
874 self.version
875 }
877 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
878 let server = self.language_server.as_deref();
879 let initialized = server
880 .map(|server| server.is_initialized())
881 .unwrap_or(false);
883 // only resolve language_server if it's initialized
884 if initialized {
885 server
886 } else {
887 None
888 }
889 }
891 #[inline]
892 /// Tree-sitter AST tree
893 pub fn syntax(&self) -> Option<&Syntax> {
894 self.syntax.as_ref()
895 }
897 /// Tab size in columns.
898 pub fn tab_width(&self) -> usize {
899 self.language
900 .as_ref()
901 .and_then(|config| config.indent.as_ref())
902 .map_or(4, |config| config.tab_width) // fallback to 4 columns
903 }
905 /// Returns a string containing a single level of indentation.
906 ///
907 /// TODO: we might not need this function anymore, since the information
908 /// is conveniently available in `Document::indent_style` now.
909 pub fn indent_unit(&self) -> &'static str {
910 self.indent_style.as_str()
911 }
913 pub fn changes(&self) -> &ChangeSet {
914 &self.changes
915 }
917 #[inline]
918 /// File path on disk.
919 pub fn path(&self) -> Option<&PathBuf> {
920 self.path.as_ref()
921 }
923 /// File path as a URL.
924 pub fn url(&self) -> Option<Url> {
925 self.path().map(|path| Url::from_file_path(path).unwrap())
926 }
928 #[inline]
929 pub fn text(&self) -> &Rope {
930 &self.text
931 }
933 #[inline]
934 pub fn selection(&self, view_id: ViewId) -> &Selection {
935 &self.selections[&view_id]
936 }
938 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
939 &self.selections
940 }
942 pub fn relative_path(&self) -> Option<PathBuf> {
943 self.path
944 .as_deref()
945 .map(helix_core::path::get_relative_path)
946 }
948 // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
949 // self.state.doc.slice
950 // }
952 // transact(Fn) ?
954 // -- LSP methods
956 #[inline]
957 pub fn identifier(&self) -> lsp::TextDocumentIdentifier {
958 lsp::TextDocumentIdentifier::new(self.url().unwrap())
959 }
961 pub fn versioned_identifier(&self) -> lsp::VersionedTextDocumentIdentifier {
962 lsp::VersionedTextDocumentIdentifier::new(self.url().unwrap(), self.version)
963 }
965 #[inline]
966 pub fn diagnostics(&self) -> &[Diagnostic] {
967 &self.diagnostics
968 }
970 pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
971 self.diagnostics = diagnostics;
972 // sort by range
973 self.diagnostics
974 .sort_unstable_by_key(|diagnostic| diagnostic.range);
975 }
978impl Default for Document {
979 fn default() -> Self {
980 let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
981 Self::from(text, None)
982 }
985#[cfg(test)]
986mod test {
987 use super::*;
989 #[test]
990 fn changeset_to_changes_ignore_line_endings() {
991 use helix_lsp::{lsp, Client, OffsetEncoding};
992 let text = Rope::from("hello\r\nworld");
993 let mut doc = Document::from(text, None);
994 let view = ViewId::default();
995 doc.set_selection(view, Selection::single(0, 0));
997 let transaction =
998 Transaction::change(doc.text(), vec![(5, 7, Some("\n".into()))].into_iter());
999 let old_doc = doc.text().clone();
1000 doc.apply(&transaction, view);
1001 let changes = Client::changeset_to_changes(
1002 &old_doc,
1003 doc.text(),
1004 transaction.changes(),
1005 OffsetEncoding::Utf8,
1006 );
1008 assert_eq!(doc.text(), "hello\nworld");
1010 assert_eq!(
1011 changes,
1012 &[lsp::TextDocumentContentChangeEvent {
1013 range: Some(lsp::Range::new(
1014 lsp::Position::new(0, 5),
1015 lsp::Position::new(1, 0)
1016 )),
1017 text: "\n".into(),
1018 range_length: None,
1019 }]
1020 );
1023 #[test]
1024 fn changeset_to_changes() {
1025 use helix_lsp::{lsp, Client, OffsetEncoding};
1026 let text = Rope::from("hello");
1027 let mut doc = Document::from(text, None);
1028 let view = ViewId::default();
1029 doc.set_selection(view, Selection::single(5, 5));
1031 // insert
1033 let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
1034 let old_doc = doc.text().clone();
1035 doc.apply(&transaction, view);
1036 let changes = Client::changeset_to_changes(
1037 &old_doc,
1038 doc.text(),
1039 transaction.changes(),
1040 OffsetEncoding::Utf8,
1041 );
1043 assert_eq!(
1044 changes,
1045 &[lsp::TextDocumentContentChangeEvent {
1046 range: Some(lsp::Range::new(
1047 lsp::Position::new(0, 5),
1048 lsp::Position::new(0, 5)
1049 )),
1050 text: " world".into(),
1051 range_length: None,
1052 }]
1053 );
1055 // delete
1057 let transaction = transaction.invert(&old_doc);
1058 let old_doc = doc.text().clone();
1059 doc.apply(&transaction, view);
1060 let changes = Client::changeset_to_changes(
1061 &old_doc,
1062 doc.text(),
1063 transaction.changes(),
1064 OffsetEncoding::Utf8,
1065 );
1067 // line: 0-based.
1068 // col: 0-based, gaps between chars.
1069 // 0 1 2 3 4 5 6 7 8 9 0 1
1070 // |h|e|l|l|o| |w|o|r|l|d|
1071 // -------------
1072 // (0, 5)-(0, 11)
1073 assert_eq!(
1074 changes,
1075 &[lsp::TextDocumentContentChangeEvent {
1076 range: Some(lsp::Range::new(
1077 lsp::Position::new(0, 5),
1078 lsp::Position::new(0, 11)
1079 )),
1080 text: "".into(),
1081 range_length: None,
1082 }]
1083 );
1085 // replace
1087 // also tests that changes are layered, positions depend on previous changes.
1089 doc.set_selection(view, Selection::single(0, 5));
1090 let transaction = Transaction::change(
1091 doc.text(),
1092 vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
1093 );
1094 // aeilou
1095 let old_doc = doc.text().clone();
1096 doc.apply(&transaction, view);
1097 let changes = Client::changeset_to_changes(
1098 &old_doc,
1099 doc.text(),
1100 transaction.changes(),
1101 OffsetEncoding::Utf8,
1102 );
1104 assert_eq!(
1105 changes,
1106 &[
1107 // 0 1 2 3 4 5
1108 // |h|e|l|l|o|
1109 // ----
1110 //
1111 // aeillo
1112 lsp::TextDocumentContentChangeEvent {
1113 range: Some(lsp::Range::new(
1114 lsp::Position::new(0, 0),
1115 lsp::Position::new(0, 2)
1116 )),
1117 text: "aei".into(),
1118 range_length: None,
1119 },
1120 // 0 1 2 3 4 5 6
1121 // |a|e|i|l|l|o|
1122 // -----
1123 //
1124 // aeilou
1125 lsp::TextDocumentContentChangeEvent {
1126 range: Some(lsp::Range::new(
1127 lsp::Position::new(0, 4),
1128 lsp::Position::new(0, 6)
1129 )),
1130 text: "ou".into(),
1131 range_length: None,
1134 );
1137 #[test]
1138 fn test_line_ending() {
1139 assert_eq!(
1140 Document::default().text().to_string(),
1141 DEFAULT_LINE_ENDING.as_str()
1142 );
1145 macro_rules! test_decode {
1146 ($label:expr, $label_override:expr) => {
1147 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1148 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1149 let path = base_path.join(format!("{}_in.txt", $label));
1150 let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
1151 assert!(path.exists());
1152 assert!(ref_path.exists());
1154 let mut file = std::fs::File::open(path).unwrap();
1155 let text = from_reader(&mut file, Some(encoding))
1156 .unwrap()
1157 .0
1158 .to_string();
1159 let expectation = std::fs::read_to_string(ref_path).unwrap();
1160 assert_eq!(text[..], expectation[..]);
1161 };
1164 macro_rules! test_encode {
1165 ($label:expr, $label_override:expr) => {
1166 let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
1167 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1168 let path = base_path.join(format!("{}_out.txt", $label));
1169 let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
1170 assert!(path.exists());
1171 assert!(ref_path.exists());
1173 let text = Rope::from_str(&std::fs::read_to_string(path).unwrap());
1174 let mut buf: Vec<u8> = Vec::new();
1175 helix_lsp::block_on(to_writer(&mut buf, encoding, &text)).unwrap();
1177 let expectation = std::fs::read(ref_path).unwrap();
1178 assert_eq!(buf, expectation);
1179 };
1182 macro_rules! test_decode_fn {
1183 ($name:ident, $label:expr, $label_override:expr) => {
1184 #[test]
1185 fn $name() {
1186 test_decode!($label, $label_override);
1188 };
1189 ($name:ident, $label:expr) => {
1190 #[test]
1191 fn $name() {
1192 test_decode!($label, $label);
1194 };
1197 macro_rules! test_encode_fn {
1198 ($name:ident, $label:expr, $label_override:expr) => {
1199 #[test]
1200 fn $name() {
1201 test_encode!($label, $label_override);
1203 };
1204 ($name:ident, $label:expr) => {
1205 #[test]
1206 fn $name() {
1207 test_encode!($label, $label);
1209 };
1212 test_decode_fn!(test_big5_decode, "big5");
1213 test_encode_fn!(test_big5_encode, "big5");
1214 test_decode_fn!(test_euc_kr_decode, "euc_kr", "EUC-KR");
1215 test_encode_fn!(test_euc_kr_encode, "euc_kr", "EUC-KR");
1216 test_decode_fn!(test_gb18030_decode, "gb18030");
1217 test_encode_fn!(test_gb18030_encode, "gb18030");
1218 test_decode_fn!(test_iso_2022_jp_decode, "iso_2022_jp", "ISO-2022-JP");
1219 test_encode_fn!(test_iso_2022_jp_encode, "iso_2022_jp", "ISO-2022-JP");
1220 test_decode_fn!(test_jis0208_decode, "jis0208", "EUC-JP");
1221 test_encode_fn!(test_jis0208_encode, "jis0208", "EUC-JP");
1222 test_decode_fn!(test_jis0212_decode, "jis0212", "EUC-JP");
1223 test_decode_fn!(test_shift_jis_decode, "shift_jis");
1224 test_encode_fn!(test_shift_jis_encode, "shift_jis");