1use anyhow::{anyhow, Context, Error};
2use serde::de::{self, Deserialize, Deserializer};
4use std::collections::HashMap;
6use std::future::Future;
7use std::path::{Component, Path, PathBuf};
13 indent::{auto_detect_indent_style, IndentStyle},
14 line_ending::auto_detect_line_ending,
15 syntax::{self, LanguageConfiguration},
16 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
19use helix_lsp::util::LspFormatting;
21use crate::{DocumentId, Theme, ViewId};
23/// 8kB of buffer space for encoding and decoding `Rope`s.
24const BUF_SIZE: usize = 8192;
26#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
33impl Display for Mode {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 Mode::Normal => f.write_str("normal"),
37 Mode::Select => f.write_str("select"),
38 Mode::Insert => f.write_str("insert"),
43impl FromStr for Mode {
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 "normal" => Ok(Mode::Normal),
49 "select" => Ok(Mode::Select),
50 "insert" => Ok(Mode::Insert),
51 _ => Err(anyhow!("Invalid mode '{}'", s)),
56// toml deserializer doesn't seem to recognize string as enum
57impl<'de> Deserialize<'de> for Mode {
58 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62 let s = String::deserialize(deserializer)?;
63 s.parse().map_err(de::Error::custom)
68 pub(crate) id: DocumentId,
70 pub(crate) selections: HashMap<ViewId, Selection>,
72 path: Option<PathBuf>,
73 encoding: &'static encoding_rs::Encoding,
75 /// Current editing mode.
77 pub restore_cursor: bool,
79 /// Current indent style.
80 pub indent_style: IndentStyle,
82 /// The document's default line ending.
83 pub line_ending: LineEnding,
85 syntax: Option<Syntax>,
86 // /// Corresponding language scope name. Usually `source.<lang>`.
87 pub(crate) language: Option<Arc<LanguageConfiguration>>,
89 /// Pending changes since last history commit.
91 /// State at last commit. Used for calculating reverts.
92 old_state: Option<State>,
94 // It can be used as a cell where we will take it out to get some parts of the history and put
95 // it back as it separated from the edits. We could split out the parts manually but that will
96 // be more troublesome.
97 history: Cell<History>,
98 last_saved_revision: usize,
99 version: i32, // should be usize?
101 diagnostics: Vec<Diagnostic>,
102 language_server: Option<Arc<helix_lsp::Client>>,
106impl fmt::Debug for Document {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.debug_struct("Document")
109 .field("id", &self.id)
110 .field("text", &self.text)
111 .field("selections", &self.selections)
112 .field("path", &self.path)
113 .field("encoding", &self.encoding)
114 .field("mode", &self.mode)
115 .field("restore_cursor", &self.restore_cursor)
116 .field("syntax", &self.syntax)
117 .field("language", &self.language)
118 .field("changes", &self.changes)
119 .field("old_state", &self.old_state)
120 // .field("history", &self.history)
121 .field("last_saved_revision", &self.last_saved_revision)
122 .field("version", &self.version)
123 .field("diagnostics", &self.diagnostics)
124 // .field("language_server", &self.language_server)
129// The documentation and implementation of this function should be up-to-date with
130// its sibling function, `to_writer()`.
132/// Decodes a stream of bytes into UTF-8, returning a `Rope` and the
133/// encoding it was decoded as. The optional `encoding` parameter can
134/// be used to override encoding auto-detection.
135pub fn from_reader<R: std::io::Read + ?Sized>(
137 encoding: Option<&'static encoding_rs::Encoding>,
138) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
139 // These two buffers are 8192 bytes in size each and are used as
140 // intermediaries during the decoding process. Text read into `buf`
141 // from `reader` is decoded into `buf_out` as UTF-8. Once either
142 // `buf_out` is full or the end of the reader was reached, the
143 // contents are appended to `builder`.
144 let mut buf = [0u8; BUF_SIZE];
145 let mut buf_out = [0u8; BUF_SIZE];
146 let mut builder = RopeBuilder::new();
148 // By default, the encoding of the text is auto-detected via the
149 // `chardetng` crate which requires sample data from the reader.
150 // As a manual override to this auto-detection is possible, the
151 // same data is read into `buf` to ensure symmetry in the upcoming
153 let (encoding, mut decoder, mut slice, mut is_empty) = {
154 let read = reader.read(&mut buf)?;
155 let is_empty = read == 0;
156 let encoding = encoding.unwrap_or_else(|| {
157 let mut encoding_detector = chardetng::EncodingDetector::new();
158 encoding_detector.feed(&buf, is_empty);
159 encoding_detector.guess(None, true)
161 let decoder = encoding.new_decoder();
163 // If the amount of bytes read from the reader is less than
164 // `buf.len()`, it is undesirable to read the bytes afterwards.
165 let slice = &buf[..read];
166 (encoding, decoder, slice, is_empty)
169 // `RopeBuilder::append()` expects a `&str`, so this is the "real"
170 // output buffer. When decoding, the number of bytes in the output
171 // buffer will often exceed the number of bytes in the input buffer.
172 // The `result` returned by `decode_to_str()` will state whether or
173 // not that happened. The contents of `buf_str` is appended to
174 // `builder` and it is reused for the next iteration of the decoding
177 // As it is possible to read less than the buffer's maximum from `read()`
178 // even when the end of the reader has yet to be reached, the end of
179 // the reader is determined only when a `read()` call returns `0`.
181 // SAFETY: `buf_out` is a zero-initialized array, thus it will always
182 // contain valid UTF-8.
183 let buf_str = unsafe { std::str::from_utf8_unchecked_mut(&mut buf_out[..]) };
184 let mut total_written = 0usize;
186 let mut total_read = 0usize;
188 // An inner loop is necessary as it is possible that the input buffer
189 // may not be completely decoded on the first `decode_to_str()` call
190 // which would happen in cases where the output buffer is filled to
193 let (result, read, written, ..) = decoder.decode_to_str(
194 &slice[total_read..],
195 &mut buf_str[total_written..],
199 // These variables act as the read and write cursors of `buf` and `buf_str` respectively.
200 // They are necessary in case the output buffer fills before decoding of the entire input
201 // loop is complete. Otherwise, the loop would endlessly iterate over the same `buf` and
202 // the data inside the output buffer would be overwritten.
204 total_written += written;
206 encoding_rs::CoderResult::InputEmpty => {
207 debug_assert_eq!(slice.len(), total_read);
210 encoding_rs::CoderResult::OutputFull => {
211 debug_assert!(slice.len() > total_read);
212 builder.append(&buf_str[..total_written]);
217 // Once the end of the stream is reached, the output buffer is
218 // flushed and the loop terminates.
220 debug_assert_eq!(reader.read(&mut buf)?, 0);
221 builder.append(&buf_str[..total_written]);
225 // Once the previous input has been processed and decoded, the next set of
226 // data is fetched from the reader. The end of the reader is determined to
227 // be when exactly `0` bytes were read from the reader, as per the invariants
228 // of the `Read` trait.
229 let read = reader.read(&mut buf)?;
230 slice = &buf[..read];
231 is_empty = read == 0;
233 let rope = builder.finish();
237// The documentation and implementation of this function should be up-to-date with
238// its sibling function, `from_reader()`.
240/// Encodes the text inside `rope` into the given `encoding` and writes the
241/// encoded output into `writer.` As a `Rope` can only contain valid UTF-8,
242/// replacement characters may appear in the encoded text.
243pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
245 encoding: &'static encoding_rs::Encoding,
247) -> Result<(), Error> {
248 // Text inside a `Rope` is stored as non-contiguous blocks of data called
249 // chunks. The absolute size of each chunk is unknown, thus it is impossible
250 // to predict the end of the chunk iterator ahead of time. Instead, it is
251 // determined by filtering the iterator to remove all empty chunks and then
252 // appending an empty chunk to it. This is valuable for detecting when all
253 // chunks in the `Rope` have been iterated over in the subsequent loop.
256 .filter(|c| !c.is_empty())
257 .chain(std::iter::once(""));
258 let mut buf = [0u8; BUF_SIZE];
259 let mut encoder = encoding.new_encoder();
260 let mut total_written = 0usize;
262 let is_empty = chunk.is_empty();
263 let mut total_read = 0usize;
265 // An inner loop is necessary as it is possible that the input buffer
266 // may not be completely encoded on the first `encode_from_utf8()` call
267 // which would happen in cases where the output buffer is filled to
270 let (result, read, written, ..) =
271 encoder.encode_from_utf8(&chunk[total_read..], &mut buf[total_written..], is_empty);
273 // These variables act as the read and write cursors of `chunk` and `buf` respectively.
274 // They are necessary in case the output buffer fills before encoding of the entire input
275 // loop is complete. Otherwise, the loop would endlessly iterate over the same `chunk` and
276 // the data inside the output buffer would be overwritten.
278 total_written += written;
280 encoding_rs::CoderResult::InputEmpty => {
281 debug_assert_eq!(chunk.len(), total_read);
282 debug_assert!(buf.len() >= total_written);
285 encoding_rs::CoderResult::OutputFull => {
286 debug_assert!(chunk.len() > total_read);
287 writer.write_all(&buf[..total_written]).await?;
293 // Once the end of the iterator is reached, the output buffer is
294 // flushed and the outer loop terminates.
296 writer.write_all(&buf[..total_written]).await?;
297 writer.flush().await?;
304/// Like std::mem::replace() except it allows the replacement value to be mapped from the
306fn take_with<T, F>(mut_ref: &mut T, closure: F)
310 use std::{panic, ptr};
313 let old_t = ptr::read(mut_ref);
314 let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
315 .unwrap_or_else(|_| ::std::process::abort());
316 ptr::write(mut_ref, new_t);
320/// Expands tilde `~` into users home directory if avilable, otherwise returns the path
321/// unchanged. The tilde will only be expanded when present as the first component of the path
322/// and only slash follows it.
323pub fn expand_tilde(path: &Path) -> PathBuf {
324 let mut components = path.components().peekable();
325 if let Some(Component::Normal(c)) = components.peek() {
327 if let Ok(home) = helix_core::home_dir() {
328 // it's ok to unwrap, the path starts with `~`
329 return home.join(path.strip_prefix("~").unwrap());
337/// Replaces users home directory from `path` with tilde `~` if the directory
338/// is available, otherwise returns the path unchanged.
339pub fn fold_home_dir(path: &Path) -> PathBuf {
340 if let Ok(home) = helix_core::home_dir() {
341 if path.starts_with(&home) {
342 // it's ok to unwrap, the path starts with home dir
343 return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
350/// Normalize a path, removing things like `.` and `..`.
352/// CAUTION: This does not resolve symlinks (unlike
353/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
354/// behavior at times. This should be used carefully. Unfortunately,
355/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
356/// fail, or on Windows returns annoying device paths. This is a problem Cargo
357/// needs to improve on.
358/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
359pub fn normalize_path(path: &Path) -> PathBuf {
360 let path = expand_tilde(path);
361 let mut components = path.components().peekable();
362 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
364 PathBuf::from(c.as_os_str())
369 for component in components {
371 Component::Prefix(..) => unreachable!(),
372 Component::RootDir => {
373 ret.push(component.as_os_str());
375 Component::CurDir => {}
376 Component::ParentDir => {
379 Component::Normal(c) => {
387/// Returns the canonical, absolute form of a path with all intermediate components normalized.
389/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
390/// here if the path exists, just normalize it's components.
391pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> {
392 let path = if path.is_relative() {
393 std::env::current_dir().map(|current_dir| current_dir.join(path))?
398 Ok(normalize_path(&path))
405 pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
406 let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
407 let changes = ChangeSet::new(&text);
408 let old_state = None;
411 id: DocumentId::default(),
415 selections: HashMap::default(),
416 indent_style: IndentStyle::Spaces(4),
418 restore_cursor: false,
423 diagnostics: Vec::new(),
425 history: Cell::new(History::default()),
426 last_saved_revision: 0,
427 language_server: None,
428 line_ending: DEFAULT_LINE_ENDING,
433 /// Create a new document from `path`. Encoding is auto-detected, but it can be manually
434 /// overwritten with the `encoding` parameter.
437 encoding: Option<&'static encoding_rs::Encoding>,
438 theme: Option<&Theme>,
439 config_loader: Option<&syntax::Loader>,
440 ) -> Result<Self, Error> {
441 // Open the file if it exists, otherwise assume it is a new file (and thus empty).
442 let (rope, encoding) = if path.exists() {
444 std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
445 from_reader(&mut file, encoding)?
447 let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
448 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
451 let mut doc = Self::from(rope, Some(encoding));
453 // set the path and try detecting the language
455 if let Some(loader) = config_loader {
456 doc.detect_language(theme, loader);
459 doc.detect_indent_and_line_ending();
464 /// The same as [`format`], but only returns formatting changes if auto-formatting
466 pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
467 if self.language_config().map(|c| c.auto_format) == Some(true) {
474 /// If supported, returns the changes that should be applied to this document in order
475 /// to format it nicely.
476 pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
477 if let Some(language_server) = self.language_server.clone() {
478 let text = self.text.clone();
479 let id = self.identifier();
480 let fut = async move {
481 let edits = language_server
482 .text_document_formatting(id, lsp::FormattingOptions::default(), None)
484 .unwrap_or_else(|e| {
485 log::warn!("LSP formatting failed: {}", e);
491 offset_encoding: language_server.offset_encoding(),
500 pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
501 self.save_impl::<futures_util::future::Ready<_>>(None)
504 pub fn format_and_save(
506 formatting: Option<impl Future<Output = LspFormatting>>,
507 ) -> impl Future<Output = anyhow::Result<()>> {
508 self.save_impl(formatting)
511 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
512 // or is that handled by the OS/async layer
513 /// The `Document`'s text is encoded according to its encoding and written to the file located
516 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
517 fn save_impl<F: Future<Output = LspFormatting>>(
519 formatting: Option<F>,
520 ) -> impl Future<Output = Result<(), anyhow::Error>> {
521 // we clone and move text + path into the future so that we asynchronously save the current
522 // state without blocking any further edits.
524 let mut text = self.text().clone();
525 let path = self.path.clone().expect("Can't save with no path set!");
526 let identifier = self.identifier();
528 let language_server = self.language_server.clone();
530 // mark changes up to now as saved
531 self.reset_modified();
533 let encoding = self.encoding;
535 // We encode the file according to the `Document`'s encoding.
538 if let Some(parent) = path.parent() {
539 // TODO: display a prompt asking the user if the directories should be created
540 if !parent.exists() {
541 return Err(Error::msg(
542 "can't save file, parent directory does not exist",
547 if let Some(fmt) = formatting {
548 let success = Transaction::from(fmt.await).changes().apply(&mut text);
550 // This shouldn't happen, because the transaction changes were generated
551 // from the same text we're saving.
552 log::error!("failed to apply format changes before saving");
556 let mut file = File::create(path).await?;
557 to_writer(&mut file, encoding, &text).await?;
559 if let Some(language_server) = language_server {
561 .text_document_did_save(identifier, &text)
569 /// Detect the programming language based on the file type.
570 pub fn detect_language(&mut self, theme: Option<&Theme>, config_loader: &syntax::Loader) {
571 if let Some(path) = &self.path {
572 let language_config = config_loader.language_config_for_file_name(path);
573 self.set_language(theme, language_config);
577 /// Detect the indentation used in the file, or otherwise defaults to the language indentation
578 /// configured in `languages.toml`, with a fallback back to 2 space indentation if it isn't
579 /// specified. Line ending is likewise auto-detected, and will fallback to the default OS
581 pub fn detect_indent_and_line_ending(&mut self) {
582 self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
583 IndentStyle::from_str(
586 .and_then(|config| config.indent.as_ref())
587 .map_or(" ", |config| config.unit.as_str()), // Fallback to 2 spaces.
590 self.line_ending = auto_detect_line_ending(&self.text).unwrap_or(DEFAULT_LINE_ENDING);
593 /// Reload the document from its path.
594 pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
595 let encoding = &self.encoding;
596 let path = self.path().filter(|path| path.exists());
598 // If there is no path or the path no longer exists.
600 return Err(anyhow!("can't find file to reload from"));
603 let mut file = std::fs::File::open(path.unwrap())?;
604 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
606 // Calculate the difference between the buffer and source text, and apply it.
607 // This is not considered a modification of the contents of the file regardless
609 let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
610 self.apply(&transaction, view_id);
611 self.append_changes_to_history(view_id);
612 self.reset_modified();
614 self.detect_indent_and_line_ending();
619 /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
620 pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
621 match encoding_rs::Encoding::for_label(label.as_bytes()) {
622 Some(encoding) => self.encoding = encoding,
623 None => return Err(anyhow::anyhow!("unknown encoding")),
628 /// Returns the [`Document`]'s current encoding.
629 pub fn encoding(&self) -> &'static encoding_rs::Encoding {
633 pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> {
634 let path = canonicalize_path(path)?;
636 // if parent doesn't exist we still want to open the document
637 // and error out when document is saved
638 self.path = Some(path);
643 /// Set the programming language for the file and load associated data (e.g. highlighting)
647 theme: Option<&Theme>,
648 language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
650 if let Some(language_config) = language_config {
651 let scopes = theme.map(|theme| theme.scopes()).unwrap_or(&[]);
652 if let Some(highlight_config) = language_config.highlight_config(scopes) {
653 let syntax = Syntax::new(&self.text, highlight_config);
654 self.syntax = Some(syntax);
655 // TODO: config.configure(scopes) is now delayed, is that ok?
658 self.language = Some(language_config);
661 self.language = None;
665 /// Set the programming language for the file if you know the name (scope) but don't have the
666 /// [`syntax::LanguageConfiguration`] for it.
667 pub fn set_language2(
670 theme: Option<&Theme>,
671 config_loader: Arc<syntax::Loader>,
673 let language_config = config_loader.language_config_for_scope(scope);
675 self.set_language(theme, language_config);
679 pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) {
680 self.language_server = language_server;
683 /// Select text within the [`Document`].
684 pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
685 // TODO: use a transaction?
687 .insert(view_id, selection.ensure_invariants(self.text().slice(..)));
690 /// Apply a [`Transaction`] to the [`Document`] to change its text.
691 fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
692 let old_doc = self.text().clone();
694 let success = transaction.changes().apply(&mut self.text);
697 for selection in self.selections.values_mut() {
698 *selection = selection
700 // Map through changes
701 .map(transaction.changes())
702 // Ensure all selections accross all views still adhere to invariants.
703 .ensure_invariants(self.text.slice(..));
706 // if specified, the current selection should instead be replaced by transaction.selection
707 if let Some(selection) = transaction.selection() {
708 self.selections.insert(
710 selection.clone().ensure_invariants(self.text.slice(..)),
715 if !transaction.changes().is_empty() {
718 // update tree-sitter syntax tree
719 if let Some(syntax) = &mut self.syntax {
722 .update(&old_doc, &self.text, transaction.changes())
726 // map state.diagnostics over changes::map_pos too
727 // NOTE: seems to do nothing since the language server resends diagnostics on each edit
728 // for diagnostic in &mut self.diagnostics {
729 // use helix_core::Assoc;
730 // let changes = transaction.changes();
731 // diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
732 // diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
733 // diagnostic.line = self.text.char_to_line(diagnostic.range.start);
736 // emit lsp notification
737 if let Some(language_server) = &self.language_server {
738 let notify = language_server.text_document_did_change(
739 self.versioned_identifier(),
742 transaction.changes(),
745 if let Some(notify) = notify {
746 tokio::spawn(notify);
747 } //.expect("failed to emit textDocument/didChange");
753 /// Apply a [`Transaction`] to the [`Document`] to change its text.
754 pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
755 // store the state just before any changes are made. This allows us to undo to the
756 // state just before a transaction was applied.
757 if self.changes.is_empty() && !transaction.changes().is_empty() {
758 self.old_state = Some(State {
759 doc: self.text.clone(),
760 selection: self.selection(view_id).clone(),
764 let success = self.apply_impl(transaction, view_id);
766 if !transaction.changes().is_empty() {
767 // Compose this transaction with the previous one
768 take_with(&mut self.changes, |changes| {
769 changes.compose(transaction.changes().clone())
775 /// Undo the last modification to the [`Document`].
776 pub fn undo(&mut self, view_id: ViewId) {
777 let mut history = self.history.take();
778 let success = if let Some(transaction) = history.undo() {
779 self.apply_impl(transaction, view_id)
783 self.history.set(history);
786 // reset changeset to fix len
787 self.changes = ChangeSet::new(self.text());
791 /// Redo the last modification to the [`Document`].
792 pub fn redo(&mut self, view_id: ViewId) {
793 let mut history = self.history.take();
794 let success = if let Some(transaction) = history.redo() {
795 self.apply_impl(transaction, view_id)
799 self.history.set(history);
802 // reset changeset to fix len
803 self.changes = ChangeSet::new(self.text());
807 /// Undo modifications to the [`Document`] according to `uk`.
808 pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
809 let txns = self.history.get_mut().earlier(uk);
811 self.apply_impl(&txn, view_id);
815 /// Redo modifications to the [`Document`] according to `uk`.
816 pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
817 let txns = self.history.get_mut().later(uk);
819 self.apply_impl(&txn, view_id);
823 /// Commit pending changes to history
824 pub fn append_changes_to_history(&mut self, view_id: ViewId) {
825 if self.changes.is_empty() {
829 let new_changeset = ChangeSet::new(self.text());
830 let changes = std::mem::replace(&mut self.changes, new_changeset);
831 // Instead of doing this messy merge we could always commit, and based on transaction
832 // annotations either add a new layer or compose into the previous one.
834 Transaction::from(changes).with_selection(self.selection(view_id).clone());
836 // HAXX: we need to reconstruct the state as it was before the changes..
837 let old_state = self.old_state.take().expect("no old_state available");
839 let mut history = self.history.take();
840 history.commit_revision(&transaction, &old_state);
841 self.history.set(history);
844 pub fn id(&self) -> DocumentId {
848 /// If there are unsaved modifications.
849 pub fn is_modified(&self) -> bool {
850 let history = self.history.take();
851 let current_revision = history.current_revision();
852 self.history.set(history);
853 current_revision != self.last_saved_revision || !self.changes.is_empty()
856 /// Save modifications to history, and so [`Self::is_modified`] will return false.
857 pub fn reset_modified(&mut self) {
858 let history = self.history.take();
859 let current_revision = history.current_revision();
860 self.history.set(history);
861 self.last_saved_revision = current_revision;
864 /// Current editing mode for the [`Document`].
865 pub fn mode(&self) -> Mode {
869 /// Corresponding language scope name. Usually `source.<lang>`.
870 pub fn language(&self) -> Option<&str> {
873 .map(|language| language.scope.as_str())
876 /// Corresponding [`LanguageConfiguration`].
877 pub fn language_config(&self) -> Option<&LanguageConfiguration> {
878 self.language.as_deref()
881 /// Current document version, incremented at each change.
882 pub fn version(&self) -> i32 {
887 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
888 self.language_server.as_deref()
892 /// Tree-sitter AST tree
893 pub fn syntax(&self) -> Option<&Syntax> {
897 /// Tab size in columns.
898 pub fn tab_width(&self) -> usize {
901 .and_then(|config| config.indent.as_ref())
902 .map_or(4, |config| config.tab_width) // fallback to 4 columns
905 /// Returns a string containing a single level of indentation.
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()
914 /// File path on disk.
915 pub fn path(&self) -> Option<&PathBuf> {
919 /// File path as a URL.
920 pub fn url(&self) -> Option<Url> {
921 self.path().map(|path| Url::from_file_path(path).unwrap())
925 pub fn text(&self) -> &Rope {
930 pub fn selection(&self, view_id: ViewId) -> &Selection {
931 &self.selections[&view_id]
934 pub fn selections(&self) -> &HashMap<ViewId, Selection> {
938 pub fn relative_path(&self) -> Option<PathBuf> {
939 let cwdir = std::env::current_dir().expect("couldn't determine current directory");
941 self.path.as_ref().map(|path| {
942 let mut path = path.as_path();
943 if path.is_absolute() {
944 path = path.strip_prefix(cwdir).unwrap_or(path)
950 // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
951 // self.state.doc.slice
959 pub fn identifier(&self) -> lsp::TextDocumentIdentifier {
960 lsp::TextDocumentIdentifier::new(self.url().unwrap())
963 pub fn versioned_identifier(&self) -> lsp::VersionedTextDocumentIdentifier {
964 lsp::VersionedTextDocumentIdentifier::new(self.url().unwrap(), self.version)
968 pub fn diagnostics(&self) -> &[Diagnostic] {
972 pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
973 self.diagnostics = diagnostics;
977impl Default for Document {
978 fn default() -> Self {
979 let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
980 Self::from(text, None)
989 fn changeset_to_changes() {
990 use helix_lsp::{lsp, Client, OffsetEncoding};
991 let text = Rope::from("hello");
992 let mut doc = Document::from(text, None);
993 let view = ViewId::default();
994 doc.set_selection(view, Selection::single(5, 5));
998 let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
999 let old_doc = doc.text().clone();
1000 doc.apply(&transaction, view);
1001 let changes = Client::changeset_to_changes(
1004 transaction.changes(),
1005 OffsetEncoding::Utf8,
1010 &[lsp::TextDocumentContentChangeEvent {
1011 range: Some(lsp::Range::new(
1012 lsp::Position::new(0, 5),
1013 lsp::Position::new(0, 5)
1015 text: " world".into(),
1022 let transaction = transaction.invert(&old_doc);
1023 let old_doc = doc.text().clone();
1024 doc.apply(&transaction, view);
1025 let changes = Client::changeset_to_changes(
1028 transaction.changes(),
1029 OffsetEncoding::Utf8,
1033 // col: 0-based, gaps between chars.
1034 // 0 1 2 3 4 5 6 7 8 9 0 1
1035 // |h|e|l|l|o| |w|o|r|l|d|
1040 &[lsp::TextDocumentContentChangeEvent {
1041 range: Some(lsp::Range::new(
1042 lsp::Position::new(0, 5),
1043 lsp::Position::new(0, 11)
1052 // also tests that changes are layered, positions depend on previous changes.
1054 doc.set_selection(view, Selection::single(0, 5));
1055 let transaction = Transaction::change(
1057 vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
1060 let old_doc = doc.text().clone();
1061 doc.apply(&transaction, view);
1062 let changes = Client::changeset_to_changes(
1065 transaction.changes(),
1066 OffsetEncoding::Utf8,
1077 lsp::TextDocumentContentChangeEvent {
1078 range: Some(lsp::Range::new(
1079 lsp::Position::new(0, 0),
1080 lsp::Position::new(0, 2)
1090 lsp::TextDocumentContentChangeEvent {
1091 range: Some(lsp::Range::new(
1092 lsp::Position::new(0, 4),
1093 lsp::Position::new(0, 6)
1103 fn test_line_ending() {
1105 Document::default().text().to_string(),
1106 DEFAULT_LINE_ENDING.as_str()
1110 macro_rules! test_decode {
1111 ($label:expr, $label_override:expr) => {
1112 let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
1113 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1114 let path = base_path.join(format!("{}_in.txt", $label));
1115 let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
1116 assert!(path.exists());
1117 assert!(ref_path.exists());
1119 let mut file = std::fs::File::open(path).unwrap();
1120 let text = from_reader(&mut file, Some(encoding))
1124 let expectation = std::fs::read_to_string(ref_path).unwrap();
1125 assert_eq!(text[..], expectation[..]);
1129 macro_rules! test_encode {
1130 ($label:expr, $label_override:expr) => {
1131 let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
1132 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
1133 let path = base_path.join(format!("{}_out.txt", $label));
1134 let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
1135 assert!(path.exists());
1136 assert!(ref_path.exists());
1138 let text = Rope::from_str(&std::fs::read_to_string(path).unwrap());
1139 let mut buf: Vec<u8> = Vec::new();
1140 helix_lsp::block_on(to_writer(&mut buf, encoding, &text)).unwrap();
1142 let expectation = std::fs::read(ref_path).unwrap();
1143 assert_eq!(buf, expectation);
1147 macro_rules! test_decode_fn {
1148 ($name:ident, $label:expr, $label_override:expr) => {
1151 test_decode!($label, $label_override);
1154 ($name:ident, $label:expr) => {
1157 test_decode!($label, $label);
1162 macro_rules! test_encode_fn {
1163 ($name:ident, $label:expr, $label_override:expr) => {
1166 test_encode!($label, $label_override);
1169 ($name:ident, $label:expr) => {
1172 test_encode!($label, $label);
1177 test_decode_fn!(test_big5_decode, "big5");
1178 test_encode_fn!(test_big5_encode, "big5");
1179 test_decode_fn!(test_euc_kr_decode, "euc_kr", "EUC-KR");
1180 test_encode_fn!(test_euc_kr_encode, "euc_kr", "EUC-KR");
1181 test_decode_fn!(test_gb18030_decode, "gb18030");
1182 test_encode_fn!(test_gb18030_encode, "gb18030");
1183 test_decode_fn!(test_iso_2022_jp_decode, "iso_2022_jp", "ISO-2022-JP");
1184 test_encode_fn!(test_iso_2022_jp_encode, "iso_2022_jp", "ISO-2022-JP");
1185 test_decode_fn!(test_jis0208_decode, "jis0208", "EUC-JP");
1186 test_encode_fn!(test_jis0208_encode, "jis0208", "EUC-JP");
1187 test_decode_fn!(test_jis0212_decode, "jis0212", "EUC-JP");
1188 test_decode_fn!(test_shift_jis_decode, "shift_jis");
1189 test_encode_fn!(test_shift_jis_encode, "shift_jis");
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list