mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 10:26:18 +04:00
define new config options
This commit is contained in:
parent
5e74d3c821
commit
7ba8674466
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1053,11 +1053,15 @@ version = "23.10.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"globset",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1102,6 +1106,7 @@ version = "23.10.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"fern",
|
"fern",
|
||||||
|
"helix-config",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1250,6 +1255,7 @@ dependencies = [
|
|||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"helix-config",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-dap",
|
"helix-dap",
|
||||||
"helix-event",
|
"helix-event",
|
||||||
|
@ -19,6 +19,10 @@ anyhow = "1.0.79"
|
|||||||
indexmap = { version = "2.1.0", features = ["serde"] }
|
indexmap = { version = "2.1.0", features = ["serde"] }
|
||||||
serde = { version = "1.0" }
|
serde = { version = "1.0" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
globset = "0.4.14"
|
||||||
|
regex = "1.10.2"
|
||||||
|
regex-syntax = "0.8.2"
|
||||||
|
which = "5.0.0"
|
||||||
|
|
||||||
regex-syntax = "0.8.2"
|
regex-syntax = "0.8.2"
|
||||||
which = "5.0.0"
|
which = "5.0.0"
|
||||||
|
113
helix-config/src/definition.rs
Normal file
113
helix-config/src/definition.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
mod language;
|
||||||
|
mod lsp;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
|
pub use lsp::init_language_server_config;
|
||||||
|
|
||||||
|
options! {
|
||||||
|
use ui::*;
|
||||||
|
use lsp::*;
|
||||||
|
use language::*;
|
||||||
|
|
||||||
|
struct WrapConfig {
|
||||||
|
/// Soft wrap lines that exceed viewport width.
|
||||||
|
enable: bool = false,
|
||||||
|
/// Maximum free space left at the end of the line.
|
||||||
|
/// Automatically limited to a quarter of the viewport.
|
||||||
|
max_wrap: u16 = 20,
|
||||||
|
/// Maximum indentation to carry over when soft wrapping a line.
|
||||||
|
/// Automatically limited to a quarter of the viewport.
|
||||||
|
max_indent_retain: u16 = 40,
|
||||||
|
/// Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap`.
|
||||||
|
wrap_indicator: String = "↪",
|
||||||
|
/// Soft wrap at `text-width` instead of using the full viewport size.
|
||||||
|
wrap_at_text_width: bool = false,
|
||||||
|
/// Maximum line length. Used for the `:reflow` command and
|
||||||
|
/// soft-wrapping if `soft-wrap.wrap-at-text-width` is set
|
||||||
|
text_width: usize = 80,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MouseConfig {
|
||||||
|
/// Enable mouse mode
|
||||||
|
#[read = copy]
|
||||||
|
mouse: bool = true,
|
||||||
|
/// Number of lines to scroll per scroll wheel step.
|
||||||
|
#[read = copy]
|
||||||
|
scroll_lines: usize = 3,
|
||||||
|
/// Middle click paste support
|
||||||
|
#[read = copy]
|
||||||
|
middle_click_paste: bool = true,
|
||||||
|
}
|
||||||
|
struct SmartTabConfig {
|
||||||
|
/// If set to true, then when the cursor is in a position with
|
||||||
|
/// non-whitespace to its left, instead of inserting a tab, it will run
|
||||||
|
/// `move_parent_node_end`. If there is only whitespace to the left,
|
||||||
|
/// then it inserts a tab as normal. With the default bindings, to
|
||||||
|
/// explicitly insert a tab character, press Shift-tab.
|
||||||
|
#[name = "smart-tab.enable"]
|
||||||
|
#[read = copy]
|
||||||
|
enable: bool = true,
|
||||||
|
/// Normally, when a menu is on screen, such as when auto complete
|
||||||
|
/// is triggered, the tab key is bound to cycling through the items.
|
||||||
|
/// This means when menus are on screen, one cannot use the tab key
|
||||||
|
/// to trigger the `smart-tab` command. If this option is set to true,
|
||||||
|
/// the `smart-tab` command always takes precedence, which means one
|
||||||
|
/// cannot use the tab key to cycle through menu items. One of the other
|
||||||
|
/// bindings must be used instead, such as arrow keys or `C-n`/`C-p`.
|
||||||
|
#[name = "smart-tab.supersede-menu"]
|
||||||
|
#[read = copy]
|
||||||
|
supersede_menu: bool = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchConfig {
|
||||||
|
/// Enable smart case regex searching (case-insensitive unless pattern
|
||||||
|
/// contains upper case characters)
|
||||||
|
#[name = "search.smart-case"]
|
||||||
|
#[read = copy]
|
||||||
|
smart_case: bool = true,
|
||||||
|
/// Whether the search should wrap after depleting the matches
|
||||||
|
#[name = "search.wrap-round"]
|
||||||
|
#[read = copy]
|
||||||
|
wrap_round: bool = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MiscConfig {
|
||||||
|
/// Number of lines of padding around the edge of the screen when scrolling.
|
||||||
|
#[read = copy]
|
||||||
|
scrolloff: usize = 5,
|
||||||
|
/// Shell to use when running external commands
|
||||||
|
#[read = deref]
|
||||||
|
shell: List<String> = if cfg!(windows) {
|
||||||
|
&["cmd", "/C"]
|
||||||
|
} else {
|
||||||
|
&["sh", "-c"]
|
||||||
|
},
|
||||||
|
/// Enable automatic saving on the focus moving away from Helix.
|
||||||
|
/// Requires [focus event support](https://github.com/helix-editor/
|
||||||
|
/// helix/wiki/Terminal-Support) from your terminal
|
||||||
|
#[read = copy]
|
||||||
|
auto_save: bool = false,
|
||||||
|
/// Whether to automatically insert a trailing line-ending on write
|
||||||
|
/// if missing
|
||||||
|
#[read = copy]
|
||||||
|
insert_final_newline: bool = true,
|
||||||
|
/// Time in milliseconds since last keypress before idle timers trigger.
|
||||||
|
/// Used for autocompletion, set to 0 for instant
|
||||||
|
#[read = copy]
|
||||||
|
idle_timeout: Duration = Duration::from_millis(250),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ty for Duration {
|
||||||
|
fn from_value(val: Value) -> anyhow::Result<Self> {
|
||||||
|
let val: usize = val.typed()?;
|
||||||
|
Ok(Duration::from_millis(val as _))
|
||||||
|
}
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
Value::Int(self.as_millis().try_into().unwrap())
|
||||||
|
}
|
||||||
|
}
|
27
helix-config/src/definition/language.rs
Normal file
27
helix-config/src/definition/language.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct LanguageConfig {
|
||||||
|
/// regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site.
|
||||||
|
#[validator = regex_str_validator()]
|
||||||
|
injection_regex: Option<String> = None,
|
||||||
|
/// The interpreters from the shebang line, for example `["sh", "bash"]`
|
||||||
|
#[read = deref]
|
||||||
|
shebangs: List<String> = List::default(),
|
||||||
|
/// The token to use as a comment-token
|
||||||
|
#[read = deref]
|
||||||
|
comment_token: String = "//",
|
||||||
|
/// The tree-sitter grammar to use (defaults to the language name)
|
||||||
|
grammar: Option<String> = None,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FormatterConfiguration {
|
||||||
|
#[read = copy]
|
||||||
|
auto_format: bool = true,
|
||||||
|
#[name = "formatter.command"]
|
||||||
|
formatter_command: Option<String> = None,
|
||||||
|
#[name = "formatter.args"]
|
||||||
|
#[read = deref]
|
||||||
|
formatter_args: List<String> = List::default(),
|
||||||
|
}
|
||||||
|
}
|
266
helix-config/src/definition/lsp.rs
Normal file
266
helix-config/src/definition/lsp.rs
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Describes the severity level of a [`Diagnostic`].
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||||
|
pub enum Severity {
|
||||||
|
Hint,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ty for Severity {
|
||||||
|
fn from_value(val: Value) -> anyhow::Result<Self> {
|
||||||
|
let val: String = val.typed()?;
|
||||||
|
match &*val {
|
||||||
|
"hint" => Ok(Severity::Hint),
|
||||||
|
"info" => Ok(Severity::Info),
|
||||||
|
"warning" => Ok(Severity::Warning),
|
||||||
|
"error" => Ok(Severity::Error),
|
||||||
|
_ => bail!("expected one of 'hint', 'info', 'warning' or 'error' (got {val:?})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
match self {
|
||||||
|
Severity::Hint => "hint".into(),
|
||||||
|
Severity::Info => "info".into(),
|
||||||
|
Severity::Warning => "warning".into(),
|
||||||
|
Severity::Error => "error".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to stdx
|
||||||
|
/// Helper macro that automatically generates an array
|
||||||
|
/// that contains all variants of an enum
|
||||||
|
macro_rules! variant_list {
|
||||||
|
(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
$vis: vis enum $name: ident {
|
||||||
|
$($(#[$inner: meta])* $variant: ident $(= $_: literal)?),*$(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
$(#[$outer])*
|
||||||
|
$vis enum $name {
|
||||||
|
$($(#[$inner])* $variant),*
|
||||||
|
}
|
||||||
|
impl $name {
|
||||||
|
$vis const ALL: &[$name] = &[$(Self::$variant),*];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
variant_list! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum LanguageServerFeature {
|
||||||
|
Format,
|
||||||
|
GotoDeclaration,
|
||||||
|
GotoDefinition,
|
||||||
|
GotoTypeDefinition,
|
||||||
|
GotoReference,
|
||||||
|
GotoImplementation,
|
||||||
|
// Goto, use bitflags, combining previous Goto members?
|
||||||
|
SignatureHelp,
|
||||||
|
Hover,
|
||||||
|
DocumentHighlight,
|
||||||
|
Completion,
|
||||||
|
CodeAction,
|
||||||
|
WorkspaceCommand,
|
||||||
|
DocumentSymbols,
|
||||||
|
WorkspaceSymbols,
|
||||||
|
// Symbols, use bitflags, see above?
|
||||||
|
Diagnostics,
|
||||||
|
RenameSymbol,
|
||||||
|
InlayHints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerFeature {
|
||||||
|
fn to_str(self) -> &'static str {
|
||||||
|
use LanguageServerFeature::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Format => "format",
|
||||||
|
GotoDeclaration => "goto-declaration",
|
||||||
|
GotoDefinition => "goto-definition",
|
||||||
|
GotoTypeDefinition => "goto-type-definition",
|
||||||
|
GotoReference => "goto-reference",
|
||||||
|
GotoImplementation => "goto-implementation",
|
||||||
|
SignatureHelp => "signature-help",
|
||||||
|
Hover => "hover",
|
||||||
|
DocumentHighlight => "document-highlight",
|
||||||
|
Completion => "completion",
|
||||||
|
CodeAction => "code-action",
|
||||||
|
WorkspaceCommand => "workspace-command",
|
||||||
|
DocumentSymbols => "document-symbols",
|
||||||
|
WorkspaceSymbols => "workspace-symbols",
|
||||||
|
Diagnostics => "diagnostics",
|
||||||
|
RenameSymbol => "rename-symbol",
|
||||||
|
InlayHints => "inlay-hints",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn description(self) -> &'static str {
|
||||||
|
use LanguageServerFeature::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Format => "Use this language server for autoformatting.",
|
||||||
|
GotoDeclaration => "Use this language server for the goto_declaration command.",
|
||||||
|
GotoDefinition => "Use this language server for the goto_definition command.",
|
||||||
|
GotoTypeDefinition => "Use this language server for the goto_type_definition command.",
|
||||||
|
GotoReference => "Use this language server for the goto_reference command.",
|
||||||
|
GotoImplementation => "Use this language server for the goto_implementation command.",
|
||||||
|
SignatureHelp => "Use this language server to display signature help.",
|
||||||
|
Hover => "Use this language server to display hover information.",
|
||||||
|
DocumentHighlight => {
|
||||||
|
"Use this language server for the select_references_to_symbol_under_cursor command."
|
||||||
|
}
|
||||||
|
Completion => "Request completion items from this language server.",
|
||||||
|
CodeAction => "Use this language server for the code_action command.",
|
||||||
|
WorkspaceCommand => "Use this language server for :lsp-workspace-command.",
|
||||||
|
DocumentSymbols => "Use this language server for the symbol_picker command.",
|
||||||
|
WorkspaceSymbols => "Use this language server for the workspace_symbol_picker command.",
|
||||||
|
Diagnostics => "Display diagnostics emitted by this language server.",
|
||||||
|
RenameSymbol => "Use this language server for the rename_symbol command.",
|
||||||
|
InlayHints => "Display inlay hints form this language server.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LanguageServerFeature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let feature = self.to_str();
|
||||||
|
write!(f, "{feature}",)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for LanguageServerFeature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ty for LanguageServerFeature {
|
||||||
|
fn from_value(val: Value) -> anyhow::Result<Self> {
|
||||||
|
let val: String = val.typed()?;
|
||||||
|
use LanguageServerFeature::*;
|
||||||
|
|
||||||
|
match &*val {
|
||||||
|
"format" => Ok(Format),
|
||||||
|
"goto-declaration" => Ok(GotoDeclaration),
|
||||||
|
"goto-definition" => Ok(GotoDefinition),
|
||||||
|
"goto-type-definition" => Ok(GotoTypeDefinition),
|
||||||
|
"goto-reference" => Ok(GotoReference),
|
||||||
|
"goto-implementation" => Ok(GotoImplementation),
|
||||||
|
"signature-help" => Ok(SignatureHelp),
|
||||||
|
"hover" => Ok(Hover),
|
||||||
|
"document-highlight" => Ok(DocumentHighlight),
|
||||||
|
"completion" => Ok(Completion),
|
||||||
|
"code-action" => Ok(CodeAction),
|
||||||
|
"workspace-command" => Ok(WorkspaceCommand),
|
||||||
|
"document-symbols" => Ok(DocumentSymbols),
|
||||||
|
"workspace-symbols" => Ok(WorkspaceSymbols),
|
||||||
|
"diagnostics" => Ok(Diagnostics),
|
||||||
|
"rename-symbol" => Ok(RenameSymbol),
|
||||||
|
"inlay-hints" => Ok(InlayHints),
|
||||||
|
_ => bail!("invalid language server feature {val}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
Value::String(self.to_str().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_language_server_config(registry: &mut OptionRegistry, languag_server: &str) {
|
||||||
|
registry.register(
|
||||||
|
&format!("language-servers.{languag_server}.active"),
|
||||||
|
"Wether this language servers is used for a buffer",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
for &feature in LanguageServerFeature::ALL {
|
||||||
|
registry.register(
|
||||||
|
&format!("language-servers.{languag_server}.{feature}"),
|
||||||
|
feature.description(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct LspConfig {
|
||||||
|
/// Enables LSP integration. Setting to false will completely disable language servers.
|
||||||
|
#[name = "lsp.enable"]
|
||||||
|
#[read = copy]
|
||||||
|
enable: bool = true,
|
||||||
|
/// Enables LSP integration. Setting to false will completely disable language servers.
|
||||||
|
#[name = "lsp.display-messages"]
|
||||||
|
#[read = copy]
|
||||||
|
display_messages: bool = false,
|
||||||
|
/// Enable automatic popup of signature help (parameter hints)
|
||||||
|
#[name = "lsp.auto-signature-help"]
|
||||||
|
#[read = copy]
|
||||||
|
auto_signature_help: bool = true,
|
||||||
|
/// Enable automatic popup of signature help (parameter hints)
|
||||||
|
#[name = "lsp.display-inlay-hints"]
|
||||||
|
#[read = copy]
|
||||||
|
display_inlay_hints: bool = false,
|
||||||
|
/// Display docs under signature help popup
|
||||||
|
#[name = "lsp.display-signature-help-docs"]
|
||||||
|
#[read = copy]
|
||||||
|
display_signature_help_docs: bool = true,
|
||||||
|
/// Enables snippet completions. Requires a server restart
|
||||||
|
/// (`:lsp-restart`) to take effect after `:config-reload`/`:set`.
|
||||||
|
#[name = "lsp.snippets"]
|
||||||
|
#[read = copy]
|
||||||
|
snippets: bool = true,
|
||||||
|
/// Include declaration in the goto references popup.
|
||||||
|
#[name = "lsp.goto-reference-include-declaration"]
|
||||||
|
#[read = copy]
|
||||||
|
goto_reference_include_declaration: bool = true,
|
||||||
|
// TODO(breaing): prefix all options below with `lsp.`
|
||||||
|
/// The language-id for language servers, checkout the
|
||||||
|
/// table at [TextDocumentItem](https://microsoft.github.io/
|
||||||
|
/// language-server-protocol/specifications/lsp/3.17/specification/
|
||||||
|
/// #textDocumentItem) for the right id
|
||||||
|
#[name = "languague-id"]
|
||||||
|
language_server_id: Option<String> = None,
|
||||||
|
// TODO(breaking): rename to root-markers to differentiate from workspace-roots
|
||||||
|
// TODO: also makes this setteble on the language server
|
||||||
|
/// A set of marker files to look for when trying to find the workspace
|
||||||
|
/// root. For example `Cargo.lock`, `yarn.lock`
|
||||||
|
roots: List<String> = List::default(),
|
||||||
|
// TODO: also makes this setteble on the language server
|
||||||
|
/// Directories relative to the workspace root that are treated as LSP
|
||||||
|
/// roots. The search for root markers (starting at the path of the
|
||||||
|
/// file) will stop at these paths.
|
||||||
|
#[name = "workspace-lsp-roots"]
|
||||||
|
workspace_roots: List<String> = List::default(),
|
||||||
|
/// An array of LSP diagnostic sources assumed unchanged when the
|
||||||
|
/// language server resends the same set of diagnostics. Helix can track
|
||||||
|
/// the position for these diagnostics internally instead. Useful for
|
||||||
|
/// diagnostics that are recomputed on save.
|
||||||
|
persistent_diagnostic_sources: List<String> = List::default(),
|
||||||
|
/// Minimal severity of diagnostic for it to be displayed. (Allowed
|
||||||
|
/// values: `error`, `warning`, `info`, `hint`)
|
||||||
|
diagnostic_severity: Severity = Severity::Hint,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CompletionConfig {
|
||||||
|
/// Automatic auto-completion, automatically pop up without user trigger.
|
||||||
|
#[read = copy]
|
||||||
|
auto_completion: bool = true,
|
||||||
|
/// Whether to apply completion item instantly when selected
|
||||||
|
#[read = copy]
|
||||||
|
preview_completion_insert: bool = true,
|
||||||
|
/// Whether to apply completion item instantly when selected
|
||||||
|
#[read = copy]
|
||||||
|
completion_replace: bool = false,
|
||||||
|
/// Whether to apply completion item instantly when selected
|
||||||
|
#[read = copy]
|
||||||
|
completion_trigger_len: u8 = 2,
|
||||||
|
}
|
||||||
|
}
|
291
helix-config/src/definition/ui.rs
Normal file
291
helix-config/src/definition/ui.rs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum StatusLineElement {
|
||||||
|
/// The editor mode (Normal, Insert, Visual/Selection)
|
||||||
|
Mode,
|
||||||
|
/// The LSP activity spinner
|
||||||
|
Spinner,
|
||||||
|
/// The file basename (the leaf of the open file's path)
|
||||||
|
FileBaseName,
|
||||||
|
/// The relative file path
|
||||||
|
FileName,
|
||||||
|
// The file modification indicator
|
||||||
|
FileModificationIndicator,
|
||||||
|
/// An indicator that shows `"[readonly]"` when a file cannot be written
|
||||||
|
ReadOnlyIndicator,
|
||||||
|
/// The file encoding
|
||||||
|
FileEncoding,
|
||||||
|
/// The file line endings (CRLF or LF)
|
||||||
|
FileLineEnding,
|
||||||
|
/// The file type (language ID or "text")
|
||||||
|
FileType,
|
||||||
|
/// A summary of the number of errors and warnings
|
||||||
|
Diagnostics,
|
||||||
|
/// A summary of the number of errors and warnings on file and workspace
|
||||||
|
WorkspaceDiagnostics,
|
||||||
|
/// The number of selections (cursors)
|
||||||
|
Selections,
|
||||||
|
/// The number of characters currently in primary selection
|
||||||
|
PrimarySelectionLength,
|
||||||
|
/// The cursor position
|
||||||
|
Position,
|
||||||
|
/// The separator string
|
||||||
|
Separator,
|
||||||
|
/// The cursor position as a percent of the total file
|
||||||
|
PositionPercentage,
|
||||||
|
/// The total line numbers of the current file
|
||||||
|
TotalLineNumbers,
|
||||||
|
/// A single space
|
||||||
|
Spacer,
|
||||||
|
/// Current version control information
|
||||||
|
VersionControl,
|
||||||
|
/// Indicator for selected register
|
||||||
|
Register,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(StatusLineElement);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
/// UNSTABLE
|
||||||
|
pub enum CursorKind {
|
||||||
|
/// █
|
||||||
|
Block,
|
||||||
|
/// |
|
||||||
|
Bar,
|
||||||
|
/// _
|
||||||
|
Underline,
|
||||||
|
/// Hidden cursor, can set cursor position with this to let IME have correct cursor position.
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CursorKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(CursorKind);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum WhitespaceRenderValue {
|
||||||
|
None,
|
||||||
|
// TODO
|
||||||
|
// Selection,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(WhitespaceRenderValue);
|
||||||
|
|
||||||
|
/// bufferline render modes
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum BufferLine {
|
||||||
|
/// Don't render bufferline
|
||||||
|
Never,
|
||||||
|
/// Always render
|
||||||
|
Always,
|
||||||
|
/// Only if multiple buffers are open
|
||||||
|
Multiple,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(BufferLine);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum PopupBorderConfig {
|
||||||
|
None,
|
||||||
|
All,
|
||||||
|
Popup,
|
||||||
|
Menu,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(PopupBorderConfig);
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct UiConfig {
|
||||||
|
/// Whether to display info boxes
|
||||||
|
#[read = copy]
|
||||||
|
auto_info: bool = true,
|
||||||
|
/// Renders a line at the top of the editor displaying open buffers.
|
||||||
|
/// Can be `always`, `never` or `multiple` (only shown if more than one
|
||||||
|
/// buffer is in use)
|
||||||
|
#[read = copy]
|
||||||
|
bufferline: BufferLine = BufferLine::Never,
|
||||||
|
/// Highlight all lines with a cursor
|
||||||
|
#[read = copy]
|
||||||
|
cursorline: bool = false,
|
||||||
|
/// Highlight all columns with a cursor
|
||||||
|
#[read = copy]
|
||||||
|
cursorcolumn: bool = false,
|
||||||
|
/// List of column positions at which to display the rulers.
|
||||||
|
#[read = deref]
|
||||||
|
rulers: List<u16> = List::default(),
|
||||||
|
/// Whether to color the mode indicator with different colors depending on the mode itself
|
||||||
|
#[read = copy]
|
||||||
|
popup_border: bool = false,
|
||||||
|
/// Whether to color the mode indicator with different colors depending on the mode itself
|
||||||
|
#[read = copy]
|
||||||
|
color_modes: bool = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WhiteSpaceRenderConfig {
|
||||||
|
#[name = "whitespace.characters.space"]
|
||||||
|
#[read = copy]
|
||||||
|
space_char: char = '·', // U+00B7
|
||||||
|
#[name = "whitespace.characters.nbsp"]
|
||||||
|
#[read = copy]
|
||||||
|
nbsp_char: char = '⍽', // U+237D
|
||||||
|
#[name = "whitespace.characters.tab"]
|
||||||
|
#[read = copy]
|
||||||
|
tab_char: char = '→', // U+2192
|
||||||
|
#[name = "whitespace.characters.tabpad"]
|
||||||
|
#[read = copy]
|
||||||
|
tabpad_char: char = '⏎', // U+23CE
|
||||||
|
#[name = "whitespace.characters.newline"]
|
||||||
|
#[read = copy]
|
||||||
|
newline_char: char = ' ',
|
||||||
|
#[name = "whitespace.render.default"]
|
||||||
|
#[read = copy]
|
||||||
|
render: WhitespaceRenderValue = WhitespaceRenderValue::None,
|
||||||
|
#[name = "whitespace.render.space"]
|
||||||
|
#[read = copy]
|
||||||
|
render_space: Option<WhitespaceRenderValue> = None,
|
||||||
|
#[name = "whitespace.render.nbsp"]
|
||||||
|
#[read = copy]
|
||||||
|
render_nbsp: Option<WhitespaceRenderValue> = None,
|
||||||
|
#[name = "whitespace.render.tab"]
|
||||||
|
#[read = copy]
|
||||||
|
render_tab: Option<WhitespaceRenderValue> = None,
|
||||||
|
#[name = "whitespace.render.newline"]
|
||||||
|
#[read = copy]
|
||||||
|
render_newline: Option<WhitespaceRenderValue> = None,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TerminfoConfig {
|
||||||
|
/// Set to `true` to override automatic detection of terminal truecolor
|
||||||
|
/// support in the event of a false negative
|
||||||
|
#[name = "true-color"]
|
||||||
|
#[read = copy]
|
||||||
|
force_true_color: bool = false,
|
||||||
|
/// Set to `true` to override automatic detection of terminal undercurl
|
||||||
|
/// support in the event of a false negative
|
||||||
|
#[name = "undercurl"]
|
||||||
|
#[read = copy]
|
||||||
|
force_undercurl: bool = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IndentGuidesConfig {
|
||||||
|
/// Whether to render indent guides
|
||||||
|
#[read = copy]
|
||||||
|
render: bool = false,
|
||||||
|
/// Character to use for rendering indent guides
|
||||||
|
#[read = copy]
|
||||||
|
character: char = '│',
|
||||||
|
/// Number of indent levels to skip
|
||||||
|
#[read = copy]
|
||||||
|
skip_levels: u8 = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CursorShapeConfig {
|
||||||
|
/// Cursor shape in normal mode
|
||||||
|
#[name = "cursor-shape.normal"]
|
||||||
|
#[read = copy]
|
||||||
|
normal_mode_cursor: CursorKind = CursorKind::Block,
|
||||||
|
/// Cursor shape in select mode
|
||||||
|
#[name = "cursor-shape.select"]
|
||||||
|
#[read = copy]
|
||||||
|
select_mode_cursor: CursorKind = CursorKind::Block,
|
||||||
|
/// Cursor shape in insert mode
|
||||||
|
#[name = "cursor-shape.insert"]
|
||||||
|
#[read = copy]
|
||||||
|
insert_mode_cursor: CursorKind = CursorKind::Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FilePickerConfig {
|
||||||
|
/// Whether to exclude hidden files from any file pickers.
|
||||||
|
#[name = "file-picker.hidden"]
|
||||||
|
#[read = copy]
|
||||||
|
hidden: bool = true,
|
||||||
|
/// Follow symlinks instead of ignoring them
|
||||||
|
#[name = "file-picker.follow-symlinks"]
|
||||||
|
#[read = copy]
|
||||||
|
follow_symlinks: bool = true,
|
||||||
|
/// Ignore symlinks that point at files already shown in the picker
|
||||||
|
#[name = "file-picker.deduplicate-links"]
|
||||||
|
#[read = copy]
|
||||||
|
deduplicate_links: bool = true,
|
||||||
|
/// Enables reading ignore files from parent directories.
|
||||||
|
#[name = "file-picker.parents"]
|
||||||
|
#[read = copy]
|
||||||
|
parents: bool = true,
|
||||||
|
/// Enables reading `.ignore` files.
|
||||||
|
#[name = "file-picker.ignore"]
|
||||||
|
#[read = copy]
|
||||||
|
ignore: bool = true,
|
||||||
|
/// Enables reading `.gitignore` files.
|
||||||
|
#[name = "file-picker.git-ignore"]
|
||||||
|
#[read = copy]
|
||||||
|
git_ignore: bool = true,
|
||||||
|
/// Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option.
|
||||||
|
#[name = "file-picker.git-global"]
|
||||||
|
#[read = copy]
|
||||||
|
git_global: bool = true,
|
||||||
|
/// Enables reading `.git/info/exclude` files.
|
||||||
|
#[name = "file-picker.git-exclude"]
|
||||||
|
#[read = copy]
|
||||||
|
git_exclude: bool = true,
|
||||||
|
/// Maximum Depth to recurse directories in file picker and global search.
|
||||||
|
#[name = "file-picker.max-depth"]
|
||||||
|
#[read = copy]
|
||||||
|
max_depth: Option<usize> = None,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatusLineConfig{
|
||||||
|
/// A list of elements aligned to the left of the statusline
|
||||||
|
#[name = "statusline.left"]
|
||||||
|
#[read = deref]
|
||||||
|
left: List<StatusLineElement> = &[
|
||||||
|
StatusLineElement::Mode,
|
||||||
|
StatusLineElement::Spinner,
|
||||||
|
StatusLineElement::FileName,
|
||||||
|
StatusLineElement::ReadOnlyIndicator,
|
||||||
|
StatusLineElement::FileModificationIndicator,
|
||||||
|
],
|
||||||
|
/// A list of elements aligned to the middle of the statusline
|
||||||
|
#[name = "statusline.center"]
|
||||||
|
#[read = deref]
|
||||||
|
center: List<StatusLineElement> = List::default(),
|
||||||
|
/// A list of elements aligned to the right of the statusline
|
||||||
|
#[name = "statusline.right"]
|
||||||
|
#[read = deref]
|
||||||
|
right: List<StatusLineElement> = &[
|
||||||
|
StatusLineElement::Diagnostics,
|
||||||
|
StatusLineElement::Selections,
|
||||||
|
StatusLineElement::Register,
|
||||||
|
StatusLineElement::Position,
|
||||||
|
StatusLineElement::FileEncoding,
|
||||||
|
],
|
||||||
|
/// The character used to separate elements in the statusline
|
||||||
|
#[name = "statusline.seperator"]
|
||||||
|
#[read = deref]
|
||||||
|
seperator: String = "│",
|
||||||
|
/// The text shown in the `mode` element for normal mode
|
||||||
|
#[name = "statusline.mode.normal"]
|
||||||
|
#[read = deref]
|
||||||
|
mode_indicator_normal: String = "NOR",
|
||||||
|
/// The text shown in the `mode` element for insert mode
|
||||||
|
#[name = "statusline.mode.insert"]
|
||||||
|
#[read = deref]
|
||||||
|
mode_indicator_insert: String = "INS",
|
||||||
|
/// The text shown in the `mode` element for select mode
|
||||||
|
#[name = "statusline.mode.select"]
|
||||||
|
#[read = deref]
|
||||||
|
mode_indicator_select: String = "SEL",
|
||||||
|
}
|
||||||
|
}
|
10
helix-config/src/env.rs
Normal file
10
helix-config/src/env.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// TOOD: move to stdx
|
||||||
|
|
||||||
|
pub fn binary_exists(binary_name: &str) -> bool {
|
||||||
|
which::which(binary_name).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn env_var_is_set(env_var_name: &str) -> bool {
|
||||||
|
std::env::var_os(env_var_name).is_some()
|
||||||
|
}
|
@ -13,13 +13,15 @@
|
|||||||
use any::ConfigData;
|
use any::ConfigData;
|
||||||
use convert::ty_into_value;
|
use convert::ty_into_value;
|
||||||
pub use convert::IntoTy;
|
pub use convert::IntoTy;
|
||||||
pub use definition::init_config;
|
pub use definition::{init_config, init_language_server_config};
|
||||||
use validator::StaticValidator;
|
use validator::StaticValidator;
|
||||||
pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator};
|
pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator};
|
||||||
pub use value::{from_value, to_value, Value};
|
pub use value::{from_value, to_value, Value};
|
||||||
|
|
||||||
mod any;
|
mod any;
|
||||||
mod convert;
|
mod convert;
|
||||||
|
mod definition;
|
||||||
|
pub mod env;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod validator;
|
mod validator;
|
||||||
mod value;
|
mod value;
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
//! this module provides the functionality to insert the paired closing character.
|
//! this module provides the functionality to insert the paired closing character.
|
||||||
|
|
||||||
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
|
use anyhow::{bail, ensure};
|
||||||
|
use helix_config::options;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
// Heavily based on https://github.com/codemirror/closebrackets/
|
// Heavily based on https://github.com/codemirror/closebrackets/
|
||||||
@ -19,7 +21,7 @@
|
|||||||
/// The type that represents the collection of auto pairs,
|
/// The type that represents the collection of auto pairs,
|
||||||
/// keyed by both opener and closer.
|
/// keyed by both opener and closer.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AutoPairs(HashMap<char, Pair>);
|
pub struct AutoPairs(IndexMap<char, Pair, ahash::RandomState>);
|
||||||
|
|
||||||
/// Represents the config for a particular pairing.
|
/// Represents the config for a particular pairing.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@ -75,15 +77,15 @@ fn from((open, close): (&char, &char)) -> Self {
|
|||||||
|
|
||||||
impl AutoPairs {
|
impl AutoPairs {
|
||||||
/// Make a new AutoPairs set with the given pairs and default conditions.
|
/// Make a new AutoPairs set with the given pairs and default conditions.
|
||||||
pub fn new<'a, V: 'a, A>(pairs: V) -> Self
|
pub fn new<'a, V: 'a, A>(pairs: V) -> anyhow::Result<Self>
|
||||||
where
|
where
|
||||||
V: IntoIterator<Item = A>,
|
V: IntoIterator<Item = anyhow::Result<A>>,
|
||||||
A: Into<Pair>,
|
A: Into<Pair>,
|
||||||
{
|
{
|
||||||
let mut auto_pairs = HashMap::new();
|
let mut auto_pairs = IndexMap::default();
|
||||||
|
|
||||||
for pair in pairs.into_iter() {
|
for pair in pairs.into_iter() {
|
||||||
let auto_pair = pair.into();
|
let auto_pair = pair?.into();
|
||||||
|
|
||||||
auto_pairs.insert(auto_pair.open, auto_pair);
|
auto_pairs.insert(auto_pair.open, auto_pair);
|
||||||
|
|
||||||
@ -92,7 +94,7 @@ pub fn new<'a, V: 'a, A>(pairs: V) -> Self
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self(auto_pairs)
|
Ok(Self(auto_pairs))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, ch: char) -> Option<&Pair> {
|
pub fn get(&self, ch: char) -> Option<&Pair> {
|
||||||
@ -102,7 +104,7 @@ pub fn get(&self, ch: char) -> Option<&Pair> {
|
|||||||
|
|
||||||
impl Default for AutoPairs {
|
impl Default for AutoPairs {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
AutoPairs::new(DEFAULT_PAIRS.iter())
|
AutoPairs::new(DEFAULT_PAIRS.iter().map(Ok)).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,3 +373,43 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
|
|||||||
log::debug!("auto pair transaction: {:#?}", t);
|
log::debug!("auto pair transaction: {:#?}", t);
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct AutopairConfig {
|
||||||
|
/// Mapping of character pairs like `{ '(' = ')', '`' = '`' }` that are
|
||||||
|
/// automatically closed by the editor when typed.
|
||||||
|
auto_pairs: AutoPairs = AutoPairs::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl helix_config::Ty for AutoPairs {
|
||||||
|
fn from_value(val: helix_config::Value) -> anyhow::Result<Self> {
|
||||||
|
let map = match val {
|
||||||
|
helix_config::Value::Map(map) => map,
|
||||||
|
helix_config::Value::Bool(false) => return Ok(Self(IndexMap::default())),
|
||||||
|
_ => bail!("expect 'false' or a map of pairs"),
|
||||||
|
};
|
||||||
|
let pairs = map.into_iter().map(|(open, close)| {
|
||||||
|
let open = helix_config::Value::String(open.into_string());
|
||||||
|
Ok(Pair {
|
||||||
|
open: open.typed()?,
|
||||||
|
close: close.typed()?,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
AutoPairs::new(pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> helix_config::Value {
|
||||||
|
let map = self
|
||||||
|
.0
|
||||||
|
.values()
|
||||||
|
.map(|pair| {
|
||||||
|
(
|
||||||
|
pair.open.to_string().into(),
|
||||||
|
helix_config::Value::String(pair.close.into()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
helix_config::Value::Map(Box::new(map))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,16 +1,36 @@
|
|||||||
use std::{borrow::Cow, collections::HashMap};
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use helix_config::{config_serde_adapter, options, IntegerRangeValidator};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
|
use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chars::{char_is_line_ending, char_is_whitespace},
|
chars::{char_is_line_ending, char_is_whitespace},
|
||||||
find_first_non_whitespace_char,
|
find_first_non_whitespace_char,
|
||||||
graphemes::{grapheme_width, tab_width_at},
|
graphemes::{grapheme_width, tab_width_at},
|
||||||
syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
|
syntax::{LanguageConfiguration, RopeProvider, Syntax},
|
||||||
tree_sitter::Node,
|
tree_sitter::Node,
|
||||||
Position, Rope, RopeGraphemes, RopeSlice,
|
Position, Rope, RopeGraphemes, RopeSlice,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// How the indentation for a newly inserted line should be determined.
|
||||||
|
/// If the selected heuristic is not available (e.g. because the current
|
||||||
|
/// language has no tree-sitter indent queries), a simpler one will be used.
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum IndentationHeuristic {
|
||||||
|
/// Just copy the indentation of the line that the cursor is currently on.
|
||||||
|
Simple,
|
||||||
|
/// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
|
||||||
|
TreeSitter,
|
||||||
|
/// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
|
||||||
|
/// and the line before. Add this to the actual indentation level of the line before.
|
||||||
|
#[default]
|
||||||
|
Hybrid,
|
||||||
|
}
|
||||||
|
config_serde_adapter!(IndentationHeuristic);
|
||||||
|
|
||||||
/// Enum representing indentation style.
|
/// Enum representing indentation style.
|
||||||
///
|
///
|
||||||
/// Only values 1-8 are valid for the `Spaces` variant.
|
/// Only values 1-8 are valid for the `Spaces` variant.
|
||||||
@ -20,6 +40,50 @@ pub enum IndentStyle {
|
|||||||
Spaces(u8),
|
Spaces(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct IndentationConfig {
|
||||||
|
/// The number columns that a tabs are aligned to.
|
||||||
|
#[name = "ident.tab_width"]
|
||||||
|
#[read = copy]
|
||||||
|
tab_width: usize = 4,
|
||||||
|
/// Indentation inserted/removed into the document when indenting/dedenting.
|
||||||
|
/// This can be set to an integer representing N spaces or "tab" for tabs.
|
||||||
|
#[name = "ident.unit"]
|
||||||
|
#[read = copy]
|
||||||
|
indent_style: IndentStyle = IndentStyle::Tabs,
|
||||||
|
/// How the indentation for a newly inserted line is computed:
|
||||||
|
/// `simple` just copies the indentation level from the previous line,
|
||||||
|
/// `tree-sitter` computes the indentation based on the syntax tree and
|
||||||
|
/// `hybrid` combines both approaches.
|
||||||
|
/// If the chosen heuristic is not available, a different one will
|
||||||
|
/// be used as a fallback (the fallback order being `hybrid` ->
|
||||||
|
/// `tree-sitter` -> `simple`).
|
||||||
|
#[read = copy]
|
||||||
|
indent_heuristic: IndentationHeuristic = IndentationHeuristic::Hybrid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl helix_config::Ty for IndentStyle {
|
||||||
|
fn from_value(val: helix_config::Value) -> anyhow::Result<Self> {
|
||||||
|
match val {
|
||||||
|
helix_config::Value::String(s) if s == "t" || s == "tab" => Ok(IndentStyle::Tabs),
|
||||||
|
helix_config::Value::Int(_) => {
|
||||||
|
let spaces = IntegerRangeValidator::new(0, MAX_INDENT)
|
||||||
|
.validate(val)
|
||||||
|
.map_err(|err| anyhow!("invalid number of spaces! {err}"))?;
|
||||||
|
Ok(IndentStyle::Spaces(spaces))
|
||||||
|
}
|
||||||
|
_ => bail!("expected an integer (spaces) or 'tab'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_value(&self) -> helix_config::Value {
|
||||||
|
match *self {
|
||||||
|
IndentStyle::Tabs => helix_config::Value::String("tab".into()),
|
||||||
|
IndentStyle::Spaces(spaces) => helix_config::Value::Int(spaces as _),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 16 spaces
|
// 16 spaces
|
||||||
const INDENTS: &str = " ";
|
const INDENTS: &str = " ";
|
||||||
pub const MAX_INDENT: u8 = 16;
|
pub const MAX_INDENT: u8 = 16;
|
||||||
|
@ -35,6 +35,7 @@ pub mod unicode {
|
|||||||
pub use unicode_width as width;
|
pub use unicode_width as width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use helix_config::OptionRegistry;
|
||||||
pub use helix_loader::find_workspace;
|
pub use helix_loader::find_workspace;
|
||||||
|
|
||||||
pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
|
pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
|
||||||
@ -69,3 +70,9 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
|
|||||||
|
|
||||||
pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
|
pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
|
||||||
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
|
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
|
||||||
|
|
||||||
|
pub fn init_config(registry: &mut OptionRegistry) {
|
||||||
|
line_ending::init_config(registry);
|
||||||
|
auto_pairs::init_config(registry);
|
||||||
|
indent::init_config(registry);
|
||||||
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
use helix_config::{options, Ty};
|
||||||
|
|
||||||
use crate::{Rope, RopeSlice};
|
use crate::{Rope, RopeSlice};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@ -5,6 +8,61 @@
|
|||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
|
pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct LineEndingConfig {
|
||||||
|
/// The line ending to use for new documents. Can be `lf` or `crlf`. If
|
||||||
|
/// helix was compiled with the `unicode-lines` feature then `vt`, `ff`,
|
||||||
|
/// `cr`, `nel`, `ls` or `ps` are also allowed.
|
||||||
|
#[read = copy]
|
||||||
|
default_line_ending: LineEnding = NATIVE_LINE_ENDING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ty for LineEnding {
|
||||||
|
fn from_value(val: helix_config::Value) -> anyhow::Result<Self> {
|
||||||
|
let val: String = val.typed()?;
|
||||||
|
match &*val {
|
||||||
|
"crlf" => Ok(LineEnding::Crlf),
|
||||||
|
"lf" => Ok(LineEnding::LF),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"vt" => Ok(LineEnding::VT),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"ff" => Ok(LineEnding::FF),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"cr" => Ok(LineEnding::CR),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"nel" => Ok(LineEnding::Nel),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"ls" => Ok(LineEnding::LS),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
"ps" => Ok(LineEnding::PS),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
_ => bail!("expecte one of 'lf', 'crlf', 'vt', 'ff', 'cr', 'nel', 'ls' or 'ps'"),
|
||||||
|
#[cfg(not(feature = "unicode-lines"))]
|
||||||
|
_ => bail!("expecte one of 'lf' or 'crlf'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> helix_config::Value {
|
||||||
|
match self {
|
||||||
|
LineEnding::Crlf => "crlf".into(),
|
||||||
|
LineEnding::LF => "lf".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
VT => "vt".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
FF => "ff".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
CR => "cr".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
Nel => "nel".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
LS => "ls".into(),
|
||||||
|
#[cfg(feature = "unicode-lines")]
|
||||||
|
PS => "ps".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents one of the valid Unicode line endings.
|
/// Represents one of the valid Unicode line endings.
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
pub enum LineEnding {
|
pub enum LineEnding {
|
||||||
|
@ -14,6 +14,7 @@ homepage.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
helix-core = { path = "../helix-core" }
|
helix-core = { path = "../helix-core" }
|
||||||
|
helix-config = { path = "../helix-config" }
|
||||||
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
146
helix-dap/src/config.rs
Normal file
146
helix-dap/src/config.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
use helix_config::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct DebugAdapterConfig {
|
||||||
|
#[name = "debugger.name"]
|
||||||
|
name: Option<String> = None,
|
||||||
|
#[name = "debugger.transport"]
|
||||||
|
#[read = copy]
|
||||||
|
transport: Transport = Transport::Stdio,
|
||||||
|
#[name = "debugger.command"]
|
||||||
|
#[read = deref]
|
||||||
|
command: String = "",
|
||||||
|
#[name = "debugger.args"]
|
||||||
|
#[read = deref]
|
||||||
|
args: List<String> = List::default(),
|
||||||
|
#[name = "debugger.port-arg"]
|
||||||
|
#[read = deref]
|
||||||
|
port_arg: String = "",
|
||||||
|
#[name = "debugger.templates"]
|
||||||
|
#[read = deref]
|
||||||
|
templates: List<DebugTemplate> = List::default(),
|
||||||
|
#[name = "debugger.quirks.absolut-path"]
|
||||||
|
#[read = copy]
|
||||||
|
absolut_path: bool = false,
|
||||||
|
#[name = "terminal.command"]
|
||||||
|
terminal_command: Option<String> = get_terminal_provider().map(|term| term.command),
|
||||||
|
#[name = "terminal.args"]
|
||||||
|
#[read = deref]
|
||||||
|
terminal_args: List<String> = get_terminal_provider().map(|term| term.args.into_boxed_slice()).unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum Transport {
|
||||||
|
Stdio,
|
||||||
|
Tcp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ty for Transport {
|
||||||
|
fn from_value(val: Value) -> anyhow::Result<Self> {
|
||||||
|
match &*String::from_value(val)? {
|
||||||
|
"stdio" => Ok(Transport::Stdio),
|
||||||
|
"tcp" => Ok(Transport::Tcp),
|
||||||
|
val => bail!("expected 'stdio' or 'tcp' (got {val:?})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
match self {
|
||||||
|
Transport::Stdio => "stdio".into(),
|
||||||
|
Transport::Tcp => "tcp".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum DebugArgumentValue {
|
||||||
|
String(String),
|
||||||
|
Array(Vec<String>),
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct AdvancedCompletion {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub completion: Option<String>,
|
||||||
|
pub default: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", untagged)]
|
||||||
|
pub enum DebugConfigCompletion {
|
||||||
|
Named(String),
|
||||||
|
Advanced(AdvancedCompletion),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct DebugTemplate {
|
||||||
|
pub name: String,
|
||||||
|
pub request: String,
|
||||||
|
pub completion: Vec<DebugConfigCompletion>,
|
||||||
|
pub args: Map<DebugArgumentValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: integrate this better with the new config system (less nesting)
|
||||||
|
// the best way to do that is probably a rewrite. I think these templates
|
||||||
|
// are probably overkill here. This may be easier to solve by moving the logic
|
||||||
|
// to scheme
|
||||||
|
config_serde_adapter!(DebugTemplate);
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
|
pub struct TerminalConfig {
|
||||||
|
pub command: String,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
||||||
|
use helix_config::env::binary_exists;
|
||||||
|
|
||||||
|
if binary_exists("wt") {
|
||||||
|
return Some(TerminalConfig {
|
||||||
|
command: "wt".into(),
|
||||||
|
args: vec![
|
||||||
|
"new-tab".into(),
|
||||||
|
"--title".into(),
|
||||||
|
"DEBUG".into(),
|
||||||
|
"cmd".into(),
|
||||||
|
"/C".into(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(TerminalConfig {
|
||||||
|
command: "conhost".into(),
|
||||||
|
args: vec!["cmd".into(), "/C".into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(windows, target_os = "wasm32")))]
|
||||||
|
fn get_terminal_provider() -> Option<TerminalConfig> {
|
||||||
|
use helix_config::env::{binary_exists, env_var_is_set};
|
||||||
|
|
||||||
|
if env_var_is_set("TMUX") && binary_exists("tmux") {
|
||||||
|
return Some(TerminalConfig {
|
||||||
|
command: "tmux".into(),
|
||||||
|
args: vec!["split-window".into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if env_var_is_set("WEZTERM_UNIX_SOCKET") && binary_exists("wezterm") {
|
||||||
|
return Some(TerminalConfig {
|
||||||
|
command: "wezterm".into(),
|
||||||
|
args: vec!["cli".into(), "split-pane".into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
mod client;
|
mod client;
|
||||||
|
mod config;
|
||||||
mod transport;
|
mod transport;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ term = ["crossterm"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
helix-core = { path = "../helix-core" }
|
helix-core = { path = "../helix-core" }
|
||||||
|
helix-config = { path = "../helix-config" }
|
||||||
helix-event = { path = "../helix-event" }
|
helix-event = { path = "../helix-event" }
|
||||||
helix-loader = { path = "../helix-loader" }
|
helix-loader = { path = "../helix-loader" }
|
||||||
helix-lsp = { path = "../helix-lsp" }
|
helix-lsp = { path = "../helix-lsp" }
|
||||||
|
@ -1,13 +1,96 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use helix_config::{config_serde_adapter, options, List};
|
||||||
use helix_core::syntax::LanguageServerFeature;
|
use helix_core::syntax::LanguageServerFeature;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::GutterType,
|
|
||||||
graphics::{Style, UnderlineStyle},
|
graphics::{Style, UnderlineStyle},
|
||||||
Document, Editor, Theme, View,
|
Document, Editor, Theme, View,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum LineNumber {
|
||||||
|
/// Show absolute line number
|
||||||
|
#[serde(alias = "abs")]
|
||||||
|
Absolute,
|
||||||
|
/// If focused and in normal/select mode, show relative line number to the primary cursor.
|
||||||
|
/// If unfocused or in insert mode, show absolute line number.
|
||||||
|
#[serde(alias = "rel")]
|
||||||
|
Relative,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_serde_adapter!(LineNumber);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum GutterType {
|
||||||
|
/// Show diagnostics and other features like breakpoints
|
||||||
|
Diagnostics,
|
||||||
|
/// Show line numbers
|
||||||
|
LineNumbers,
|
||||||
|
/// Show one blank space
|
||||||
|
Spacer,
|
||||||
|
/// Highlight local changes
|
||||||
|
Diff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for GutterType {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"diagnostics" => Ok(Self::Diagnostics),
|
||||||
|
"spacer" => Ok(Self::Spacer),
|
||||||
|
"line-numbers" => Ok(Self::LineNumbers),
|
||||||
|
"diff" => Ok(Self::Diff),
|
||||||
|
_ => anyhow::bail!(
|
||||||
|
"expected one of `diagnostics`, `spacer`, `line-numbers` or `diff` (found {s:?})"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl helix_config::Ty for GutterType {
|
||||||
|
fn from_value(val: helix_config::Value) -> anyhow::Result<Self> {
|
||||||
|
let val: String = val.typed()?;
|
||||||
|
val.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> helix_config::Value {
|
||||||
|
match self {
|
||||||
|
GutterType::Diagnostics => "diagnostics".into(),
|
||||||
|
GutterType::LineNumbers => "lineNumbers".into(),
|
||||||
|
GutterType::Spacer => "spacer".into(),
|
||||||
|
GutterType::Diff => "diff".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
struct GutterConfig {
|
||||||
|
/// A list of gutters to display
|
||||||
|
#[name = "gutters.layout"]
|
||||||
|
layout: List<GutterType> = &[
|
||||||
|
GutterType::Diagnostics,
|
||||||
|
GutterType::Spacer,
|
||||||
|
GutterType::LineNumbers,
|
||||||
|
GutterType::Spacer,
|
||||||
|
GutterType::Diff,
|
||||||
|
],
|
||||||
|
/// The minimum number of characters the line number gutter should take up.
|
||||||
|
#[name = "gutters.line-numbers.min-width"]
|
||||||
|
line_number_min_width: usize = 3,
|
||||||
|
/// Line number display: `absolute` simply shows each line's number,
|
||||||
|
/// while `relative` shows the distance from the current line. When
|
||||||
|
/// unfocused or in insert mode, `relative` will still show absolute
|
||||||
|
/// line numbers
|
||||||
|
#[name = "line-number"]
|
||||||
|
line_number_mode: LineNumber = LineNumber::Absolute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn count_digits(n: usize) -> usize {
|
fn count_digits(n: usize) -> usize {
|
||||||
(usize::checked_ilog10(n).unwrap_or(0) + 1) as usize
|
(usize::checked_ilog10(n).unwrap_or(0) + 1) as usize
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user