Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pickers "v2" #9647

Merged
merged 20 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dae3841
Use an AsyncHook for picker preview highlighting
the-mikedavis Feb 15, 2024
f40fca8
Refactor Picker in terms of columns
the-mikedavis Feb 16, 2024
c4c17c6
Add a special query syntax for Pickers to select columns
the-mikedavis Feb 16, 2024
385b398
Add column configurations for existing pickers
the-mikedavis Sep 3, 2023
53ac833
Replace picker shutdown bool with version number
the-mikedavis Feb 16, 2024
2c9f5b3
Implement Error for InjectorShutdown
the-mikedavis Feb 16, 2024
11f809c
Bump nucleo to v0.4.1
the-mikedavis Feb 21, 2024
9e31ba5
Consolidate DynamicPicker into Picker
the-mikedavis Feb 16, 2024
5622db6
Remove sym_picker helper fun
the-mikedavis Apr 1, 2024
1d023b0
Refactor global_search as a dynamic Picker
the-mikedavis Feb 17, 2024
7b1131a
global_search: Suggest latest '/' register value
the-mikedavis Mar 6, 2024
6492f17
Add a hidden column for the global search line contents
the-mikedavis Mar 24, 2024
6ccbfe9
Request a UI redraw on Drop of an Injector
the-mikedavis Mar 26, 2024
4082820
avoid collecting columns to a temporary vec
pascalkuthe Apr 2, 2024
f4a433f
Convert LSP URIs into custom URIs
the-mikedavis Apr 5, 2024
3906f66
Avoid allocations in Picker file preview callback
the-mikedavis Apr 5, 2024
8555248
Accept 'IntoIterator<Item = T>' for Picker::new options
the-mikedavis Apr 23, 2024
009bbda
Picker: Reset the cursor on prompt change
the-mikedavis Apr 24, 2024
a7777b3
Accept 'IntoIterator<Item = Column<T, D>>' for picker columns
the-mikedavis Apr 24, 2024
9de5f5c
Picker: Highlight the currently active column
the-mikedavis Apr 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ package.helix-term.opt-level = 2

[workspace.dependencies]
tree-sitter = { version = "0.22" }
nucleo = "0.2.0"
nucleo = "0.5.0"
slotmap = "1.0.7"
thiserror = "1.0"

Expand Down
2 changes: 2 additions & 0 deletions book/src/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ These scopes are used for theming the editor interface:
| `ui.bufferline.background` | Style for bufferline background |
| `ui.popup` | Documentation popups (e.g. Space + k) |
| `ui.popup.info` | Prompt for multiple key options |
| `ui.picker.header` | Column names in pickers with multiple columns |
| `ui.picker.header.active` | The column name in pickers with multiple columns where the cursor is entering into. |
| `ui.window` | Borderlines separating splits |
| `ui.help` | Description box for commands |
| `ui.text` | Default text style, command prompts, popup text, etc. |
Expand Down
1 change: 1 addition & 0 deletions helix-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ bitflags = "2.6"
ahash = "0.8.11"
hashbrown = { version = "0.14.5", features = ["raw"] }
dunce = "1.0"
url = "2.5.0"

log = "0.4"
serde = { version = "1.0", features = ["derive"] }
Expand Down
10 changes: 8 additions & 2 deletions helix-core/src/fuzzy.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ops::DerefMut;

use nucleo::pattern::{Atom, AtomKind, CaseMatching};
use nucleo::pattern::{Atom, AtomKind, CaseMatching, Normalization};
use nucleo::Config;
use parking_lot::Mutex;

Expand Down Expand Up @@ -38,6 +38,12 @@ pub fn fuzzy_match<T: AsRef<str>>(
if path {
matcher.config.set_match_paths();
}
let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
let pattern = Atom::new(
pattern,
CaseMatching::Smart,
Normalization::Smart,
AtomKind::Fuzzy,
false,
);
pattern.match_list(items, &mut matcher)
}
3 changes: 3 additions & 0 deletions helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod test;
pub mod text_annotations;
pub mod textobject;
mod transaction;
pub mod uri;
pub mod wrap;

pub mod unicode {
Expand Down Expand Up @@ -66,3 +67,5 @@ pub use diagnostic::Diagnostic;

pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};

pub use uri::Uri;
122 changes: 122 additions & 0 deletions helix-core/src/uri.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::path::{Path, PathBuf};

/// A generic pointer to a file location.
///
/// Currently this type only supports paths to local files.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Uri {
File(PathBuf),
}

impl Uri {
// This clippy allow mirrors url::Url::from_file_path
#[allow(clippy::result_unit_err)]
pub fn to_url(&self) -> Result<url::Url, ()> {
match self {
Uri::File(path) => url::Url::from_file_path(path),
}
}

pub fn as_path(&self) -> Option<&Path> {
match self {
Self::File(path) => Some(path),
}
}

pub fn as_path_buf(self) -> Option<PathBuf> {
match self {
Self::File(path) => Some(path),
}
}
}

impl From<PathBuf> for Uri {
fn from(path: PathBuf) -> Self {
Self::File(path)
}
}

impl TryFrom<Uri> for PathBuf {
type Error = ();

fn try_from(uri: Uri) -> Result<Self, Self::Error> {
match uri {
Uri::File(path) => Ok(path),
}
}
}

#[derive(Debug)]
pub struct UrlConversionError {
source: url::Url,
kind: UrlConversionErrorKind,
}

#[derive(Debug)]
pub enum UrlConversionErrorKind {
UnsupportedScheme,
UnableToConvert,
}

impl std::fmt::Display for UrlConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
UrlConversionErrorKind::UnsupportedScheme => {
write!(f, "unsupported scheme in URL: {}", self.source.scheme())
}
UrlConversionErrorKind::UnableToConvert => {
write!(f, "unable to convert URL to file path: {}", self.source)
}
}
}
}

impl std::error::Error for UrlConversionError {}

fn convert_url_to_uri(url: &url::Url) -> Result<Uri, UrlConversionErrorKind> {
if url.scheme() == "file" {
url.to_file_path()
.map(|path| Uri::File(helix_stdx::path::normalize(path)))
.map_err(|_| UrlConversionErrorKind::UnableToConvert)
} else {
Err(UrlConversionErrorKind::UnsupportedScheme)
}
}

impl TryFrom<url::Url> for Uri {
type Error = UrlConversionError;

fn try_from(url: url::Url) -> Result<Self, Self::Error> {
convert_url_to_uri(&url).map_err(|kind| Self::Error { source: url, kind })
}
}

impl TryFrom<&url::Url> for Uri {
type Error = UrlConversionError;

fn try_from(url: &url::Url) -> Result<Self, Self::Error> {
convert_url_to_uri(url).map_err(|kind| Self::Error {
source: url.clone(),
kind,
})
}
}

#[cfg(test)]
mod test {
use super::*;
use url::Url;

#[test]
fn unknown_scheme() {
let url = Url::parse("csharp:/metadata/foo/bar/Baz.cs").unwrap();
assert!(matches!(
Uri::try_from(url),
Err(UrlConversionError {
kind: UrlConversionErrorKind::UnsupportedScheme,
..
})
));
}
}
4 changes: 3 additions & 1 deletion helix-event/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
use anyhow::Result;
pub use cancel::{cancelable_future, cancelation, CancelRx, CancelTx};
pub use debounce::{send_blocking, AsyncHook};
pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
pub use redraw::{
lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard, RequestRedrawOnDrop,
};
pub use registry::Event;

mod cancel;
Expand Down
9 changes: 9 additions & 0 deletions helix-event/src/redraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ pub fn start_frame() {
pub fn lock_frame() -> RenderLockGuard {
RENDER_LOCK.read()
}

/// A zero sized type that requests a redraw via [request_redraw] when the type [Drop]s.
pub struct RequestRedrawOnDrop;

impl Drop for RequestRedrawOnDrop {
fn drop(&mut self) {
request_redraw();
}
}
1 change: 1 addition & 0 deletions helix-term/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ ignore = "0.4"
pulldown-cmark = { version = "0.11", default-features = false }
# file type detection
content_inspector = "0.2.4"
thiserror = "1.0"

# opening URLs
open = "5.2.0"
Expand Down
26 changes: 14 additions & 12 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -735,10 +735,10 @@ impl Application {
}
}
Notification::PublishDiagnostics(mut params) => {
let path = match params.uri.to_file_path() {
Ok(path) => helix_stdx::path::normalize(path),
Err(_) => {
log::error!("Unsupported file URI: {}", params.uri);
let uri = match helix_core::Uri::try_from(params.uri) {
Ok(uri) => uri,
Err(err) => {
log::error!("{err}");
return;
}
};
Expand All @@ -749,11 +749,11 @@ impl Application {
}
// have to inline the function because of borrow checking...
let doc = self.editor.documents.values_mut()
.find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
.find(|doc| doc.uri().is_some_and(|u| u == uri))
.filter(|doc| {
if let Some(version) = params.version {
if version != doc.version() {
log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
log::info!("Version ({version}) is out of date for {uri:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
return false;
}
}
Expand All @@ -765,7 +765,7 @@ impl Application {
let lang_conf = doc.language.clone();

if let Some(lang_conf) = &lang_conf {
if let Some(old_diagnostics) = self.editor.diagnostics.get(&path) {
if let Some(old_diagnostics) = self.editor.diagnostics.get(&uri) {
if !lang_conf.persistent_diagnostic_sources.is_empty() {
// Sort diagnostics first by severity and then by line numbers.
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
Expand Down Expand Up @@ -798,7 +798,7 @@ impl Application {
// Insert the original lsp::Diagnostics here because we may have no open document
// for diagnosic message and so we can't calculate the exact position.
// When using them later in the diagnostics picker, we calculate them on-demand.
let diagnostics = match self.editor.diagnostics.entry(path) {
let diagnostics = match self.editor.diagnostics.entry(uri) {
Entry::Occupied(o) => {
let current_diagnostics = o.into_mut();
// there may entries of other language servers, which is why we can't overwrite the whole entry
Expand Down Expand Up @@ -1132,20 +1132,22 @@ impl Application {
..
} = params;

let path = match uri.to_file_path() {
Ok(path) => path,
let uri = match helix_core::Uri::try_from(uri) {
Ok(uri) => uri,
Err(err) => {
log::error!("unsupported file URI: {}: {:?}", uri, err);
log::error!("{err}");
return lsp::ShowDocumentResult { success: false };
}
};
// If `Uri` gets another variant other than `Path` this may not be valid.
let path = uri.as_path().expect("URIs are valid paths");

let action = match take_focus {
Some(true) => helix_view::editor::Action::Replace,
_ => helix_view::editor::Action::VerticalSplit,
};

let doc_id = match self.editor.open(&path, action) {
let doc_id = match self.editor.open(path, action) {
Ok(id) => id,
Err(err) => {
log::error!("failed to open path: {:?}: {:?}", uri, err);
Expand Down
Loading
Loading