Baseline project
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
use super::{ReplCommand, REPL_COMMANDS};
|
||||
|
||||
use crate::{config::GlobalConfig, utils::fuzzy_filter};
|
||||
|
||||
use reedline::{Completer, Span, Suggestion};
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl Completer for ReplCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut suggestions = vec![];
|
||||
let line = &line[0..pos];
|
||||
let mut parts = split_line(line);
|
||||
if parts.is_empty() {
|
||||
return suggestions;
|
||||
}
|
||||
if parts[0].0 == r#":::"# {
|
||||
parts.remove(0);
|
||||
}
|
||||
|
||||
let parts_len = parts.len();
|
||||
if parts_len == 0 {
|
||||
return suggestions;
|
||||
}
|
||||
let (cmd, cmd_start) = parts[0];
|
||||
|
||||
if !cmd.starts_with('.') {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
let state = self.config.read().state();
|
||||
|
||||
let command_filter = parts
|
||||
.iter()
|
||||
.take(2)
|
||||
.map(|(v, _)| *v)
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ");
|
||||
let commands: Vec<_> = self
|
||||
.commands
|
||||
.iter()
|
||||
.filter(|cmd| {
|
||||
cmd.is_valid(state)
|
||||
&& (command_filter.len() == 1 || cmd.name.starts_with(&command_filter[..2]))
|
||||
})
|
||||
.collect();
|
||||
let commands = fuzzy_filter(commands, |v| v.name, &command_filter);
|
||||
|
||||
if parts_len > 1 {
|
||||
let span = Span::new(parts[parts_len - 1].1, pos);
|
||||
let args_line = &line[parts[1].1..];
|
||||
let args: Vec<&str> = parts.iter().skip(1).map(|(v, _)| *v).collect();
|
||||
suggestions.extend(
|
||||
self.config
|
||||
.read()
|
||||
.repl_complete(cmd, &args, args_line)
|
||||
.iter()
|
||||
.map(|(value, description)| {
|
||||
let description = description.as_deref().unwrap_or_default();
|
||||
create_suggestion(value, description, span)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if suggestions.is_empty() {
|
||||
let span = Span::new(cmd_start, pos);
|
||||
suggestions.extend(commands.iter().map(|cmd| {
|
||||
let name = cmd.name;
|
||||
let description = cmd.description;
|
||||
let has_group = self.groups.get(name).map(|v| *v > 1).unwrap_or_default();
|
||||
let name = if has_group {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{name} ")
|
||||
};
|
||||
create_suggestion(&name, description, span)
|
||||
}))
|
||||
}
|
||||
suggestions
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReplCompleter {
|
||||
config: GlobalConfig,
|
||||
commands: Vec<ReplCommand>,
|
||||
groups: HashMap<&'static str, usize>,
|
||||
}
|
||||
|
||||
impl ReplCompleter {
|
||||
pub fn new(config: &GlobalConfig) -> Self {
|
||||
let mut groups = HashMap::new();
|
||||
|
||||
let commands: Vec<ReplCommand> = REPL_COMMANDS.to_vec();
|
||||
|
||||
for cmd in REPL_COMMANDS.iter() {
|
||||
let name = cmd.name;
|
||||
*groups.entry(name).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
Self {
|
||||
config: config.clone(),
|
||||
commands,
|
||||
groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_suggestion(value: &str, description: &str, span: Span) -> Suggestion {
|
||||
let description = if description.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(description.to_string())
|
||||
};
|
||||
Suggestion {
|
||||
value: value.to_string(),
|
||||
description,
|
||||
style: None,
|
||||
extra: None,
|
||||
span,
|
||||
append_whitespace: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn split_line(line: &str) -> Vec<(&str, usize)> {
|
||||
let mut parts = vec![];
|
||||
let mut part_start = None;
|
||||
for (i, ch) in line.char_indices() {
|
||||
if ch == ' ' {
|
||||
if let Some(s) = part_start {
|
||||
parts.push((&line[s..i], s));
|
||||
part_start = None;
|
||||
}
|
||||
} else if part_start.is_none() {
|
||||
part_start = Some(i)
|
||||
}
|
||||
}
|
||||
if let Some(s) = part_start {
|
||||
parts.push((&line[s..], s));
|
||||
} else {
|
||||
parts.push(("", line.len()))
|
||||
}
|
||||
parts
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_line() {
|
||||
assert_eq!(split_line(".role coder"), vec![(".role", 0), ("coder", 6)],);
|
||||
assert_eq!(
|
||||
split_line(" .role coder"),
|
||||
vec![(".role", 1), ("coder", 9)],
|
||||
);
|
||||
assert_eq!(
|
||||
split_line(".set highlight "),
|
||||
vec![(".set", 0), ("highlight", 5), ("", 15)],
|
||||
);
|
||||
assert_eq!(
|
||||
split_line(".set highlight t"),
|
||||
vec![(".set", 0), ("highlight", 5), ("t", 15)],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use super::REPL_COMMANDS;
|
||||
|
||||
use crate::{config::GlobalConfig, utils::NO_COLOR};
|
||||
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use reedline::{Highlighter, StyledText};
|
||||
|
||||
const DEFAULT_COLOR: Color = Color::Default;
|
||||
const MATCH_COLOR: Color = Color::Green;
|
||||
|
||||
pub struct ReplHighlighter;
|
||||
|
||||
impl ReplHighlighter {
|
||||
pub fn new(_config: &GlobalConfig) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for ReplHighlighter {
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
|
||||
let mut styled_text = StyledText::new();
|
||||
|
||||
if *NO_COLOR {
|
||||
styled_text.push((Style::default(), line.to_string()));
|
||||
} else if REPL_COMMANDS.iter().any(|cmd| line.contains(cmd.name)) {
|
||||
let matches: Vec<&str> = REPL_COMMANDS
|
||||
.iter()
|
||||
.filter(|cmd| line.contains(cmd.name))
|
||||
.map(|cmd| cmd.name)
|
||||
.collect();
|
||||
let longest_match = matches.iter().fold(String::new(), |acc, &item| {
|
||||
if item.len() > acc.len() {
|
||||
item.to_string()
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
});
|
||||
let buffer_split: Vec<&str> = line.splitn(2, &longest_match).collect();
|
||||
|
||||
styled_text.push((Style::new().fg(DEFAULT_COLOR), buffer_split[0].to_string()));
|
||||
styled_text.push((Style::new().fg(MATCH_COLOR), longest_match));
|
||||
styled_text.push((Style::new().fg(DEFAULT_COLOR), buffer_split[1].to_string()));
|
||||
} else {
|
||||
styled_text.push((Style::new().fg(DEFAULT_COLOR), line.to_string()));
|
||||
}
|
||||
|
||||
styled_text
|
||||
}
|
||||
}
|
||||
+1014
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
use crate::config::GlobalConfig;
|
||||
|
||||
use reedline::{Prompt, PromptHistorySearch, PromptHistorySearchStatus};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReplPrompt {
|
||||
config: GlobalConfig,
|
||||
}
|
||||
|
||||
impl ReplPrompt {
|
||||
pub fn new(config: &GlobalConfig) -> Self {
|
||||
Self {
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for ReplPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<'_, str> {
|
||||
Cow::Owned(self.config.read().render_prompt_left())
|
||||
}
|
||||
|
||||
fn render_prompt_right(&self) -> Cow<'_, str> {
|
||||
Cow::Owned(self.config.read().render_prompt_right())
|
||||
}
|
||||
|
||||
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<'_, str> {
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> {
|
||||
Cow::Borrowed("... ")
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
history_search: PromptHistorySearch,
|
||||
) -> Cow<'_, str> {
|
||||
let prefix = match history_search.status {
|
||||
PromptHistorySearchStatus::Passing => "",
|
||||
PromptHistorySearchStatus::Failing => "failing ",
|
||||
};
|
||||
// NOTE: magic strings, given there is logic on how these compose I am not sure if it
|
||||
// is worth extracting in to static constant
|
||||
Cow::Owned(format!(
|
||||
"({}reverse-search: {}) ",
|
||||
prefix, history_search.term
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user