Merge branch 'master' into help-command

pull/997/head
Omnikar 3 years ago
commit 9bcc8a6e74
No known key found for this signature in database
GPG Key ID: 7DE6694CDA7885ED

4
.gitmodules vendored

@ -170,3 +170,7 @@
path = helix-syntax/languages/tree-sitter-fish
url = https://github.com/ram02z/tree-sitter-fish
shallow = true
[submodule "helix-syntax/languages/tree-sitter-git-commit"]
path = helix-syntax/languages/tree-sitter-git-commit
url = https://github.com/the-mikedavis/tree-sitter-git-commit.git
shallow = true

2
Cargo.lock generated

@ -370,6 +370,7 @@ version = "0.5.0"
dependencies = [
"arc-swap",
"chrono",
"encoding_rs",
"etcetera",
"helix-syntax",
"log",
@ -471,7 +472,6 @@ dependencies = [
"chardetng",
"clipboard-win",
"crossterm",
"encoding_rs",
"futures-util",
"helix-core",
"helix-lsp",

@ -29,6 +29,10 @@
"namespace" = "magenta"
"ui.help" = { fg = "white", bg = "black" }
"diff.plus" = "green"
"diff.delta" = "yellow"
"diff.minus" = "red"
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "black" }
"info" = "blue"

@ -3,7 +3,7 @@
| bash | ✓ | | | `bash-language-server` |
| c | ✓ | | | `clangd` |
| c-sharp | ✓ | | | |
| cmake | ✓ | | | `cmake-language-server` |
| cmake | ✓ | | | `cmake-language-server` |
| comment | ✓ | | | |
| cpp | ✓ | | | `clangd` |
| css | ✓ | | | |
@ -11,6 +11,7 @@
| dockerfile | ✓ | | | `docker-langserver` |
| elixir | ✓ | | | `elixir-ls` |
| fish | ✓ | ✓ | ✓ | |
| git-commit | ✓ | | | |
| glsl | ✓ | | ✓ | |
| go | ✓ | ✓ | ✓ | `gopls` |
| html | ✓ | | | |

@ -41,4 +41,5 @@
| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
| `:tutor` | Open the tutorial. |
| `:goto`, `:g` | Go to line number. |
| `:set-option`, `:set` | Set a config option at runtime |
| `:help`, `:h` | Open documentation for a command or keybind. |

@ -36,6 +36,7 @@ These are the available keys and descriptions for the file.
| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
| auto-format | Whether to autoformat this language when saving |
| diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
| comment-token | The token to use as a comment-token |
| indent | The indent to use. Has sub keys `tab-width` and `unit` |
| config | Language server configuration |

@ -77,8 +77,8 @@
| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
| `Ctrl-a` | Increment object (number) under cursor | `increment` |
| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
| `q` | Start/stop macro recording to the selected register | `record_macro` |
| `Q` | Play back a recorded macro from the selected register | `play_macro` |
| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` |
| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` |
#### Shell

@ -11,4 +11,3 @@ Changes made to the `languages.toml` file in a user's [configuration directory](
name = "rust"
auto-format = false
```

@ -178,6 +178,12 @@ We use a similar set of scopes as
- `inline`
- `block`
- `diff` - version control changes
- `plus` - additions
- `minus` - deletions
- `delta` - modifications
- `moved` - renamed or moved files/changes
#### Interface
These scopes are used for theming the editor interface.

@ -35,6 +35,7 @@ toml = "0.5"
similar = "2.1"
etcetera = "0.3"
encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }

@ -1,12 +1,19 @@
//! LSP diagnostic utility types.
use serde::{Deserialize, Serialize};
/// Describes the severity level of a [`Diagnostic`].
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum Severity {
Error,
Warning,
Info,
Hint,
Info,
Warning,
Error,
}
impl Default for Severity {
fn default() -> Self {
Self::Hint
}
}
/// A range of `char`s within the text.

@ -442,6 +442,7 @@ where
);
let doc = Rope::from(doc);
use crate::diagnostic::Severity;
use crate::syntax::{
Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
};
@ -459,6 +460,7 @@ where
roots: vec![],
comment_token: None,
auto_format: false,
diagnostic_severity: Severity::Warning,
language_server: None,
indent: Some(IndentationConfiguration {
tab_width: 4,

@ -1,3 +1,5 @@
pub use encoding_rs as encoding;
pub mod auto_pairs;
pub mod chars;
pub mod comment;

@ -1,5 +1,6 @@
use crate::{
chars::char_is_line_ending,
diagnostic::Severity,
regex::Regex,
transaction::{ChangeSet, Operation},
Rope, RopeSlice, Tendril,
@ -63,6 +64,8 @@ pub struct LanguageConfiguration {
#[serde(default)]
pub auto_format: bool,
#[serde(default)]
pub diagnostic_severity: Severity,
// content_regex
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]

@ -556,6 +556,14 @@ impl Client {
self.call::<lsp::request::Completion>(params)
}
pub async fn resolve_completion_item(
&self,
completion_item: lsp::CompletionItem,
) -> Result<lsp::CompletionItem> {
self.request::<lsp::request::ResolveCompletionItem>(completion_item)
.await
}
pub fn text_document_signature_help(
&self,
text_document: lsp::TextDocumentIdentifier,

@ -0,0 +1 @@
Subproject commit 5cd4776c86c82d9d6afdc8c73a47a08057aef618

@ -378,6 +378,7 @@ impl Application {
let doc = self.editor.document_by_path_mut(&path);
if let Some(doc) = doc {
let lang_conf = doc.language_config();
let text = doc.text();
let diagnostics = params
@ -415,19 +416,31 @@ impl Application {
return None;
};
Some(Diagnostic {
range: Range { start, end },
line: diagnostic.range.start.line as usize,
message: diagnostic.message,
severity: diagnostic.severity.map(
|severity| match severity {
let severity =
diagnostic.severity.map(|severity| match severity {
DiagnosticSeverity::ERROR => Error,
DiagnosticSeverity::WARNING => Warning,
DiagnosticSeverity::INFORMATION => Info,
DiagnosticSeverity::HINT => Hint,
severity => unimplemented!("{:?}", severity),
},
severity => unreachable!(
"unrecognized diagnostic severity: {:?}",
severity
),
});
if let Some(lang_conf) = lang_conf {
if let Some(severity) = severity {
if severity < lang_conf.diagnostic_severity {
return None;
}
}
};
Some(Diagnostic {
range: Range { start, end },
line: diagnostic.range.start.line as usize,
message: diagnostic.message,
severity,
// code
// source
})

@ -396,7 +396,7 @@ impl MappableCommand {
increment, "Increment",
decrement, "Decrement",
record_macro, "Record macro",
play_macro, "Play macro",
replay_macro, "Replay macro",
);
}
@ -2637,6 +2637,36 @@ pub mod cmd {
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, line);
Ok(())
}
fn setting(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
let runtime_config = &mut cx.editor.config;
if args.len() != 2 {
anyhow::bail!("Bad arguments. Usage: `:set key field`");
}
let (key, arg) = (&args[0].to_lowercase(), &args[1]);
match key.as_ref() {
"scrolloff" => runtime_config.scrolloff = arg.parse()?,
"scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
"mouse" => runtime_config.mouse = arg.parse()?,
"line-number" => runtime_config.line_number = arg.parse()?,
"middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
"smart-case" => runtime_config.smart_case = arg.parse()?,
"auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
"auto-completion" => runtime_config.auto_completion = arg.parse()?,
"completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
"auto-info" => runtime_config.auto_info = arg.parse()?,
"true-color" => runtime_config.true_color = arg.parse()?,
_ => anyhow::bail!("Unknown key `{}`.", args[0]),
}
Ok(())
}
@ -2965,6 +2995,13 @@ pub mod cmd {
fun: goto_line_number,
completer: None,
},
TypableCommand {
name: "set-option",
aliases: &["set"],
doc: "Set a config option at runtime",
fun: setting,
completer: Some(completers::setting),
},
TypableCommand {
name: "help",
aliases: &["h"],
@ -6021,42 +6058,42 @@ fn record_macro(cx: &mut Context) {
keys.pop();
let s = keys
.into_iter()
.map(|key| format!("{}", key))
.collect::<Vec<_>>()
.join(" ");
.map(|key| {
let s = key.to_string();
if s.chars().count() == 1 {
s
} else {
format!("<{}>", s)
}
})
.collect::<String>();
cx.editor.registers.get_mut(reg).write(vec![s]);
cx.editor
.set_status(format!("Recorded to register {}", reg));
.set_status(format!("Recorded to register [{}]", reg));
} else {
let reg = cx.register.take().unwrap_or('@');
cx.editor.macro_recording = Some((reg, Vec::new()));
cx.editor
.set_status(format!("Recording to register {}", reg));
.set_status(format!("Recording to register [{}]", reg));
}
}
fn play_macro(cx: &mut Context) {
fn replay_macro(cx: &mut Context) {
let reg = cx.register.unwrap_or('@');
let keys = match cx
.editor
.registers
.get(reg)
.and_then(|reg| reg.read().get(0))
.context("Register empty")
.and_then(|s| {
s.split_whitespace()
.map(str::parse::<KeyEvent>)
.collect::<Result<Vec<_>, _>>()
.context("Failed to parse macro")
}) {
let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
match helix_view::input::parse_macro(keys_str) {
Ok(keys) => keys,
Err(e) => {
cx.editor.set_error(format!("{}", e));
Err(err) => {
cx.editor.set_error(format!("Invalid macro: {}", err));
return;
}
}
} else {
cx.editor.set_error(format!("Register [{}] empty", reg));
return;
};
let count = cx.count();
let count = cx.count();
cx.callback = Some(Box::new(
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
for _ in 0..count {

@ -593,8 +593,8 @@ impl Default for Keymaps {
// paste_all
"P" => paste_before,
"q" => record_macro,
"Q" => play_macro,
"Q" => record_macro,
"q" => replay_macro,
">" => indent,
"<" => unindent,

@ -154,8 +154,19 @@ impl Completion {
);
doc.apply(&transaction, view.id);
if let Some(additional_edits) = &item.additional_text_edits {
// gopls uses this to add extra imports
// apply additional edits, mostly used to auto import unqualified types
let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
None
} else {
Completion::resolve_completion_item(doc, item.clone())
.and_then(|item| item.additional_text_edits)
};
if let Some(additional_edits) = item
.additional_text_edits
.as_ref()
.or_else(|| resolved_additional_text_edits.as_ref())
{
if !additional_edits.is_empty() {
let transaction = util::generate_transaction_from_edits(
doc.text(),
@ -181,6 +192,31 @@ impl Completion {
completion
}
fn resolve_completion_item(
doc: &Document,
completion_item: lsp::CompletionItem,
) -> Option<CompletionItem> {
let language_server = doc.language_server()?;
let completion_resolve_provider = language_server
.capabilities()
.completion_provider
.as_ref()?
.resolve_provider;
if completion_resolve_provider != Some(true) {
return None;
}
let future = language_server.resolve_completion_item(completion_item);
let response = helix_lsp::block_on(future);
match response {
Ok(completion_item) => Some(completion_item),
Err(err) => {
log::error!("execute LSP command: {}", err);
None
}
}
}
pub fn recompute_filter(&mut self, editor: &Editor) {
// recompute menu based on matches
let menu = self.popup.contents_mut();

@ -7,7 +7,7 @@ use crate::{
};
use helix_core::{
coords_at_pos,
coords_at_pos, encoding,
graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
movement::Direction,
syntax::{self, HighlightEvent},
@ -548,21 +548,6 @@ impl EditorView {
}
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
let rel_path = doc.relative_path();
let path = rel_path
.as_ref()
.map(|p| p.to_string_lossy())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" });
surface.set_stringn(
viewport.x + 8,
viewport.y,
title,
viewport.width.saturating_sub(6) as usize,
base_style,
);
//-------------------------------
// Right side of the status line.
//-------------------------------
@ -636,6 +621,13 @@ impl EditorView {
base_style,
));
let enc = doc.encoding();
if enc != encoding::UTF_8 {
right_side_text
.0
.push(Span::styled(format!(" {} ", enc.name()), base_style));
}
// Render to the statusline.
surface.set_spans(
viewport.x
@ -646,6 +638,31 @@ impl EditorView {
&right_side_text,
right_side_text.width() as u16,
);
//-------------------------------
// Middle / File path / Title
//-------------------------------
let title = {
let rel_path = doc.relative_path();
let path = rel_path
.as_ref()
.map(|p| p.to_string_lossy())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
};
surface.set_string_truncated(
viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
viewport.y,
title,
viewport
.width
.saturating_sub(6)
.saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
base_style,
true,
true,
);
}
/// Handle events by looking them up in `self.keymaps`. Returns None

@ -174,7 +174,9 @@ pub mod completers {
use crate::ui::prompt::Completion;
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
use helix_view::editor::Config;
use helix_view::theme;
use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cmp::Reverse;
@ -208,6 +210,31 @@ pub mod completers {
names
}
pub fn setting(input: &str) -> Vec<Completion> {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
serde_json::to_value(Config::default())
.unwrap()
.as_object()
.unwrap()
.keys()
.cloned()
.collect()
});
let matcher = Matcher::default();
let mut matches: Vec<_> = KEYS
.iter()
.filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
.collect();
matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
matches
.into_iter()
.map(|(name, _)| ((0..), name.into()))
.collect()
}
pub fn filename(input: &str) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());

@ -29,7 +29,6 @@ futures-util = { version = "0.3", features = ["std", "async-await"], default-fea
slotmap = "1"
encoding_rs = "0.8"
chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] }

@ -9,6 +9,7 @@ use std::str::FromStr;
use std::sync::Arc;
use helix_core::{
encoding,
history::History,
indent::{auto_detect_indent_style, IndentStyle},
line_ending::auto_detect_line_ending,
@ -74,7 +75,7 @@ pub struct Document {
pub(crate) selections: HashMap<ViewId, Selection>,
path: Option<PathBuf>,
encoding: &'static encoding_rs::Encoding,
encoding: &'static encoding::Encoding,
/// Current editing mode.
pub mode: Mode,
@ -143,8 +144,8 @@ impl fmt::Debug for Document {
/// be used to override encoding auto-detection.
pub fn from_reader<R: std::io::Read + ?Sized>(
reader: &mut R,
encoding: Option<&'static encoding_rs::Encoding>,
) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
encoding: Option<&'static encoding::Encoding>,
) -> Result<(Rope, &'static encoding::Encoding), Error> {
// These two buffers are 8192 bytes in size each and are used as
// intermediaries during the decoding process. Text read into `buf`
// from `reader` is decoded into `buf_out` as UTF-8. Once either
@ -212,11 +213,11 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
total_read += read;
total_written += written;
match result {
encoding_rs::CoderResult::InputEmpty => {
encoding::CoderResult::InputEmpty => {
debug_assert_eq!(slice.len(), total_read);
break;
}
encoding_rs::CoderResult::OutputFull => {
encoding::CoderResult::OutputFull => {
debug_assert!(slice.len() > total_read);
builder.append(&buf_str[..total_written]);
total_written = 0;
@ -251,7 +252,7 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
/// replacement characters may appear in the encoded text.
pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
writer: &'a mut W,
encoding: &'static encoding_rs::Encoding,
encoding: &'static encoding::Encoding,
rope: &'a Rope,
) -> Result<(), Error> {
// Text inside a `Rope` is stored as non-contiguous blocks of data called
@ -286,12 +287,12 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
total_read += read;
total_written += written;
match result {
encoding_rs::CoderResult::InputEmpty => {
encoding::CoderResult::InputEmpty => {
debug_assert_eq!(chunk.len(), total_read);
debug_assert!(buf.len() >= total_written);
break;
}
encoding_rs::CoderResult::OutputFull => {
encoding::CoderResult::OutputFull => {
debug_assert!(chunk.len() > total_read);
writer.write_all(&buf[..total_written]).await?;
total_written = 0;
@ -322,8 +323,8 @@ use helix_lsp::lsp;
use url::Url;
impl Document {
pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
let encoding = encoding.unwrap_or(encoding::UTF_8);
let changes = ChangeSet::new(&text);
let old_state = None;
@ -356,7 +357,7 @@ impl Document {
/// overwritten with the `encoding` parameter.
pub fn open(
path: &Path,
encoding: Option<&'static encoding_rs::Encoding>,
encoding: Option<&'static encoding::Encoding>,
theme: Option<&Theme>,
config_loader: Option<&syntax::Loader>,
) -> Result<Self, Error> {
@ -366,7 +367,7 @@ impl Document {
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader(&mut file, encoding)?
} else {
let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
let encoding = encoding.unwrap_or(encoding::UTF_8);
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
};
@ -548,7 +549,7 @@ impl Document {
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
match encoding_rs::Encoding::for_label(label.as_bytes()) {
match encoding::Encoding::for_label(label.as_bytes()) {
Some(encoding) => self.encoding = encoding,
None => return Err(anyhow::anyhow!("unknown encoding")),
}
@ -556,7 +557,7 @@ impl Document {
}
/// Returns the [`Document`]'s current encoding.
pub fn encoding(&self) -> &'static encoding_rs::Encoding {
pub fn encoding(&self) -> &'static encoding::Encoding {
self.encoding
}
@ -1123,7 +1124,7 @@ mod test {
macro_rules! test_decode {
($label:expr, $label_override:expr) => {
let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_in.txt", $label));
let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
@ -1142,7 +1143,7 @@ mod test {
macro_rules! test_encode {
($label:expr, $label_override:expr) => {
let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_out.txt", $label));
let ref_path = base_path.join(format!("{}_out_ref.txt", $label));

@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
use helix_core::syntax;
use helix_core::{Position, Selection};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
@ -37,7 +37,7 @@ where
Ok(Duration::from_millis(millis))
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig {
/// IgnoreOptions
@ -77,7 +77,7 @@ impl Default for FilePickerConfig {
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
/// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5.
@ -109,7 +109,7 @@ pub struct Config {
pub true_color: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LineNumber {
/// Show absolute line number
@ -119,6 +119,18 @@ pub enum LineNumber {
Relative,
}
impl std::str::FromStr for LineNumber {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"absolute" | "abs" => Ok(Self::Absolute),
"relative" | "rel" => Ok(Self::Relative),
_ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {

@ -254,6 +254,43 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
}
}
pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
use anyhow::Context;
let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
let mut i = 0;
while let Ok(keys) = &mut keys_res {
if i >= keys_str.len() {
break;
}
if !keys_str.is_char_boundary(i) {
i += 1;
continue;
}
let s = &keys_str[i..];
let mut end_i = 1;
while !s.is_char_boundary(end_i) {
end_i += 1;
}
let c = &s[..end_i];
if c == ">" {
keys_res = Err(anyhow!("Unmatched '>'"));
} else if c != "<" {
keys.push(c);
i += end_i;
} else {
match s.find('>').context("'>' expected") {
Ok(end_i) => {
keys.push(&s[1..end_i]);
i += end_i + 1;
}
Err(err) => keys_res = Err(err),
}
}
}
keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
}
#[cfg(test)]
mod test {
use super::*;
@ -339,4 +376,120 @@ mod test {
assert!(str::parse::<KeyEvent>("123").is_err());
assert!(str::parse::<KeyEvent>("S--").is_err());
}
#[test]
fn parsing_valid_macros() {
assert_eq!(
parse_macro("xdo").ok(),
Some(vec![
KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('d'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::NONE,
},
]),
);
assert_eq!(
parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
Some(vec![
KeyEvent {
code: KeyCode::Char('w'),
modifiers: KeyModifiers::CONTROL,
},
KeyEvent {
code: KeyCode::Char('v'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('w'),
modifiers: KeyModifiers::CONTROL,
},
KeyEvent {
code: KeyCode::Char('h'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::CONTROL,
},
KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::ALT,
},
])
);
assert_eq!(
parse_macro(":o foo.bar<ret>").ok(),
Some(vec![
KeyEvent {
code: KeyCode::Char(':'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('f'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('o'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('.'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('b'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('a'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Char('r'),
modifiers: KeyModifiers::NONE,
},
KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
},
])
);
}
#[test]
fn parsing_invalid_macros_fails() {
assert!(parse_macro("abc<C-").is_err());
assert!(parse_macro("abc>123").is_err());
assert!(parse_macro("wd<foo>").is_err());
}
}

@ -473,3 +473,11 @@ file-types = ["Dockerfile", "dockerfile"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-server = { command = "docker-langserver", args = ["--stdio"] }
[[language]]
name = "git-commit"
scope = "git.commitmsg"
roots = []
file-types = ["COMMIT_EDITMSG"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }

@ -0,0 +1,12 @@
indent = [
"if_condition",
"foreach_loop",
"while_loop",
"function_def",
"macro_def",
"normal_command",
]
outdent = [
")"
]

@ -0,0 +1,3 @@
(macro_def) @function.around
(argument) @parameter.inside

@ -0,0 +1,19 @@
(subject) @markup.heading
(path) @string.special.path
(branch) @string.special.symbol
(commit) @constant
(item) @markup.link.url
(header) @tag
(rebase_command) @markup.raw
(change kind: "new file" @diff.plus)
(change kind: "deleted" @diff.minus)
(change kind: "modified" @diff.delta)
(change kind: "renamed" @diff.delta.moved)
[":" "->"] @punctuation.delimeter
(comment) @comment
; once we have diff injections, @comment should become @none
((comment (scissors))
(message)+ @comment)

@ -0,0 +1,15 @@
; once a diff grammar is available, we can inject diff highlighting into the
; trailer after scissors (git commit --verbose)
; see https://github.com/helix-editor/helix/pull/1338#issuecomment-1000013539
;
; ((comment (scissors))
; (message) @injection.content
; (#set! injection.language "diff"))
; ---
; once a rebase grammar is available, we can inject rebase highlighting into
; interactive rebase summary sections like so:
;
; ((rebase_command) @injection.content
; (#set! injection.language "git-rebase"))

@ -33,6 +33,10 @@
"namespace" = "base0E"
"ui.help" = { fg = "base06", bg = "base01" }
"diff.plus" = "base0B"
"diff.delta" = "base09"
"diff.minus" = "base08"
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "base01" }
"info" = "base0D"

@ -33,6 +33,10 @@
"namespace" = "base0E"
"ui.help" = { fg = "base06", bg = "base01" }
"diff.plus" = "base0B"
"diff.delta" = "base09"
"diff.minus" = "base08"
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "base01" }
"info" = "base0D"

@ -30,6 +30,10 @@
"namespace" = "light-magenta"
"ui.help" = { fg = "white", bg = "black" }
"diff.plus" = "light-green"
"diff.delta" = "yellow"
"diff.minus" = "light-red"
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "black" }
"info" = "light-blue"

@ -28,6 +28,10 @@
"module" = "#d32c5d"
"diff.plus" = "#59dcb7"
"diff.delta" = "#dc7759"
"diff.minus" = "#dc597f"
"ui.background" = { bg = "#161c23" }
"ui.linenr" = { fg = "#415367" }
"ui.linenr.selected" = { fg = "#e5ded6" } # TODO

@ -39,6 +39,10 @@
"constant.numeric" = { fg = "pale_green" }
"constant.character.escape" = { fg = "gold" }
"diff.plus" = { fg = "pale_green" }
"diff.delta" = { fg = "gold" }
"diff.minus" = { fg = "red" }
"ui.background" = { fg = "light_gray", bg = "dark_gray2" }
"ui.window" = { bg = "widget" }

@ -15,6 +15,10 @@
"variable.builtin" = { fg = "cyan", modifiers = ["italic"] }
"variable.parameter" = { fg ="orange", modifiers = ["italic"] }
"diff.plus" = { fg = "green" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
"ui.background" = { fg = "foreground", bg = "background" }
"ui.cursor" = { fg = "background", bg = "orange", modifiers = ["dim"] }
"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }

@ -34,6 +34,10 @@
"module" = "blue"
"special" = "orange"
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
"ui.background" = { bg = "bg0" }
"ui.cursor" = { fg = "bg0", bg = "fg" }
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }

@ -28,6 +28,10 @@
"label" = "aqua1"
"module" = "aqua1"
"diff.plus" = "green1"
"diff.delta" = "orange1"
"diff.minus" = "red1"
"warning" = { fg = "orange1", bg = "bg1" }
"error" = { fg = "red1", bg = "bg1" }
"info" = { fg = "aqua1", bg = "bg1" }

@ -28,6 +28,10 @@
"module" = "#839A53"
"diff.plus" = "#839A53"
"diff.delta" = "#D4A520"
"diff.minus" = "#D74E50"
"ui.background" = { bg = "#FFFCFD" }
"ui.linenr" = { fg = "#bbbbbb" }
"ui.linenr.selected" = { fg = "#F3EAE9" } # TODO

@ -39,6 +39,10 @@
"constant.numeric" = { fg = "#ae81ff" }
"constant.character.escape" = { fg = "#ae81ff" }
"diff.plus" = { fg = "#a6e22e" }
"diff.delta" = { fg = "#fd971f" }
"diff.minus" = { fg = "#f92672" }
"ui.background" = { fg = "text", bg = "background" }
"ui.window" = { bg = "widget" }
@ -65,7 +69,7 @@
"warning" = { fg = "#cca700" }
"error" = { fg = "#f48771" }
"info" = { fg = "#75beff" }
"hint" = { fg = "#eeeeeeb3" }
"hint" = { fg = "#eeeeeb3" }
diagnostic = { modifiers = ["underlined"] }

@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
# vcs
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
# vcs
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
# vcs
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
# vcs
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
# vcs
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -84,6 +84,11 @@
# nord15 - integer, floating point
"constant.numeric" = "nord15"
# vcs
"diff.plus" = "nord14"
"diff.delta" = "nord12"
"diff.minus" = "nord11"
[palette]
nord0 = "#2e3440"
nord1 = "#3b4252"

@ -36,6 +36,10 @@
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"]}
"markup.link.label" = { fg = "purple" }
"diff.plus" = "green"
"diff.delta" = "gold"
"diff.minus" = "red"
diagnostic = { modifiers = ["underlined"] }
"info" = { fg = "blue", modifiers = ["bold"] }
"hint" = { fg = "green", modifiers = ["bold"] }

@ -1,4 +1,5 @@
# Author: RayGervais<raygervais@hotmail.ca>
# Author: ChrisHa<chunghha@users.noreply.github.com>
"ui.background" = { bg = "base" }
"ui.menu" = "surface"
@ -37,6 +38,9 @@
"ui.window" = { bg = "base" }
"ui.help" = { bg = "overlay", fg = "foam" }
"text" = "text"
"diff.plus" = "foam"
"diff.delta" = "rose"
"diff.minus" = "love"
"info" = "gold"
"hint" = "gold"
@ -44,6 +48,15 @@
"diagnostic" = "rose"
"error" = "love"
"markup.heading" = { fg = "rose" }
"markup.raw.inline" = { fg = "foam" }
"markup.bold" = { fg = "gold", modifiers = ["bold"] }
"markup.italic" = { fg = "iris", modifiers = ["italic"] }
"markup.list" = { fg = "love" }
"markup.quote" = { fg = "rose" }
"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
"markup.link.label" = { fg = "foam" }
[palette]
base = "#191724"
surface = "#1f1d2e"

@ -1,8 +1,8 @@
# Author: ChrisHa<chunghha@users.noreply.github.com>
# Author: RayGervais<raygervais@hotmail.ca>
# Author: ChrisHa<chunghha@users.noreply.github.com>
"ui.background" = { bg = "base" }
"ui.menu" = "surface"
"ui.background" = { bg = "surface" }
"ui.menu" = "base"
"ui.menu.selected" = { fg = "iris", bg = "surface" }
"ui.linenr" = {fg = "subtle" }
"ui.popup" = { bg = "overlay" }
@ -38,6 +38,9 @@
"ui.window" = { bg = "base" }
"ui.help" = { bg = "overlay", fg = "foam" }
"text" = "text"
"diff.plus" = "foam"
"diff.delta" = "rose"
"diff.minus" = "love"
"info" = "gold"
"hint" = "gold"
@ -45,6 +48,15 @@
"diagnostic" = "rose"
"error" = "love"
"markup.heading" = { fg = "rose" }
"markup.raw.inline" = { fg = "foam" }
"markup.bold" = { fg = "gold", modifiers = ["bold"] }
"markup.italic" = { fg = "iris", modifiers = ["italic"] }
"markup.list" = { fg = "love" }
"markup.quote" = { fg = "rose" }
"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
"markup.link.label" = { fg = "foam" }
[palette]
base = "#faf4ed"
surface = "#fffaf3"

@ -22,6 +22,10 @@
"module" = { fg = "violet" }
"tag" = { fg = "magenta" }
"diff.plus" = { fg = "green" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
# 背景
"ui.background" = { bg = "base03" }

@ -22,6 +22,10 @@
"module" = { fg = "violet" }
"tag" = { fg = "magenta" }
"diff.plus" = { fg = "green" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
# 背景
"ui.background" = { bg = "base03" }

@ -30,6 +30,10 @@
"label" = "#b1951d"
"module" = "#b1951d"
"diff.plus" = "#2d9574"
"diff.delta" = "#715ab1"
"diff.minus" = "#ba2f59"
"warning" = { fg = "#da8b55" }
"error" = { fg = "#e0211d" }
"info" = { fg = "#b1951d" }

@ -34,6 +34,10 @@ label = "honey"
"markup.link.url" = { fg = "silver", modifiers = ["underlined"] }
"markup.raw" = "almond"
"diff.plus" = "#35bf86"
"diff.minus" = "#f22c86"
"diff.delta" = "#6f44f0"
# TODO: diferentiate doc comment
# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors

Loading…
Cancel
Save