forked from Mirrors/helix
Merge remote-tracking branch 'origin/master' into debug
commit
bd549d8a20
@ -0,0 +1,2 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
@ -0,0 +1,51 @@
|
||||
# Author: NNB <nnbnh@protonmail.com>
|
||||
|
||||
"ui.menu" = "black"
|
||||
"ui.menu.selected" = { modifiers = ["reversed"] }
|
||||
"ui.linenr" = { fg = "gray", bg = "black" }
|
||||
"ui.popup" = { modifiers = ["reversed"] }
|
||||
"ui.linenr.selected" = { fg = "white", bg = "black", modifiers = ["bold"] }
|
||||
"ui.selection" = { fg = "black", bg = "blue" }
|
||||
"ui.selection.primary" = { fg = "white", bg = "blue" }
|
||||
"comment" = { fg = "gray" }
|
||||
"ui.statusline" = { fg = "black", bg = "white" }
|
||||
"ui.statusline.inactive" = { fg = "gray", bg = "white" }
|
||||
"ui.help" = { modifiers = ["reversed"] }
|
||||
"ui.cursor" = { fg = "white", modifiers = ["reversed"] }
|
||||
"variable" = "red"
|
||||
"constant.numeric" = "yellow"
|
||||
"constant" = "yellow"
|
||||
"attributes" = "yellow"
|
||||
"type" = "yellow"
|
||||
"ui.cursor.match" = { fg = "yellow", modifiers = ["underlined"] }
|
||||
"string" = "green"
|
||||
"variable.other.member" = "green"
|
||||
"constant.character.escape" = "cyan"
|
||||
"function" = "blue"
|
||||
"constructor" = "blue"
|
||||
"special" = "blue"
|
||||
"keyword" = "magenta"
|
||||
"label" = "magenta"
|
||||
"namespace" = "magenta"
|
||||
"ui.help" = { fg = "white", bg = "black" }
|
||||
|
||||
"markup.heading" = "blue"
|
||||
"markup.list" = "red"
|
||||
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
|
||||
"markup.italic" = { fg = "magenta", modifiers = ["italic"] }
|
||||
"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
|
||||
"markup.link.text" = "red"
|
||||
"markup.quote" = "cyan"
|
||||
"markup.raw" = "green"
|
||||
|
||||
"diff.plus" = "green"
|
||||
"diff.delta" = "yellow"
|
||||
"diff.minus" = "red"
|
||||
|
||||
"diagnostic" = { modifiers = ["underlined"] }
|
||||
"ui.gutter" = { bg = "black" }
|
||||
"info" = "blue"
|
||||
"hint" = "gray"
|
||||
"debug" = "gray"
|
||||
"warning" = "yellow"
|
||||
"error" = "red"
|
@ -0,0 +1,5 @@
|
||||
# Commands
|
||||
|
||||
Command mode can be activated by pressing `:`, similar to vim. Built-in commands:
|
||||
|
||||
{{#include ./generated/typable-cmd.md}}
|
@ -0,0 +1,63 @@
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| bash | ✓ | | | `bash-language-server` |
|
||||
| c | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | | | |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
| cpp | ✓ | ✓ | ✓ | `clangd` |
|
||||
| css | ✓ | | | |
|
||||
| dart | ✓ | | ✓ | `dart` |
|
||||
| dockerfile | ✓ | | | `docker-langserver` |
|
||||
| elixir | ✓ | | | `elixir-ls` |
|
||||
| elm | ✓ | | | `elm-language-server` |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
| git-commit | ✓ | | | |
|
||||
| git-config | ✓ | | | |
|
||||
| git-diff | ✓ | | | |
|
||||
| git-rebase | ✓ | | | |
|
||||
| glsl | ✓ | | ✓ | |
|
||||
| go | ✓ | ✓ | ✓ | `gopls` |
|
||||
| graphql | ✓ | | | |
|
||||
| haskell | ✓ | | | `haskell-language-server-wrapper` |
|
||||
| html | ✓ | | | |
|
||||
| iex | ✓ | | | |
|
||||
| java | ✓ | | | |
|
||||
| javascript | ✓ | | ✓ | `typescript-language-server` |
|
||||
| json | ✓ | | ✓ | |
|
||||
| julia | ✓ | | | `julia` |
|
||||
| latex | ✓ | | | |
|
||||
| lean | ✓ | | | `lean` |
|
||||
| ledger | ✓ | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | |
|
||||
| lua | ✓ | | ✓ | |
|
||||
| make | ✓ | | | |
|
||||
| markdown | ✓ | | | |
|
||||
| mint | | | | `mint` |
|
||||
| nix | ✓ | | ✓ | `rnix-lsp` |
|
||||
| ocaml | ✓ | | ✓ | |
|
||||
| ocaml-interface | ✓ | | | |
|
||||
| perl | ✓ | ✓ | ✓ | |
|
||||
| php | ✓ | ✓ | ✓ | |
|
||||
| prolog | | | | `swipl` |
|
||||
| protobuf | ✓ | | ✓ | |
|
||||
| python | ✓ | ✓ | ✓ | `pylsp` |
|
||||
| racket | | | | `racket` |
|
||||
| regex | ✓ | | | |
|
||||
| rescript | ✓ | ✓ | | `rescript-language-server` |
|
||||
| ruby | ✓ | | ✓ | `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| scala | ✓ | | ✓ | `metals` |
|
||||
| svelte | ✓ | | ✓ | `svelteserver` |
|
||||
| tablegen | ✓ | ✓ | ✓ | |
|
||||
| toml | ✓ | | | |
|
||||
| tsq | ✓ | | | |
|
||||
| tsx | ✓ | | | `typescript-language-server` |
|
||||
| twig | ✓ | | | |
|
||||
| typescript | ✓ | | ✓ | `typescript-language-server` |
|
||||
| vue | ✓ | | | |
|
||||
| wgsl | ✓ | | | |
|
||||
| yaml | ✓ | | ✓ | |
|
||||
| zig | ✓ | | ✓ | `zls` |
|
@ -0,0 +1,48 @@
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `:quit`, `:q` | Close the current view. |
|
||||
| `:quit!`, `:q!` | Close the current view forcefully (ignoring unsaved changes). |
|
||||
| `:open`, `:o` | Open a file from disk into the current view. |
|
||||
| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
|
||||
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully (ignoring unsaved changes). |
|
||||
| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
|
||||
| `:new`, `:n` | Create a new scratch buffer. |
|
||||
| `:format`, `:fmt` | Format the file using the LSP formatter. |
|
||||
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
|
||||
| `:line-ending` | Set the document's default line ending. Options: crlf, lf, cr, ff, nel. |
|
||||
| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
|
||||
| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
|
||||
| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
|
||||
| `:write-all`, `:wa` | Write changes from all views to disk. |
|
||||
| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all views to disk and close all views. |
|
||||
| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all views to disk and close all views forcefully (ignoring unsaved changes). |
|
||||
| `:quit-all`, `:qa` | Close all views. |
|
||||
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
|
||||
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
|
||||
| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
|
||||
| `:theme` | Change the editor theme. |
|
||||
| `:clipboard-yank` | Yank main selection into system clipboard. |
|
||||
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
|
||||
| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
|
||||
| `:primary-clipboard-yank-join` | Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline. |
|
||||
| `:clipboard-paste-after` | Paste system clipboard after selections. |
|
||||
| `:clipboard-paste-before` | Paste system clipboard before selections. |
|
||||
| `:clipboard-paste-replace` | Replace selections with content of system clipboard. |
|
||||
| `:primary-clipboard-paste-after` | Paste primary clipboard after selections. |
|
||||
| `:primary-clipboard-paste-before` | Paste primary clipboard before selections. |
|
||||
| `:primary-clipboard-paste-replace` | Replace selections with content of system primary clipboard. |
|
||||
| `:show-clipboard-provider` | Show clipboard provider name in status bar. |
|
||||
| `:change-current-directory`, `:cd` | Change the current working directory. |
|
||||
| `:show-directory`, `:pwd` | Show the current working directory. |
|
||||
| `:encoding` | Set encoding based on `https://encoding.spec.whatwg.org` |
|
||||
| `:reload` | Discard changes and reload from the source file. |
|
||||
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
|
||||
| `:vsplit`, `:vs` | Open the file in a vertical split. |
|
||||
| `: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 |
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
@ -0,0 +1,10 @@
|
||||
# Language Support
|
||||
|
||||
For more information like arguments passed to default LSP server,
|
||||
extensions assosciated with a filetype, custom LSP settings, filetype
|
||||
specific indent settings, etc see the default
|
||||
[`languages.toml`][languages.toml] file.
|
||||
|
||||
{{#include ./generated/lang-support.md}}
|
||||
|
||||
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
|
@ -0,0 +1,37 @@
|
||||
# Contributing
|
||||
|
||||
Contributors are very welcome! **No contribution is too small and all contributions are valued.**
|
||||
|
||||
Some suggestions to get started:
|
||||
|
||||
- You can look at the [good first issue][good-first-issue] label on the issue tracker.
|
||||
- Help with packaging on various distributions needed!
|
||||
- To use print debugging to the [Helix log file][log-file], you must:
|
||||
* Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
|
||||
* Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher severity inclusive)
|
||||
- If your preferred language is missing, integrating a tree-sitter grammar for
|
||||
it and defining syntax highlight queries for it is straight forward and
|
||||
doesn't require much knowledge of the internals.
|
||||
|
||||
We provide an [architecture.md][architecture.md] that should give you
|
||||
a good overview of the internals.
|
||||
|
||||
# Auto generated documentation
|
||||
|
||||
Some parts of [the book][docs] are autogenerated from the code itself,
|
||||
like the list of `:commands` and supported languages. To generate these
|
||||
files, run
|
||||
|
||||
```shell
|
||||
cargo xtask docgen
|
||||
```
|
||||
|
||||
inside the project. We use [xtask][xtask] as an ad-hoc task runner and
|
||||
thus do not require any dependencies other than `cargo` (You don't have
|
||||
to `cargo install` anything either).
|
||||
|
||||
[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
|
||||
[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
|
||||
[architecture.md]: ./architecture.md
|
||||
[docs]: https://docs.helix-editor.com/
|
||||
[xtask]: https://github.com/matklad/cargo-xtask
|
@ -0,0 +1,490 @@
|
||||
use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ropey::RopeSlice;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cmp;
|
||||
|
||||
use super::Increment;
|
||||
use crate::{Range, Tendril};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct DateTimeIncrementor {
|
||||
date_time: NaiveDateTime,
|
||||
range: Range,
|
||||
fmt: &'static str,
|
||||
field: DateField,
|
||||
}
|
||||
|
||||
impl DateTimeIncrementor {
|
||||
pub fn from_range(text: RopeSlice, range: Range) -> Option<DateTimeIncrementor> {
|
||||
let range = if range.is_empty() {
|
||||
if range.anchor < text.len_chars() {
|
||||
// Treat empty range as a cursor range.
|
||||
range.put_cursor(text, range.anchor + 1, true)
|
||||
} else {
|
||||
// The range is empty and at the end of the text.
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
range
|
||||
};
|
||||
|
||||
FORMATS.iter().find_map(|format| {
|
||||
let from = range.from().saturating_sub(format.max_len);
|
||||
let to = (range.from() + format.max_len).min(text.len_chars());
|
||||
|
||||
let (from_in_text, to_in_text) = (range.from() - from, range.to() - from);
|
||||
let text: Cow<str> = text.slice(from..to).into();
|
||||
|
||||
let captures = format.regex.captures(&text)?;
|
||||
if captures.len() - 1 != format.fields.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let date_time = captures.get(0)?;
|
||||
let offset = range.from() - from_in_text;
|
||||
let range = Range::new(date_time.start() + offset, date_time.end() + offset);
|
||||
|
||||
let field = captures
|
||||
.iter()
|
||||
.skip(1)
|
||||
.enumerate()
|
||||
.find_map(|(i, capture)| {
|
||||
let capture = capture?;
|
||||
let capture_range = capture.range();
|
||||
|
||||
if capture_range.contains(&from_in_text)
|
||||
&& capture_range.contains(&(to_in_text - 1))
|
||||
{
|
||||
Some(format.fields[i])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
let has_date = format.fields.iter().any(|f| f.unit.is_date());
|
||||
let has_time = format.fields.iter().any(|f| f.unit.is_time());
|
||||
|
||||
let date_time = &text[date_time.start()..date_time.end()];
|
||||
let date_time = match (has_date, has_time) {
|
||||
(true, true) => NaiveDateTime::parse_from_str(date_time, format.fmt).ok()?,
|
||||
(true, false) => {
|
||||
let date = NaiveDate::parse_from_str(date_time, format.fmt).ok()?;
|
||||
|
||||
date.and_hms(0, 0, 0)
|
||||
}
|
||||
(false, true) => {
|
||||
let time = NaiveTime::parse_from_str(date_time, format.fmt).ok()?;
|
||||
|
||||
NaiveDate::from_ymd(0, 1, 1).and_time(time)
|
||||
}
|
||||
(false, false) => return None,
|
||||
};
|
||||
|
||||
Some(DateTimeIncrementor {
|
||||
date_time,
|
||||
range,
|
||||
fmt: format.fmt,
|
||||
field,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Increment for DateTimeIncrementor {
|
||||
fn increment(&self, amount: i64) -> (Range, Tendril) {
|
||||
let date_time = match self.field.unit {
|
||||
DateUnit::Years => add_years(self.date_time, amount),
|
||||
DateUnit::Months => add_months(self.date_time, amount),
|
||||
DateUnit::Days => add_duration(self.date_time, Duration::days(amount)),
|
||||
DateUnit::Hours => add_duration(self.date_time, Duration::hours(amount)),
|
||||
DateUnit::Minutes => add_duration(self.date_time, Duration::minutes(amount)),
|
||||
DateUnit::Seconds => add_duration(self.date_time, Duration::seconds(amount)),
|
||||
DateUnit::AmPm => toggle_am_pm(self.date_time),
|
||||
}
|
||||
.unwrap_or(self.date_time);
|
||||
|
||||
(self.range, date_time.format(self.fmt).to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
static FORMATS: Lazy<Vec<Format>> = Lazy::new(|| {
|
||||
vec![
|
||||
Format::new("%Y-%m-%d %H:%M:%S"), // 2021-11-24 07:12:23
|
||||
Format::new("%Y/%m/%d %H:%M:%S"), // 2021/11/24 07:12:23
|
||||
Format::new("%Y-%m-%d %H:%M"), // 2021-11-24 07:12
|
||||
Format::new("%Y/%m/%d %H:%M"), // 2021/11/24 07:12
|
||||
Format::new("%Y-%m-%d"), // 2021-11-24
|
||||
Format::new("%Y/%m/%d"), // 2021/11/24
|
||||
Format::new("%a %b %d %Y"), // Wed Nov 24 2021
|
||||
Format::new("%d-%b-%Y"), // 24-Nov-2021
|
||||
Format::new("%Y %b %d"), // 2021 Nov 24
|
||||
Format::new("%b %d, %Y"), // Nov 24, 2021
|
||||
Format::new("%-I:%M:%S %P"), // 7:21:53 am
|
||||
Format::new("%-I:%M %P"), // 7:21 am
|
||||
Format::new("%-I:%M:%S %p"), // 7:21:53 AM
|
||||
Format::new("%-I:%M %p"), // 7:21 AM
|
||||
Format::new("%H:%M:%S"), // 23:24:23
|
||||
Format::new("%H:%M"), // 23:24
|
||||
]
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Format {
|
||||
fmt: &'static str,
|
||||
fields: Vec<DateField>,
|
||||
regex: Regex,
|
||||
max_len: usize,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
fn new(fmt: &'static str) -> Self {
|
||||
let mut remaining = fmt;
|
||||
let mut fields = Vec::new();
|
||||
let mut regex = String::new();
|
||||
let mut max_len = 0;
|
||||
|
||||
while let Some(i) = remaining.find('%') {
|
||||
let after = &remaining[i + 1..];
|
||||
let mut chars = after.chars();
|
||||
let c = chars.next().unwrap();
|
||||
|
||||
let spec_len = if c == '-' {
|
||||
1 + chars.next().unwrap().len_utf8()
|
||||
} else {
|
||||
c.len_utf8()
|
||||
};
|
||||
|
||||
let specifier = &after[..spec_len];
|
||||
let field = DateField::from_specifier(specifier).unwrap();
|
||||
fields.push(field);
|
||||
max_len += field.max_len + remaining[..i].len();
|
||||
regex += &remaining[..i];
|
||||
regex += &format!("({})", field.regex);
|
||||
remaining = &after[spec_len..];
|
||||
}
|
||||
|
||||
let regex = Regex::new(®ex).unwrap();
|
||||
|
||||
Self {
|
||||
fmt,
|
||||
fields,
|
||||
regex,
|
||||
max_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Format {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.fmt == other.fmt && self.fields == other.fields && self.max_len == other.max_len
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Format {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
struct DateField {
|
||||
regex: &'static str,
|
||||
unit: DateUnit,
|
||||
max_len: usize,
|
||||
}
|
||||
|
||||
impl DateField {
|
||||
fn from_specifier(specifier: &str) -> Option<Self> {
|
||||
match specifier {
|
||||
"Y" => Some(Self {
|
||||
regex: r"\d{4}",
|
||||
unit: DateUnit::Years,
|
||||
max_len: 5,
|
||||
}),
|
||||
"y" => Some(Self {
|
||||
regex: r"\d\d",
|
||||
unit: DateUnit::Years,
|
||||
max_len: 2,
|
||||
}),
|
||||
"m" => Some(Self {
|
||||
regex: r"[0-1]\d",
|
||||
unit: DateUnit::Months,
|
||||
max_len: 2,
|
||||
}),
|
||||
"d" => Some(Self {
|
||||
regex: r"[0-3]\d",
|
||||
unit: DateUnit::Days,
|
||||
max_len: 2,
|
||||
}),
|
||||
"-d" => Some(Self {
|
||||
regex: r"[1-3]?\d",
|
||||
unit: DateUnit::Days,
|
||||
max_len: 2,
|
||||
}),
|
||||
"a" => Some(Self {
|
||||
regex: r"Sun|Mon|Tue|Wed|Thu|Fri|Sat",
|
||||
unit: DateUnit::Days,
|
||||
max_len: 3,
|
||||
}),
|
||||
"A" => Some(Self {
|
||||
regex: r"Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday",
|
||||
unit: DateUnit::Days,
|
||||
max_len: 9,
|
||||
}),
|
||||
"b" | "h" => Some(Self {
|
||||
regex: r"Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",
|
||||
unit: DateUnit::Months,
|
||||
max_len: 3,
|
||||
}),
|
||||
"B" => Some(Self {
|
||||
regex: r"January|February|March|April|May|June|July|August|September|October|November|December",
|
||||
unit: DateUnit::Months,
|
||||
max_len: 9,
|
||||
}),
|
||||
"H" => Some(Self {
|
||||
regex: r"[0-2]\d",
|
||||
unit: DateUnit::Hours,
|
||||
max_len: 2,
|
||||
}),
|
||||
"M" => Some(Self {
|
||||
regex: r"[0-5]\d",
|
||||
unit: DateUnit::Minutes,
|
||||
max_len: 2,
|
||||
}),
|
||||
"S" => Some(Self {
|
||||
regex: r"[0-5]\d",
|
||||
unit: DateUnit::Seconds,
|
||||
max_len: 2,
|
||||
}),
|
||||
"I" => Some(Self {
|
||||
regex: r"[0-1]\d",
|
||||
unit: DateUnit::Hours,
|
||||
max_len: 2,
|
||||
}),
|
||||
"-I" => Some(Self {
|
||||
regex: r"1?\d",
|
||||
unit: DateUnit::Hours,
|
||||
max_len: 2,
|
||||
}),
|
||||
"P" => Some(Self {
|
||||
regex: r"am|pm",
|
||||
unit: DateUnit::AmPm,
|
||||
max_len: 2,
|
||||
}),
|
||||
"p" => Some(Self {
|
||||
regex: r"AM|PM",
|
||||
unit: DateUnit::AmPm,
|
||||
max_len: 2,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum DateUnit {
|
||||
Years,
|
||||
Months,
|
||||
Days,
|
||||
Hours,
|
||||
Minutes,
|
||||
Seconds,
|
||||
AmPm,
|
||||
}
|
||||
|
||||
impl DateUnit {
|
||||
fn is_date(self) -> bool {
|
||||
matches!(self, DateUnit::Years | DateUnit::Months | DateUnit::Days)
|
||||
}
|
||||
|
||||
fn is_time(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
DateUnit::Hours | DateUnit::Minutes | DateUnit::Seconds
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn ndays_in_month(year: i32, month: u32) -> u32 {
|
||||
// The first day of the next month...
|
||||
let (y, m) = if month == 12 {
|
||||
(year + 1, 1)
|
||||
} else {
|
||||
(year, month + 1)
|
||||
};
|
||||
let d = NaiveDate::from_ymd(y, m, 1);
|
||||
|
||||
// ...is preceded by the last day of the original month.
|
||||
d.pred().day()
|
||||
}
|
||||
|
||||
fn add_months(date_time: NaiveDateTime, amount: i64) -> Option<NaiveDateTime> {
|
||||
let month = (date_time.month0() as i64).checked_add(amount)?;
|
||||
let year = date_time.year() + i32::try_from(month / 12).ok()?;
|
||||
let year = if month.is_negative() { year - 1 } else { year };
|
||||
|
||||
// Normalize month
|
||||
let month = month % 12;
|
||||
let month = if month.is_negative() {
|
||||
month + 12
|
||||
} else {
|
||||
month
|
||||
} as u32
|
||||
+ 1;
|
||||
|
||||
let day = cmp::min(date_time.day(), ndays_in_month(year, month));
|
||||
|
||||
Some(NaiveDate::from_ymd(year, month, day).and_time(date_time.time()))
|
||||
}
|
||||
|
||||
fn add_years(date_time: NaiveDateTime, amount: i64) -> Option<NaiveDateTime> {
|
||||
let year = i32::try_from((date_time.year() as i64).checked_add(amount)?).ok()?;
|
||||
let ndays = ndays_in_month(year, date_time.month());
|
||||
|
||||
if date_time.day() > ndays {
|
||||
let d = NaiveDate::from_ymd(year, date_time.month(), ndays);
|
||||
Some(d.succ().and_time(date_time.time()))
|
||||
} else {
|
||||
date_time.with_year(year)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_duration(date_time: NaiveDateTime, duration: Duration) -> Option<NaiveDateTime> {
|
||||
date_time.checked_add_signed(duration)
|
||||
}
|
||||
|
||||
fn toggle_am_pm(date_time: NaiveDateTime) -> Option<NaiveDateTime> {
|
||||
if date_time.hour() < 12 {
|
||||
add_duration(date_time, Duration::hours(12))
|
||||
} else {
|
||||
add_duration(date_time, Duration::hours(-12))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Rope;
|
||||
|
||||
#[test]
|
||||
fn test_increment_date_times() {
|
||||
let tests = [
|
||||
// (original, cursor, amount, expected)
|
||||
("2020-02-28", 0, 1, "2021-02-28"),
|
||||
("2020-02-29", 0, 1, "2021-03-01"),
|
||||
("2020-01-31", 5, 1, "2020-02-29"),
|
||||
("2020-01-20", 5, 1, "2020-02-20"),
|
||||
("2021-01-01", 5, -1, "2020-12-01"),
|
||||
("2021-01-31", 5, -2, "2020-11-30"),
|
||||
("2020-02-28", 8, 1, "2020-02-29"),
|
||||
("2021-02-28", 8, 1, "2021-03-01"),
|
||||
("2021-02-28", 0, -1, "2020-02-28"),
|
||||
("2021-03-01", 0, -1, "2020-03-01"),
|
||||
("2020-02-29", 5, -1, "2020-01-29"),
|
||||
("2020-02-20", 5, -1, "2020-01-20"),
|
||||
("2020-02-29", 8, -1, "2020-02-28"),
|
||||
("2021-03-01", 8, -1, "2021-02-28"),
|
||||
("1980/12/21", 8, 100, "1981/03/31"),
|
||||
("1980/12/21", 8, -100, "1980/09/12"),
|
||||
("1980/12/21", 8, 1000, "1983/09/17"),
|
||||
("1980/12/21", 8, -1000, "1978/03/27"),
|
||||
("2021-11-24 07:12:23", 0, 1, "2022-11-24 07:12:23"),
|
||||
("2021-11-24 07:12:23", 5, 1, "2021-12-24 07:12:23"),
|
||||
("2021-11-24 07:12:23", 8, 1, "2021-11-25 07:12:23"),
|
||||
("2021-11-24 07:12:23", 11, 1, "2021-11-24 08:12:23"),
|
||||
("2021-11-24 07:12:23", 14, 1, "2021-11-24 07:13:23"),
|
||||
("2021-11-24 07:12:23", 17, 1, "2021-11-24 07:12:24"),
|
||||
("2021/11/24 07:12:23", 0, 1, "2022/11/24 07:12:23"),
|
||||
("2021/11/24 07:12:23", 5, 1, "2021/12/24 07:12:23"),
|
||||
("2021/11/24 07:12:23", 8, 1, "2021/11/25 07:12:23"),
|
||||
("2021/11/24 07:12:23", 11, 1, "2021/11/24 08:12:23"),
|
||||
("2021/11/24 07:12:23", 14, 1, "2021/11/24 07:13:23"),
|
||||
("2021/11/24 07:12:23", 17, 1, "2021/11/24 07:12:24"),
|
||||
("2021-11-24 07:12", 0, 1, "2022-11-24 07:12"),
|
||||
("2021-11-24 07:12", 5, 1, "2021-12-24 07:12"),
|
||||
("2021-11-24 07:12", 8, 1, "2021-11-25 07:12"),
|
||||
("2021-11-24 07:12", 11, 1, "2021-11-24 08:12"),
|
||||
("2021-11-24 07:12", 14, 1, "2021-11-24 07:13"),
|
||||
("2021/11/24 07:12", 0, 1, "2022/11/24 07:12"),
|
||||
("2021/11/24 07:12", 5, 1, "2021/12/24 07:12"),
|
||||
("2021/11/24 07:12", 8, 1, "2021/11/25 07:12"),
|
||||
("2021/11/24 07:12", 11, 1, "2021/11/24 08:12"),
|
||||
("2021/11/24 07:12", 14, 1, "2021/11/24 07:13"),
|
||||
("Wed Nov 24 2021", 0, 1, "Thu Nov 25 2021"),
|
||||
("Wed Nov 24 2021", 4, 1, "Fri Dec 24 2021"),
|
||||
("Wed Nov 24 2021", 8, 1, "Thu Nov 25 2021"),
|
||||
("Wed Nov 24 2021", 11, 1, "Thu Nov 24 2022"),
|
||||
("24-Nov-2021", 0, 1, "25-Nov-2021"),
|
||||
("24-Nov-2021", 3, 1, "24-Dec-2021"),
|
||||
("24-Nov-2021", 7, 1, "24-Nov-2022"),
|
||||
("2021 Nov 24", 0, 1, "2022 Nov 24"),
|
||||
("2021 Nov 24", 5, 1, "2021 Dec 24"),
|
||||
("2021 Nov 24", 9, 1, "2021 Nov 25"),
|
||||
("Nov 24, 2021", 0, 1, "Dec 24, 2021"),
|
||||
("Nov 24, 2021", 4, 1, "Nov 25, 2021"),
|
||||
("Nov 24, 2021", 8, 1, "Nov 24, 2022"),
|
||||
("7:21:53 am", 0, 1, "8:21:53 am"),
|
||||
("7:21:53 am", 3, 1, "7:22:53 am"),
|
||||
("7:21:53 am", 5, 1, "7:21:54 am"),
|
||||
("7:21:53 am", 8, 1, "7:21:53 pm"),
|
||||
("7:21:53 AM", 0, 1, "8:21:53 AM"),
|
||||
("7:21:53 AM", 3, 1, "7:22:53 AM"),
|
||||
("7:21:53 AM", 5, 1, "7:21:54 AM"),
|
||||
("7:21:53 AM", 8, 1, "7:21:53 PM"),
|
||||
("7:21 am", 0, 1, "8:21 am"),
|
||||
("7:21 am", 3, 1, "7:22 am"),
|
||||
("7:21 am", 5, 1, "7:21 pm"),
|
||||
("7:21 AM", 0, 1, "8:21 AM"),
|
||||
("7:21 AM", 3, 1, "7:22 AM"),
|
||||
("7:21 AM", 5, 1, "7:21 PM"),
|
||||
("23:24:23", 1, 1, "00:24:23"),
|
||||
("23:24:23", 3, 1, "23:25:23"),
|
||||
("23:24:23", 6, 1, "23:24:24"),
|
||||
("23:24", 1, 1, "00:24"),
|
||||
("23:24", 3, 1, "23:25"),
|
||||
];
|
||||
|
||||
for (original, cursor, amount, expected) in tests {
|
||||
let rope = Rope::from_str(original);
|
||||
let range = Range::new(cursor, cursor + 1);
|
||||
assert_eq!(
|
||||
DateTimeIncrementor::from_range(rope.slice(..), range)
|
||||
.unwrap()
|
||||
.increment(amount)
|
||||
.1,
|
||||
Tendril::from(expected)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_date_times() {
|
||||
let tests = [
|
||||
"0000-00-00",
|
||||
"1980-2-21",
|
||||
"1980-12-1",
|
||||
"12345",
|
||||
"2020-02-30",
|
||||
"1999-12-32",
|
||||
"19-12-32",
|
||||
"1-2-3",
|
||||
"0000/00/00",
|
||||
"1980/2/21",
|
||||
"1980/12/1",
|
||||
"12345",
|
||||
"2020/02/30",
|
||||
"1999/12/32",
|
||||
"19/12/32",
|
||||
"1/2/3",
|
||||
"123:456:789",
|
||||
"11:61",
|
||||
"2021-55-12 08:12:54",
|
||||
];
|
||||
|
||||
for invalid in tests {
|
||||
let rope = Rope::from_str(invalid);
|
||||
let range = Range::new(0, 1);
|
||||
|
||||
assert_eq!(DateTimeIncrementor::from_range(rope.slice(..), range), None)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
pub mod date_time;
|
||||
pub mod number;
|
||||
|
||||
use crate::{Range, Tendril};
|
||||
|
||||
pub trait Increment {
|
||||
fn increment(&self, amount: i64) -> (Range, Tendril);
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Get the vec of escaped / quoted / doublequoted filenames from the input str
|
||||
pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
|
||||
enum State {
|
||||
Normal,
|
||||
NormalEscaped,
|
||||
Quoted,
|
||||
QuoteEscaped,
|
||||
Dquoted,
|
||||
DquoteEscaped,
|
||||
}
|
||||
|
||||
use State::*;
|
||||
|
||||
let mut state = Normal;
|
||||
let mut args: Vec<Cow<str>> = Vec::new();
|
||||
let mut escaped = String::with_capacity(input.len());
|
||||
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
|
||||
for (i, c) in input.char_indices() {
|
||||
state = match state {
|
||||
Normal => match c {
|
||||
'\\' => {
|
||||
escaped.push_str(&input[start..i]);
|
||||
start = i + 1;
|
||||
NormalEscaped
|
||||
}
|
||||
'"' => {
|
||||
end = i;
|
||||
Dquoted
|
||||
}
|
||||
'\'' => {
|
||||
end = i;
|
||||
Quoted
|
||||
}
|
||||
c if c.is_ascii_whitespace() => {
|
||||
end = i;
|
||||
Normal
|
||||
}
|
||||
_ => Normal,
|
||||
},
|
||||
NormalEscaped => Normal,
|
||||
Quoted => match c {
|
||||
'\\' => {
|
||||
escaped.push_str(&input[start..i]);
|
||||
start = i + 1;
|
||||
QuoteEscaped
|
||||
}
|
||||
'\'' => {
|
||||
end = i;
|
||||
Normal
|
||||
}
|
||||
_ => Quoted,
|
||||
},
|
||||
QuoteEscaped => Quoted,
|
||||
Dquoted => match c {
|
||||
'\\' => {
|
||||
escaped.push_str(&input[start..i]);
|
||||
start = i + 1;
|
||||
DquoteEscaped
|
||||
}
|
||||
'"' => {
|
||||
end = i;
|
||||
Normal
|
||||
}
|
||||
_ => Dquoted,
|
||||
},
|
||||
DquoteEscaped => Dquoted,
|
||||
};
|
||||
|
||||
if i >= input.len() - 1 && end == 0 {
|
||||
end = i + 1;
|
||||
}
|
||||
|
||||
if end > 0 {
|
||||
let esc_trim = escaped.trim();
|
||||
let inp = &input[start..end];
|
||||
|
||||
if !(esc_trim.is_empty() && inp.trim().is_empty()) {
|
||||
if esc_trim.is_empty() {
|
||||
args.push(inp.into());
|
||||
} else {
|
||||
args.push([escaped, inp.into()].concat().into());
|
||||
escaped = "".to_string();
|
||||
}
|
||||
}
|
||||
start = i + 1;
|
||||
end = 0;
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_normal() {
|
||||
let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
|
||||
let result = shellwords(input);
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó"),
|
||||
Cow::from("wörds"),
|
||||
Cow::from(r#"three "with escaping\"#),
|
||||
];
|
||||
// TODO test is_owned and is_borrowed, once they get stabilized.
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quoted() {
|
||||
let quoted =
|
||||
r#":o 'single_word' 'twó wörds' '' ' ''\three\' \"with\ escaping\\' 'quote incomplete"#;
|
||||
let result = shellwords(quoted);
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from(r#"three' "with escaping\"#),
|
||||
Cow::from("quote incomplete"),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dquoted() {
|
||||
let dquoted = r#":o "single_word" "twó wörds" "" " ""\three\' \"with\ escaping\\" "dquote incomplete"#;
|
||||
let result = shellwords(dquoted);
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from(r#"three' "with escaping\"#),
|
||||
Cow::from("dquote incomplete"),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed() {
|
||||
let dquoted = r#":o single_word 'twó wörds' "\three\' \"with\ escaping\\""no space before"'and after' $#%^@ "%^&(%^" ')(*&^%''a\\\\\b' '"#;
|
||||
let result = shellwords(dquoted);
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from("three' \"with escaping\\"),
|
||||
Cow::from("no space before"),
|
||||
Cow::from("and after"),
|
||||
Cow::from("$#%^@"),
|
||||
Cow::from("%^&(%^"),
|
||||
Cow::from(")(*&^%"),
|
||||
Cow::from(r#"a\\b"#),
|
||||
//last ' just changes to quoted but since we dont have anything after it, it should be ignored
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@
|
||||
helix-syntax
|
||||
============
|
||||
|
||||
Syntax highlighting for helix, (shallow) submodules resides here.
|
||||
|
||||
Differences from nvim-treesitter
|
||||
--------------------------------
|
||||
|
||||
As the syntax are commonly ported from
|
||||
<https://github.com/nvim-treesitter/nvim-treesitter>.
|
||||
|
||||
Note that we do not support the custom `#any-of` predicate which is
|
||||
supported by neovim so one needs to change it to `#match` with regex.
|
@ -0,0 +1 @@
|
||||
Subproject commit 5dd3c62f1bbe378b220fe16b317b85247898639e
|
@ -0,0 +1 @@
|
||||
Subproject commit 6a25376685d1d47968c2cef06d4db8d84a70025e
|
@ -0,0 +1 @@
|
||||
Subproject commit 7af32bc04a66ab196f5b9f92ac471f29372ae2ce
|
@ -0,0 +1 @@
|
||||
Subproject commit bd50ccf66b42c55252ac8efc1086af4ac6bab8cd
|
@ -0,0 +1 @@
|
||||
Subproject commit 04e54ab6585dfd4fee6ddfe5849af56f101b6d4f
|
@ -0,0 +1 @@
|
||||
Subproject commit 066e395e1107df17183cf3ae4230f1a1406cc972
|
@ -0,0 +1 @@
|
||||
Subproject commit 0e4f0baf90b57e5aeb62dcdbf03062c6315d43ea
|
@ -0,0 +1 @@
|
||||
Subproject commit c12e6ecb54485f764250556ffd7ccb18f8e2942b
|
@ -0,0 +1 @@
|
||||
Subproject commit 332dc528f27044bc4427024dbb33e6941fc131f2
|
@ -1 +1 @@
|
||||
Subproject commit 2a83dfdd759a632651f852aa4dc0af2525fae5cd
|
||||
Subproject commit 0fa917a7022d1cd2e9b779a6a8fc5dc7fad69c75
|
@ -0,0 +1 @@
|
||||
Subproject commit 5e66e961eee421786bdda8495ed1db045e06b5fe
|
@ -1 +1 @@
|
||||
Subproject commit 237f4eb4417c28f643a29d795ed227246afb66f9
|
||||
Subproject commit b6ec26f181dd059eedd506fa5fbeae1b8e5556c8
|
@ -0,0 +1 @@
|
||||
Subproject commit 3ec55082cf0be015d03148be8edfdfa8c56e77f9
|
@ -0,0 +1 @@
|
||||
Subproject commit d98426109258b266e1e92358c5f11716d2e8f638
|
@ -0,0 +1 @@
|
||||
Subproject commit 3b213925b9c4f42c1acfe2e10bfbb438d9c6834d
|
@ -0,0 +1 @@
|
||||
Subproject commit 06fabca19454b2dc00c1b211a7cb7ad0bc2585f1
|
@ -0,0 +1 @@
|
||||
Subproject commit a4b9187417d6be349ee5fd4b6e77b4172c6827dd
|
@ -0,0 +1 @@
|
||||
Subproject commit ad8c32917a16dfbb387d1da567bf0c3fb6fffde2
|
@ -1 +1 @@
|
||||
Subproject commit 0d63eaf94e8d6c0694551b016c802787e61b3fb2
|
||||
Subproject commit 57f855461aeeca73bd4218754fb26b5ac143f98f
|
@ -0,0 +1 @@
|
||||
Subproject commit e1cfca3c79896ff79842f057ea13e529b66af636
|
@ -0,0 +1 @@
|
||||
Subproject commit 761eb9126b65e078b1b5770ac296b4af8870f933
|
@ -1 +1 @@
|
||||
Subproject commit fb23ed9a99da012d86b7a5059b9d8928607cce29
|
||||
Subproject commit 0a3dd53a7fc4b352a538397d054380aaa28be54c
|
@ -0,0 +1 @@
|
||||
Subproject commit 568dd8a937347175fd58db83d4c4cdaeb6069bd2
|
@ -0,0 +1 @@
|
||||
Subproject commit b7444181fb38e603e25ea8fcdac55f9492e49c27
|
@ -1 +1 @@
|
||||
Subproject commit 1f27fd1dfe7f352408f01b4894c7825f3a1d6c47
|
||||
Subproject commit 93331b8bd8b4ebee2b575490b2758f16ad4e9f30
|
@ -1,12 +1,17 @@
|
||||
use std::borrow::Cow;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let git_hash = Command::new("git")
|
||||
.args(&["describe", "--dirty"])
|
||||
.args(&["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.map(|x| String::from_utf8(x.stdout).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| String::from(env!("CARGO_PKG_VERSION")));
|
||||
println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", git_hash);
|
||||
.and_then(|x| String::from_utf8(x.stdout).ok());
|
||||
|
||||
let version: Cow<_> = match git_hash {
|
||||
Some(git_hash) => format!("{} ({})", env!("CARGO_PKG_VERSION"), &git_hash[..8]).into(),
|
||||
None => env!("CARGO_PKG_VERSION").into(),
|
||||
};
|
||||
|
||||
println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue