mirror of https://github.com/helix-editor/helix
Merge 2108ec3132
into 4281228da3
commit
049b49d322
|
@ -80,6 +80,7 @@
|
||||||
| `search_selection_detect_word_boundaries` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | normal: `` * ``, select: `` * `` |
|
| `search_selection_detect_word_boundaries` | Use current selection as the search pattern, automatically wrapping with `\b` on word boundaries | normal: `` * ``, select: `` * `` |
|
||||||
| `make_search_word_bounded` | Modify current search to make it word bounded | |
|
| `make_search_word_bounded` | Modify current search to make it word bounded | |
|
||||||
| `global_search` | Global search in workspace folder | normal: `` <space>/ ``, select: `` <space>/ `` |
|
| `global_search` | Global search in workspace folder | normal: `` <space>/ ``, select: `` <space>/ `` |
|
||||||
|
| `global_refactor` | Global refactoring in workspace folder | |
|
||||||
| `extend_line` | Select current line, if already selected, extend to another line based on the anchor | |
|
| `extend_line` | Select current line, if already selected, extend to another line based on the anchor | |
|
||||||
| `extend_line_below` | Select current line, if already selected, extend to next line | normal: `` x ``, select: `` x `` |
|
| `extend_line_below` | Select current line, if already selected, extend to next line | normal: `` x ``, select: `` x `` |
|
||||||
| `extend_line_above` | Select current line, if already selected, extend to previous line | |
|
| `extend_line_above` | Select current line, if already selected, extend to previous line | |
|
||||||
|
|
|
@ -44,7 +44,7 @@ use helix_core::{
|
||||||
Selection, SmallVec, Syntax, Tendril, Transaction,
|
Selection, SmallVec, Syntax, Tendril, Transaction,
|
||||||
};
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
|
document::{FormatterError, Mode, REFACTOR_BUFFER_NAME, SCRATCH_BUFFER_NAME},
|
||||||
editor::Action,
|
editor::Action,
|
||||||
info::Info,
|
info::Info,
|
||||||
input::KeyEvent,
|
input::KeyEvent,
|
||||||
|
@ -380,6 +380,7 @@ impl MappableCommand {
|
||||||
search_selection_detect_word_boundaries, "Use current selection as the search pattern, automatically wrapping with `\\b` on word boundaries",
|
search_selection_detect_word_boundaries, "Use current selection as the search pattern, automatically wrapping with `\\b` on word boundaries",
|
||||||
make_search_word_bounded, "Modify current search to make it word bounded",
|
make_search_word_bounded, "Modify current search to make it word bounded",
|
||||||
global_search, "Global search in workspace folder",
|
global_search, "Global search in workspace folder",
|
||||||
|
global_refactor, "Global refactoring in workspace folder",
|
||||||
extend_line, "Select current line, if already selected, extend to another line based on the anchor",
|
extend_line, "Select current line, if already selected, extend to another line based on the anchor",
|
||||||
extend_line_below, "Select current line, if already selected, extend to next line",
|
extend_line_below, "Select current line, if already selected, extend to next line",
|
||||||
extend_line_above, "Select current line, if already selected, extend to previous line",
|
extend_line_above, "Select current line, if already selected, extend to previous line",
|
||||||
|
@ -2452,13 +2453,15 @@ fn global_search(cx: &mut Context) {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
/// 0 indexed lines
|
/// 0 indexed lines
|
||||||
line_num: usize,
|
line_num: usize,
|
||||||
|
line_content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileResult {
|
impl FileResult {
|
||||||
fn new(path: &Path, line_num: usize) -> Self {
|
fn new(path: &Path, line_num: usize, line_content: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
line_num,
|
line_num,
|
||||||
|
line_content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2583,9 +2586,13 @@ fn global_search(cx: &mut Context) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stop = false;
|
let mut stop = false;
|
||||||
let sink = sinks::UTF8(|line_num, _line_content| {
|
let sink = sinks::UTF8(|line_num, line_content| {
|
||||||
stop = injector
|
stop = injector
|
||||||
.push(FileResult::new(entry.path(), line_num as usize - 1))
|
.push(FileResult::new(
|
||||||
|
entry.path(),
|
||||||
|
line_num as usize - 1,
|
||||||
|
line_content.into(),
|
||||||
|
))
|
||||||
.is_err();
|
.is_err();
|
||||||
|
|
||||||
Ok(!stop)
|
Ok(!stop)
|
||||||
|
@ -2670,12 +2677,127 @@ fn global_search(cx: &mut Context) {
|
||||||
.with_preview(|_editor, FileResult { path, line_num, .. }| {
|
.with_preview(|_editor, FileResult { path, line_num, .. }| {
|
||||||
Some((path.as_path().into(), Some((*line_num, *line_num))))
|
Some((path.as_path().into(), Some((*line_num, *line_num))))
|
||||||
})
|
})
|
||||||
|
.with_refactor(move |cx, results: Vec<&FileResult>| {
|
||||||
|
if results.is_empty() {
|
||||||
|
cx.editor.set_status("No matches found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut matches: HashMap<PathBuf, Vec<(usize, String)>> = HashMap::new();
|
||||||
|
let mut lines: Vec<(PathBuf, usize)> = vec![];
|
||||||
|
|
||||||
|
for result in results {
|
||||||
|
let path = result.path.clone();
|
||||||
|
let line = result.line_num;
|
||||||
|
let text = result.line_content.clone();
|
||||||
|
|
||||||
|
lines.push((path.clone(), line));
|
||||||
|
matches.entry(path).or_default().push((line, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut doc_text = Rope::new();
|
||||||
|
let mut line_map = HashMap::new();
|
||||||
|
let language_id = doc!(cx.editor).language_id().map(String::from);
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
for (key, value) in &matches {
|
||||||
|
for (line, text) in value {
|
||||||
|
doc_text.insert(doc_text.len_chars(), text);
|
||||||
|
line_map.insert((key.clone(), *line), count);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doc_text.split_off(doc_text.len_chars().saturating_sub(1));
|
||||||
|
let mut doc = Document::refactor(
|
||||||
|
doc_text,
|
||||||
|
matches,
|
||||||
|
line_map,
|
||||||
|
lines,
|
||||||
|
// TODO: actually learn how to detect encoding
|
||||||
|
None,
|
||||||
|
cx.editor.config.clone(),
|
||||||
|
cx.editor.syn_loader.clone(),
|
||||||
|
);
|
||||||
|
if let Some(language_id) = language_id {
|
||||||
|
doc.set_language_by_language_id(&language_id, &cx.editor.syn_loader.load())
|
||||||
|
.ok();
|
||||||
|
};
|
||||||
|
cx.editor.new_file_from_document(Action::Replace, doc);
|
||||||
|
})
|
||||||
.with_history_register(Some(reg))
|
.with_history_register(Some(reg))
|
||||||
.with_dynamic_query(get_files, Some(275));
|
.with_dynamic_query(get_files, Some(275));
|
||||||
|
|
||||||
cx.push_layer(Box::new(overlaid(picker)));
|
cx.push_layer(Box::new(overlaid(picker)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn global_refactor(cx: &mut Context) {
|
||||||
|
let document_type = doc!(cx.editor).document_type.clone();
|
||||||
|
|
||||||
|
match &document_type {
|
||||||
|
helix_view::document::DocumentType::File => (),
|
||||||
|
helix_view::document::DocumentType::Refactor {
|
||||||
|
matches, line_map, ..
|
||||||
|
} => {
|
||||||
|
let line_ending: LineEnding = cx.editor.config.load().default_line_ending.into();
|
||||||
|
let refactor_id = doc!(cx.editor).id();
|
||||||
|
let replace_text = doc!(cx.editor).text().clone();
|
||||||
|
let view = view!(cx.editor).clone();
|
||||||
|
let mut documents: usize = 0;
|
||||||
|
let mut count: usize = 0;
|
||||||
|
for (key, value) in matches {
|
||||||
|
let mut changes = Vec::<(usize, usize, String)>::new();
|
||||||
|
for (line, text) in value {
|
||||||
|
if let Some(re_line) = line_map.get(&(key.clone(), *line)) {
|
||||||
|
let mut replace = replace_text
|
||||||
|
.get_line(*re_line)
|
||||||
|
.unwrap_or_else(|| "".into())
|
||||||
|
.to_string()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
replace = replace
|
||||||
|
.strip_suffix(line_ending.as_str())
|
||||||
|
.unwrap_or(&replace)
|
||||||
|
.to_string();
|
||||||
|
replace.push_str(line_ending.as_str());
|
||||||
|
if text != &replace {
|
||||||
|
changes.push((*line, text.chars().count(), replace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changes.is_empty() {
|
||||||
|
if let Some(doc) = cx
|
||||||
|
.editor
|
||||||
|
.open(key, Action::Load)
|
||||||
|
.ok()
|
||||||
|
.and_then(|id| cx.editor.document_mut(id))
|
||||||
|
{
|
||||||
|
documents += 1;
|
||||||
|
let mut applychanges = Vec::<(usize, usize, Option<Tendril>)>::new();
|
||||||
|
for (line, length, text) in changes {
|
||||||
|
if doc.text().len_lines() > line {
|
||||||
|
let start = doc.text().line_to_char(line);
|
||||||
|
applychanges.push((
|
||||||
|
start,
|
||||||
|
start + length,
|
||||||
|
Some(Tendril::from(text.to_string())),
|
||||||
|
));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let transaction = Transaction::change(doc.text(), applychanges.into_iter());
|
||||||
|
doc.apply(&transaction, view.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.editor.set_status(format!(
|
||||||
|
"Refactored {} documents, {} lines changed.",
|
||||||
|
documents, count
|
||||||
|
));
|
||||||
|
cx.editor.close_document(refactor_id, true).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Extend {
|
enum Extend {
|
||||||
Above,
|
Above,
|
||||||
Below,
|
Below,
|
||||||
|
@ -3153,6 +3275,7 @@ fn buffer_picker(cx: &mut Context) {
|
||||||
is_modified: bool,
|
is_modified: bool,
|
||||||
is_current: bool,
|
is_current: bool,
|
||||||
focused_at: std::time::Instant,
|
focused_at: std::time::Instant,
|
||||||
|
is_refactor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_meta = |doc: &Document| BufferMeta {
|
let new_meta = |doc: &Document| BufferMeta {
|
||||||
|
@ -3161,6 +3284,14 @@ fn buffer_picker(cx: &mut Context) {
|
||||||
is_modified: doc.is_modified(),
|
is_modified: doc.is_modified(),
|
||||||
is_current: doc.id() == current,
|
is_current: doc.id() == current,
|
||||||
focused_at: doc.focused_at,
|
focused_at: doc.focused_at,
|
||||||
|
is_refactor: matches!(
|
||||||
|
&doc.document_type,
|
||||||
|
helix_view::document::DocumentType::Refactor {
|
||||||
|
matches: _,
|
||||||
|
line_map: _,
|
||||||
|
lines: _,
|
||||||
|
}
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut items = cx
|
let mut items = cx
|
||||||
|
@ -3186,6 +3317,9 @@ fn buffer_picker(cx: &mut Context) {
|
||||||
flags.into()
|
flags.into()
|
||||||
}),
|
}),
|
||||||
PickerColumn::new("path", |meta: &BufferMeta, _| {
|
PickerColumn::new("path", |meta: &BufferMeta, _| {
|
||||||
|
if meta.is_refactor {
|
||||||
|
return REFACTOR_BUFFER_NAME.into();
|
||||||
|
}
|
||||||
let path = meta
|
let path = meta
|
||||||
.path
|
.path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|
|
@ -583,13 +583,20 @@ impl EditorView {
|
||||||
let current_doc = view!(editor).doc;
|
let current_doc = view!(editor).doc;
|
||||||
|
|
||||||
for doc in editor.documents() {
|
for doc in editor.documents() {
|
||||||
let fname = doc
|
let fname = match &doc.document_type {
|
||||||
|
helix_view::document::DocumentType::File => doc
|
||||||
.path()
|
.path()
|
||||||
.unwrap_or(&scratch)
|
.unwrap_or(&scratch)
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default(),
|
||||||
|
helix_view::document::DocumentType::Refactor {
|
||||||
|
matches: _,
|
||||||
|
line_map: _,
|
||||||
|
lines: _,
|
||||||
|
} => helix_view::document::REFACTOR_BUFFER_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
let style = if current_doc == doc.id() {
|
let style = if current_doc == doc.id() {
|
||||||
bufferline_active
|
bufferline_active
|
||||||
|
|
|
@ -266,6 +266,7 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
|
||||||
/// Given an item in the picker, return the file path and line number to display.
|
/// Given an item in the picker, return the file path and line number to display.
|
||||||
file_fn: Option<FileCallback<T>>,
|
file_fn: Option<FileCallback<T>>,
|
||||||
/// An event handler for syntax highlighting the currently previewed file.
|
/// An event handler for syntax highlighting the currently previewed file.
|
||||||
|
refactor_fn: RefactorCallback<T>,
|
||||||
preview_highlight_handler: Sender<Arc<Path>>,
|
preview_highlight_handler: Sender<Arc<Path>>,
|
||||||
dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
|
dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
|
||||||
}
|
}
|
||||||
|
@ -382,6 +383,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||||
truncate_start: true,
|
truncate_start: true,
|
||||||
show_preview: true,
|
show_preview: true,
|
||||||
callback_fn: Box::new(callback_fn),
|
callback_fn: Box::new(callback_fn),
|
||||||
|
refactor_fn: None,
|
||||||
completion_height: 0,
|
completion_height: 0,
|
||||||
widths,
|
widths,
|
||||||
preview_cache: HashMap::new(),
|
preview_cache: HashMap::new(),
|
||||||
|
@ -419,6 +421,11 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_refactor(mut self, quickfix_fn: impl Fn(&mut Context, Vec<&T>) + 'static) -> Self {
|
||||||
|
self.refactor_fn = Some(Box::new(quickfix_fn));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_history_register(mut self, history_register: Option<char>) -> Self {
|
pub fn with_history_register(mut self, history_register: Option<char>) -> Self {
|
||||||
self.prompt.with_history_register(history_register);
|
self.prompt.with_history_register(history_register);
|
||||||
self
|
self
|
||||||
|
@ -490,6 +497,15 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||||
.map(|item| item.data)
|
.map(|item| item.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_list(&self) -> Vec<&T> {
|
||||||
|
let matcher = self.matcher.snapshot();
|
||||||
|
let total = matcher.matched_item_count();
|
||||||
|
matcher
|
||||||
|
.matched_items(0..total)
|
||||||
|
.map(|item| item.data)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn primary_query(&self) -> Arc<str> {
|
fn primary_query(&self) -> Arc<str> {
|
||||||
self.query
|
self.query
|
||||||
.get(&self.columns[self.primary_column].name)
|
.get(&self.columns[self.primary_column].name)
|
||||||
|
@ -1124,6 +1140,15 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
|
||||||
ctrl!('t') => {
|
ctrl!('t') => {
|
||||||
self.toggle_preview();
|
self.toggle_preview();
|
||||||
}
|
}
|
||||||
|
ctrl!('q') => {
|
||||||
|
if self.selection().is_some() {
|
||||||
|
if let Some(quickfix) = &self.refactor_fn {
|
||||||
|
let items = self.get_list();
|
||||||
|
(quickfix)(ctx, items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return close_fn(self);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.prompt_handle_event(event, ctx);
|
self.prompt_handle_event(event, ctx);
|
||||||
}
|
}
|
||||||
|
@ -1168,3 +1193,4 @@ impl<T: 'static + Send + Sync, D> Drop for Picker<T, D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PickerCallback<T> = Box<dyn Fn(&mut Context, &T, Action)>;
|
type PickerCallback<T> = Box<dyn Fn(&mut Context, &T, Action)>;
|
||||||
|
type RefactorCallback<T> = Option<Box<dyn Fn(&mut Context, Vec<&T>)>>;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use helix_core::{coords_at_pos, encoding, Position};
|
||||||
use helix_lsp::lsp::DiagnosticSeverity;
|
use helix_lsp::lsp::DiagnosticSeverity;
|
||||||
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
use helix_view::document::DEFAULT_LANGUAGE_NAME;
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
document::{Mode, REFACTOR_BUFFER_NAME, SCRATCH_BUFFER_NAME},
|
||||||
graphics::Rect,
|
graphics::Rect,
|
||||||
theme::Style,
|
theme::Style,
|
||||||
Document, Editor, View,
|
Document, Editor, View,
|
||||||
|
@ -450,12 +450,21 @@ where
|
||||||
F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
|
F: Fn(&mut RenderContext<'a>, Span<'a>) + Copy,
|
||||||
{
|
{
|
||||||
let title = {
|
let title = {
|
||||||
|
match &context.doc.document_type {
|
||||||
|
helix_view::document::DocumentType::File => {
|
||||||
let rel_path = context.doc.relative_path();
|
let rel_path = context.doc.relative_path();
|
||||||
let path = rel_path
|
let path = rel_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| p.to_string_lossy())
|
.map(|p| p.to_string_lossy())
|
||||||
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
|
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
|
||||||
format!(" {} ", path)
|
format!(" {} ", path)
|
||||||
|
}
|
||||||
|
helix_view::document::DocumentType::Refactor {
|
||||||
|
matches: _,
|
||||||
|
line_map: _,
|
||||||
|
lines: _,
|
||||||
|
} => REFACTOR_BUFFER_NAME.into(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
write(context, title.into());
|
write(context, title.into());
|
||||||
|
|
|
@ -61,6 +61,8 @@ pub const DEFAULT_LANGUAGE_NAME: &str = "text";
|
||||||
|
|
||||||
pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";
|
pub const SCRATCH_BUFFER_NAME: &str = "[scratch]";
|
||||||
|
|
||||||
|
pub const REFACTOR_BUFFER_NAME: &str = "[refactor]";
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
|
@ -138,6 +140,19 @@ pub enum DocumentOpenError {
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DocumentType {
|
||||||
|
File,
|
||||||
|
Refactor {
|
||||||
|
// Filepath: list of (line_num, text)
|
||||||
|
matches: HashMap<PathBuf, Vec<(usize, String)>>,
|
||||||
|
// (Filepath, line_num): line_num (in buffer)
|
||||||
|
line_map: HashMap<(PathBuf, usize), usize>,
|
||||||
|
// List of (line_num, Filepath) in buffer order
|
||||||
|
lines: Vec<(PathBuf, usize)>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
pub(crate) id: DocumentId,
|
pub(crate) id: DocumentId,
|
||||||
text: Rope,
|
text: Rope,
|
||||||
|
@ -210,6 +225,7 @@ pub struct Document {
|
||||||
// large refactor that would make `&mut Editor` available on the `DocumentDidChange` event.
|
// large refactor that would make `&mut Editor` available on the `DocumentDidChange` event.
|
||||||
pub color_swatch_controller: TaskController,
|
pub color_swatch_controller: TaskController,
|
||||||
|
|
||||||
|
pub document_type: DocumentType,
|
||||||
// NOTE: this field should eventually go away - we should use the Editor's syn_loader instead
|
// NOTE: this field should eventually go away - we should use the Editor's syn_loader instead
|
||||||
// of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the
|
// of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the
|
||||||
// `ArcSwap` directly.
|
// `ArcSwap` directly.
|
||||||
|
@ -728,6 +744,66 @@ impl Document {
|
||||||
color_swatches: None,
|
color_swatches: None,
|
||||||
color_swatch_controller: TaskController::new(),
|
color_swatch_controller: TaskController::new(),
|
||||||
syn_loader,
|
syn_loader,
|
||||||
|
document_type: DocumentType::File,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refactor(
|
||||||
|
text: Rope,
|
||||||
|
matches: HashMap<PathBuf, Vec<(usize, String)>>,
|
||||||
|
line_map: HashMap<(PathBuf, usize), usize>,
|
||||||
|
lines: Vec<(PathBuf, usize)>,
|
||||||
|
encoding_with_bom_info: Option<(&'static Encoding, bool)>,
|
||||||
|
config: Arc<dyn DynAccess<Config>>,
|
||||||
|
syn_loader: Arc<ArcSwap<syntax::Loader>>,
|
||||||
|
) -> Self {
|
||||||
|
let (encoding, has_bom) = encoding_with_bom_info.unwrap_or((encoding::UTF_8, false));
|
||||||
|
let line_ending = config.load().default_line_ending.into();
|
||||||
|
let changes = ChangeSet::new(text.slice(..));
|
||||||
|
let old_state = None;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: DocumentId::default(),
|
||||||
|
active_snippet: None,
|
||||||
|
path: None,
|
||||||
|
relative_path: OnceCell::new(),
|
||||||
|
encoding,
|
||||||
|
has_bom,
|
||||||
|
text,
|
||||||
|
selections: HashMap::default(),
|
||||||
|
inlay_hints: HashMap::default(),
|
||||||
|
inlay_hints_oudated: false,
|
||||||
|
view_data: Default::default(),
|
||||||
|
indent_style: DEFAULT_INDENT,
|
||||||
|
editor_config: EditorConfig::default(),
|
||||||
|
line_ending,
|
||||||
|
restore_cursor: false,
|
||||||
|
syntax: None,
|
||||||
|
language: None,
|
||||||
|
changes,
|
||||||
|
old_state,
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
version: 0,
|
||||||
|
history: Cell::new(History::default()),
|
||||||
|
savepoints: Vec::new(),
|
||||||
|
last_saved_time: SystemTime::now(),
|
||||||
|
last_saved_revision: 0,
|
||||||
|
modified_since_accessed: false,
|
||||||
|
language_servers: HashMap::new(),
|
||||||
|
diff_handle: None,
|
||||||
|
config,
|
||||||
|
version_control_head: None,
|
||||||
|
focused_at: std::time::Instant::now(),
|
||||||
|
readonly: false,
|
||||||
|
jump_labels: HashMap::new(),
|
||||||
|
color_swatches: None,
|
||||||
|
color_swatch_controller: TaskController::new(),
|
||||||
|
syn_loader,
|
||||||
|
document_type: DocumentType::Refactor {
|
||||||
|
matches,
|
||||||
|
line_map,
|
||||||
|
lines,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1731,6 +1807,8 @@ impl Document {
|
||||||
|
|
||||||
/// If there are unsaved modifications.
|
/// If there are unsaved modifications.
|
||||||
pub fn is_modified(&self) -> bool {
|
pub fn is_modified(&self) -> bool {
|
||||||
|
match self.document_type {
|
||||||
|
DocumentType::File => {
|
||||||
let history = self.history.take();
|
let history = self.history.take();
|
||||||
let current_revision = history.current_revision();
|
let current_revision = history.current_revision();
|
||||||
self.history.set(history);
|
self.history.set(history);
|
||||||
|
@ -1742,6 +1820,13 @@ impl Document {
|
||||||
);
|
);
|
||||||
current_revision != self.last_saved_revision || !self.changes.is_empty()
|
current_revision != self.last_saved_revision || !self.changes.is_empty()
|
||||||
}
|
}
|
||||||
|
DocumentType::Refactor {
|
||||||
|
matches: _,
|
||||||
|
line_map: _,
|
||||||
|
lines: _,
|
||||||
|
} => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Save modifications to history, and so [`Self::is_modified`] will return false.
|
/// Save modifications to history, and so [`Self::is_modified`] will return false.
|
||||||
pub fn reset_modified(&mut self) {
|
pub fn reset_modified(&mut self) {
|
||||||
|
|
|
@ -1771,7 +1771,7 @@ impl Editor {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_file_from_document(&mut self, action: Action, doc: Document) -> DocumentId {
|
pub fn new_file_from_document(&mut self, action: Action, doc: Document) -> DocumentId {
|
||||||
let id = self.new_document(doc);
|
let id = self.new_document(doc);
|
||||||
self.switch(id, action);
|
self.switch(id, action);
|
||||||
id
|
id
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::fmt::Write;
|
||||||
|
|
||||||
use helix_core::syntax::config::LanguageServerFeature;
|
use helix_core::syntax::config::LanguageServerFeature;
|
||||||
|
|
||||||
|
use crate::document::DocumentType;
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::GutterType,
|
editor::GutterType,
|
||||||
graphics::{Style, UnderlineStyle},
|
graphics::{Style, UnderlineStyle},
|
||||||
|
@ -16,6 +17,8 @@ pub type GutterFn<'doc> = Box<dyn FnMut(usize, bool, bool, &mut String) -> Optio
|
||||||
pub type Gutter =
|
pub type Gutter =
|
||||||
for<'doc> fn(&'doc Editor, &'doc Document, &View, &Theme, bool, usize) -> GutterFn<'doc>;
|
for<'doc> fn(&'doc Editor, &'doc Document, &View, &Theme, bool, usize) -> GutterFn<'doc>;
|
||||||
|
|
||||||
|
const REFACTOR_GUTTER_WIDTH: usize = 20;
|
||||||
|
|
||||||
impl GutterType {
|
impl GutterType {
|
||||||
pub fn style<'doc>(
|
pub fn style<'doc>(
|
||||||
self,
|
self,
|
||||||
|
@ -151,10 +154,6 @@ pub fn line_numbers<'doc>(
|
||||||
|
|
||||||
let last_line_in_view = view.estimate_last_doc_line(doc);
|
let last_line_in_view = view.estimate_last_doc_line(doc);
|
||||||
|
|
||||||
// Whether to draw the line number for the last line of the
|
|
||||||
// document or not. We only draw it if it's not an empty line.
|
|
||||||
let draw_last = text.line_to_byte(last_line_in_view) < text.len_bytes();
|
|
||||||
|
|
||||||
let linenr = theme.get("ui.linenr");
|
let linenr = theme.get("ui.linenr");
|
||||||
let linenr_select = theme.get("ui.linenr.selected");
|
let linenr_select = theme.get("ui.linenr.selected");
|
||||||
|
|
||||||
|
@ -163,10 +162,14 @@ pub fn line_numbers<'doc>(
|
||||||
.char_to_line(doc.selection(view.id).primary().cursor(text));
|
.char_to_line(doc.selection(view.id).primary().cursor(text));
|
||||||
|
|
||||||
let line_number = editor.config().line_number;
|
let line_number = editor.config().line_number;
|
||||||
let mode = editor.mode;
|
let draw_last = text.line_to_byte(last_line_in_view) < text.len_bytes();
|
||||||
|
|
||||||
Box::new(
|
match &doc.document_type {
|
||||||
|
DocumentType::File => Box::new(
|
||||||
move |line: usize, selected: bool, first_visual_line: bool, out: &mut String| {
|
move |line: usize, selected: bool, first_visual_line: bool, out: &mut String| {
|
||||||
|
// Whether to draw the line number for the last line of the
|
||||||
|
// document or not. We only draw it if it's not an empty line.
|
||||||
|
let mode = editor.mode;
|
||||||
if line == last_line_in_view && !draw_last {
|
if line == last_line_in_view && !draw_last {
|
||||||
write!(out, "{:>1$}", '~', width).unwrap();
|
write!(out, "{:>1$}", '~', width).unwrap();
|
||||||
Some(linenr)
|
Some(linenr)
|
||||||
|
@ -199,7 +202,53 @@ pub fn line_numbers<'doc>(
|
||||||
first_visual_line.then_some(style)
|
first_visual_line.then_some(style)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
DocumentType::Refactor {
|
||||||
|
matches: _,
|
||||||
|
line_map: _,
|
||||||
|
lines,
|
||||||
|
} => Box::new(
|
||||||
|
move |line: usize, selected: bool, first_visual_line: bool, out: &mut String| {
|
||||||
|
if let Some((file_path, line_num)) = lines.get(line) {
|
||||||
|
let gutter = format_path_line(
|
||||||
|
file_path.to_str().unwrap_or("<invalid path>"),
|
||||||
|
line_num + 1,
|
||||||
|
REFACTOR_GUTTER_WIDTH,
|
||||||
|
);
|
||||||
|
write!(out, "{}", gutter).unwrap();
|
||||||
|
} else {
|
||||||
|
write!(out, "{:>1$}", '~', REFACTOR_GUTTER_WIDTH).unwrap();
|
||||||
|
}
|
||||||
|
let style = if selected && is_focused {
|
||||||
|
linenr_select
|
||||||
|
} else {
|
||||||
|
linenr
|
||||||
|
};
|
||||||
|
// Some(style)
|
||||||
|
first_visual_line.then_some(style)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_path_line(path: &str, line: usize, max_width: usize) -> String {
|
||||||
|
let raw = format!("{path}:{line}");
|
||||||
|
|
||||||
|
if raw.len() <= max_width {
|
||||||
|
format!("{:>width$}", raw, width = max_width)
|
||||||
|
} else {
|
||||||
|
let ellipsis = "...";
|
||||||
|
let keep = max_width.saturating_sub(ellipsis.len());
|
||||||
|
let tail = raw
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.take(keep)
|
||||||
|
.collect::<String>()
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.collect::<String>();
|
||||||
|
format!("{:>width$}", format!("{ellipsis}{tail}"), width = max_width)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The width of a "line-numbers" gutter
|
/// The width of a "line-numbers" gutter
|
||||||
|
@ -208,6 +257,9 @@ pub fn line_numbers<'doc>(
|
||||||
/// whether there is content on the last line (the `~` line), and the
|
/// whether there is content on the last line (the `~` line), and the
|
||||||
/// `editor.gutters.line-numbers.min-width` settings.
|
/// `editor.gutters.line-numbers.min-width` settings.
|
||||||
fn line_numbers_width(view: &View, doc: &Document) -> usize {
|
fn line_numbers_width(view: &View, doc: &Document) -> usize {
|
||||||
|
if matches!(doc.document_type, DocumentType::Refactor { .. }) {
|
||||||
|
return REFACTOR_GUTTER_WIDTH;
|
||||||
|
}
|
||||||
let text = doc.text();
|
let text = doc.text();
|
||||||
let last_line = text.len_lines().saturating_sub(1);
|
let last_line = text.len_lines().saturating_sub(1);
|
||||||
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
||||||
|
|
Loading…
Reference in New Issue