|
|
@ -1,4 +1,4 @@
|
|
|
|
use anyhow::{anyhow, Context, Error};
|
|
|
|
use anyhow::{anyhow, bail, Context, Error};
|
|
|
|
use serde::de::{self, Deserialize, Deserializer};
|
|
|
|
use serde::de::{self, Deserialize, Deserializer};
|
|
|
|
use serde::Serialize;
|
|
|
|
use serde::Serialize;
|
|
|
|
use std::cell::Cell;
|
|
|
|
use std::cell::Cell;
|
|
|
@ -11,7 +11,7 @@ use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
|
|
use helix_core::{
|
|
|
|
use helix_core::{
|
|
|
|
encoding,
|
|
|
|
encoding,
|
|
|
|
history::History,
|
|
|
|
history::{History, UndoKind},
|
|
|
|
indent::{auto_detect_indent_style, IndentStyle},
|
|
|
|
indent::{auto_detect_indent_style, IndentStyle},
|
|
|
|
line_ending::auto_detect_line_ending,
|
|
|
|
line_ending::auto_detect_line_ending,
|
|
|
|
syntax::{self, LanguageConfiguration},
|
|
|
|
syntax::{self, LanguageConfiguration},
|
|
|
@ -54,7 +54,7 @@ impl FromStr for Mode {
|
|
|
|
"normal" => Ok(Mode::Normal),
|
|
|
|
"normal" => Ok(Mode::Normal),
|
|
|
|
"select" => Ok(Mode::Select),
|
|
|
|
"select" => Ok(Mode::Select),
|
|
|
|
"insert" => Ok(Mode::Insert),
|
|
|
|
"insert" => Ok(Mode::Insert),
|
|
|
|
_ => Err(anyhow!("Invalid mode '{}'", s)),
|
|
|
|
_ => bail!("Invalid mode '{}'", s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -396,7 +396,7 @@ impl Document {
|
|
|
|
/// The same as [`format`], but only returns formatting changes if auto-formatting
|
|
|
|
/// The same as [`format`], but only returns formatting changes if auto-formatting
|
|
|
|
/// is configured.
|
|
|
|
/// is configured.
|
|
|
|
pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
|
|
|
pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
|
|
|
if self.language_config().map(|c| c.auto_format) == Some(true) {
|
|
|
|
if self.language_config()?.auto_format {
|
|
|
|
self.format()
|
|
|
|
self.format()
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
None
|
|
|
@ -406,30 +406,27 @@ impl Document {
|
|
|
|
/// If supported, returns the changes that should be applied to this document in order
|
|
|
|
/// If supported, returns the changes that should be applied to this document in order
|
|
|
|
/// to format it nicely.
|
|
|
|
/// to format it nicely.
|
|
|
|
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
|
|
|
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
|
|
|
|
if let Some(language_server) = self.language_server() {
|
|
|
|
let language_server = self.language_server()?;
|
|
|
|
let text = self.text.clone();
|
|
|
|
let text = self.text.clone();
|
|
|
|
let offset_encoding = language_server.offset_encoding();
|
|
|
|
let offset_encoding = language_server.offset_encoding();
|
|
|
|
let request = language_server.text_document_formatting(
|
|
|
|
let request = language_server.text_document_formatting(
|
|
|
|
self.identifier(),
|
|
|
|
self.identifier(),
|
|
|
|
lsp::FormattingOptions::default(),
|
|
|
|
lsp::FormattingOptions::default(),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)?;
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
|
|
let fut = async move {
|
|
|
|
let fut = async move {
|
|
|
|
let edits = request.await.unwrap_or_else(|e| {
|
|
|
|
let edits = request.await.unwrap_or_else(|e| {
|
|
|
|
log::warn!("LSP formatting failed: {}", e);
|
|
|
|
log::warn!("LSP formatting failed: {}", e);
|
|
|
|
Default::default()
|
|
|
|
Default::default()
|
|
|
|
});
|
|
|
|
});
|
|
|
|
LspFormatting {
|
|
|
|
LspFormatting {
|
|
|
|
doc: text,
|
|
|
|
doc: text,
|
|
|
|
edits,
|
|
|
|
edits,
|
|
|
|
offset_encoding,
|
|
|
|
offset_encoding,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
Some(fut)
|
|
|
|
Some(fut)
|
|
|
|
} else {
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
|
|
|
|
pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
|
|
|
@ -473,9 +470,7 @@ impl Document {
|
|
|
|
if let Some(parent) = path.parent() {
|
|
|
|
if let Some(parent) = path.parent() {
|
|
|
|
// TODO: display a prompt asking the user if the directories should be created
|
|
|
|
// TODO: display a prompt asking the user if the directories should be created
|
|
|
|
if !parent.exists() {
|
|
|
|
if !parent.exists() {
|
|
|
|
return Err(Error::msg(
|
|
|
|
bail!("can't save file, parent directory does not exist");
|
|
|
|
"can't save file, parent directory does not exist",
|
|
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -522,8 +517,7 @@ impl Document {
|
|
|
|
/// line ending.
|
|
|
|
/// line ending.
|
|
|
|
pub fn detect_indent_and_line_ending(&mut self) {
|
|
|
|
pub fn detect_indent_and_line_ending(&mut self) {
|
|
|
|
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
|
|
|
|
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
|
|
|
|
self.language
|
|
|
|
self.language_config()
|
|
|
|
.as_ref()
|
|
|
|
|
|
|
|
.and_then(|config| config.indent.as_ref())
|
|
|
|
.and_then(|config| config.indent.as_ref())
|
|
|
|
.map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
|
|
|
|
.map_or(DEFAULT_INDENT, |config| IndentStyle::from_str(&config.unit))
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -537,7 +531,7 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
// If there is no path or the path no longer exists.
|
|
|
|
// If there is no path or the path no longer exists.
|
|
|
|
if path.is_none() {
|
|
|
|
if path.is_none() {
|
|
|
|
return Err(anyhow!("can't find file to reload from"));
|
|
|
|
bail!("can't find file to reload from");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut file = std::fs::File::open(path.unwrap())?;
|
|
|
|
let mut file = std::fs::File::open(path.unwrap())?;
|
|
|
@ -558,10 +552,8 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
|
|
|
|
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
|
|
|
|
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
|
|
|
|
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
|
|
|
|
match encoding::Encoding::for_label(label.as_bytes()) {
|
|
|
|
self.encoding = encoding::Encoding::for_label(label.as_bytes())
|
|
|
|
Some(encoding) => self.encoding = encoding,
|
|
|
|
.ok_or_else(|| anyhow!("unknown encoding"))?;
|
|
|
|
None => return Err(anyhow::anyhow!("unknown encoding")),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -646,7 +638,6 @@ impl Document {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set modified since accessed
|
|
|
|
|
|
|
|
self.modified_since_accessed = true;
|
|
|
|
self.modified_since_accessed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -689,7 +680,7 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(notify) = notify {
|
|
|
|
if let Some(notify) = notify {
|
|
|
|
tokio::spawn(notify);
|
|
|
|
tokio::spawn(notify);
|
|
|
|
} //.expect("failed to emit textDocument/didChange");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
success
|
|
|
|
success
|
|
|
@ -717,11 +708,11 @@ impl Document {
|
|
|
|
success
|
|
|
|
success
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
|
|
|
|
fn undo_redo_impl(&mut self, view_id: ViewId, undo: bool) -> bool {
|
|
|
|
pub fn undo(&mut self, view_id: ViewId) -> bool {
|
|
|
|
|
|
|
|
let mut history = self.history.take();
|
|
|
|
let mut history = self.history.take();
|
|
|
|
let success = if let Some(transaction) = history.undo() {
|
|
|
|
let txn = if undo { history.undo() } else { history.redo() };
|
|
|
|
self.apply_impl(transaction, view_id)
|
|
|
|
let success = if let Some(txn) = txn {
|
|
|
|
|
|
|
|
self.apply_impl(txn, view_id)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
false
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -734,21 +725,14 @@ impl Document {
|
|
|
|
success
|
|
|
|
success
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
|
|
|
|
|
|
|
|
pub fn undo(&mut self, view_id: ViewId) -> bool {
|
|
|
|
|
|
|
|
self.undo_redo_impl(view_id, true)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Redo the last modification to the [`Document`]. Returns whether the redo was sucessful.
|
|
|
|
/// Redo the last modification to the [`Document`]. Returns whether the redo was sucessful.
|
|
|
|
pub fn redo(&mut self, view_id: ViewId) -> bool {
|
|
|
|
pub fn redo(&mut self, view_id: ViewId) -> bool {
|
|
|
|
let mut history = self.history.take();
|
|
|
|
self.undo_redo_impl(view_id, false)
|
|
|
|
let success = if let Some(transaction) = history.redo() {
|
|
|
|
|
|
|
|
self.apply_impl(transaction, view_id)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
self.history.set(history);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if success {
|
|
|
|
|
|
|
|
// reset changeset to fix len
|
|
|
|
|
|
|
|
self.changes = ChangeSet::new(self.text());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
success
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn savepoint(&mut self) {
|
|
|
|
pub fn savepoint(&mut self) {
|
|
|
@ -761,9 +745,12 @@ impl Document {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Undo modifications to the [`Document`] according to `uk`.
|
|
|
|
fn earlier_later_impl(&mut self, view_id: ViewId, uk: UndoKind, earlier: bool) -> bool {
|
|
|
|
pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool {
|
|
|
|
let txns = if earlier {
|
|
|
|
let txns = self.history.get_mut().earlier(uk);
|
|
|
|
self.history.get_mut().earlier(uk)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
self.history.get_mut().later(uk)
|
|
|
|
|
|
|
|
};
|
|
|
|
let mut success = false;
|
|
|
|
let mut success = false;
|
|
|
|
for txn in txns {
|
|
|
|
for txn in txns {
|
|
|
|
if self.apply_impl(&txn, view_id) {
|
|
|
|
if self.apply_impl(&txn, view_id) {
|
|
|
@ -777,20 +764,14 @@ impl Document {
|
|
|
|
success
|
|
|
|
success
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Undo modifications to the [`Document`] according to `uk`.
|
|
|
|
|
|
|
|
pub fn earlier(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
|
|
|
|
|
|
|
|
self.earlier_later_impl(view_id, uk, true)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Redo modifications to the [`Document`] according to `uk`.
|
|
|
|
/// Redo modifications to the [`Document`] according to `uk`.
|
|
|
|
pub fn later(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) -> bool {
|
|
|
|
pub fn later(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
|
|
|
|
let txns = self.history.get_mut().later(uk);
|
|
|
|
self.earlier_later_impl(view_id, uk, false)
|
|
|
|
let mut success = false;
|
|
|
|
|
|
|
|
for txn in txns {
|
|
|
|
|
|
|
|
if self.apply_impl(&txn, view_id) {
|
|
|
|
|
|
|
|
success = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if success {
|
|
|
|
|
|
|
|
// reset changeset to fix len
|
|
|
|
|
|
|
|
self.changes = ChangeSet::new(self.text());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
success
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Commit pending changes to history
|
|
|
|
/// Commit pending changes to history
|
|
|
@ -850,18 +831,10 @@ impl Document {
|
|
|
|
/// `language-server` configuration, or the document language if no
|
|
|
|
/// `language-server` configuration, or the document language if no
|
|
|
|
/// `language-id` has been specified.
|
|
|
|
/// `language-id` has been specified.
|
|
|
|
pub fn language_id(&self) -> Option<&str> {
|
|
|
|
pub fn language_id(&self) -> Option<&str> {
|
|
|
|
self.language
|
|
|
|
self.language_config()
|
|
|
|
.as_ref()
|
|
|
|
|
|
|
|
.and_then(|config| config.language_server.as_ref())
|
|
|
|
.and_then(|config| config.language_server.as_ref())
|
|
|
|
.and_then(|lsp_config| lsp_config.language_id.as_ref())
|
|
|
|
.and_then(|lsp_config| lsp_config.language_id.as_deref())
|
|
|
|
.map_or_else(
|
|
|
|
.or_else(|| Some(self.language()?.rsplit_once('.')?.1))
|
|
|
|
|| {
|
|
|
|
|
|
|
|
self.language()
|
|
|
|
|
|
|
|
.and_then(|s| s.rsplit_once('.'))
|
|
|
|
|
|
|
|
.map(|(_, language_id)| language_id)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|language_id| Some(language_id.as_str()),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Corresponding [`LanguageConfiguration`].
|
|
|
|
/// Corresponding [`LanguageConfiguration`].
|
|
|
@ -874,18 +847,10 @@ impl Document {
|
|
|
|
self.version
|
|
|
|
self.version
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Language server if it has been initialized.
|
|
|
|
pub fn language_server(&self) -> Option<&helix_lsp::Client> {
|
|
|
|
pub fn language_server(&self) -> Option<&helix_lsp::Client> {
|
|
|
|
let server = self.language_server.as_deref();
|
|
|
|
let server = self.language_server.as_deref()?;
|
|
|
|
let initialized = server
|
|
|
|
server.is_initialized().then(|| server)
|
|
|
|
.map(|server| server.is_initialized())
|
|
|
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// only resolve language_server if it's initialized
|
|
|
|
|
|
|
|
if initialized {
|
|
|
|
|
|
|
|
server
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
@ -896,8 +861,7 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
/// Tab size in columns.
|
|
|
|
/// Tab size in columns.
|
|
|
|
pub fn tab_width(&self) -> usize {
|
|
|
|
pub fn tab_width(&self) -> usize {
|
|
|
|
self.language
|
|
|
|
self.language_config()
|
|
|
|
.as_ref()
|
|
|
|
|
|
|
|
.and_then(|config| config.indent.as_ref())
|
|
|
|
.and_then(|config| config.indent.as_ref())
|
|
|
|
.map_or(4, |config| config.tab_width) // fallback to 4 columns
|
|
|
|
.map_or(4, |config| config.tab_width) // fallback to 4 columns
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -922,7 +886,7 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
/// File path as a URL.
|
|
|
|
/// File path as a URL.
|
|
|
|
pub fn url(&self) -> Option<Url> {
|
|
|
|
pub fn url(&self) -> Option<Url> {
|
|
|
|
self.path().map(|path| Url::from_file_path(path).unwrap())
|
|
|
|
Url::from_file_path(self.path()?).ok()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
@ -945,10 +909,6 @@ impl Document {
|
|
|
|
.map(helix_core::path::get_relative_path)
|
|
|
|
.map(helix_core::path::get_relative_path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
|
|
|
|
|
|
|
|
// self.state.doc.slice
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// transact(Fn) ?
|
|
|
|
// transact(Fn) ?
|
|
|
|
|
|
|
|
|
|
|
|
// -- LSP methods
|
|
|
|
// -- LSP methods
|
|
|
@ -969,7 +929,6 @@ impl Document {
|
|
|
|
|
|
|
|
|
|
|
|
pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
|
|
|
|
pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
|
|
|
|
self.diagnostics = diagnostics;
|
|
|
|
self.diagnostics = diagnostics;
|
|
|
|
// sort by range
|
|
|
|
|
|
|
|
self.diagnostics
|
|
|
|
self.diagnostics
|
|
|
|
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
|
|
|
.sort_unstable_by_key(|diagnostic| diagnostic.range);
|
|
|
|
}
|
|
|
|
}
|
|
|
|