Skip to content

Commit

Permalink
Introduce colorizer plugin (#1015)
Browse files Browse the repository at this point in the history
* Initial colorizer plugin

* Implement colorizer/toggle

* Support rgb(), rgba(), hsl() and hsla()

* Fix s:types

* Docs

* fixes

* Nits
  • Loading branch information
liuchengxu authored Nov 4, 2023
1 parent 985648b commit c8e024a
Show file tree
Hide file tree
Showing 10 changed files with 784 additions and 363 deletions.
577 changes: 265 additions & 312 deletions Cargo.lock

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions autoload/clap/plugin/colorizer.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
" Author: liuchengxu <xuliuchengxlc@gmail.com>

scriptencoding utf-8

let s:save_cpo = &cpoptions
set cpoptions&vim

if has('nvim')
let s:colorizer_ns_id = nvim_create_namespace('clap_colorizer')
else
let s:types = []
endif

function! s:create_new_group(bufnr, highlight_group) abort
execute printf(
\ 'hi %s guibg=%s ctermbg=%d',
\ a:highlight_group.name,
\ a:highlight_group.guibg,
\ a:highlight_group.ctermbg,
\ )

if !has('nvim') && index(s:types, a:highlight_group.name) == -1
call add(s:types, a:highlight_group.name)
call prop_type_add(a:highlight_group.name, {'highlight': a:highlight_group.name})
endif
endfunction

" lnum and col is 0-based.
function! s:add_highlight(bufnr, line_number, color_info) abort
if !hlexists(a:color_info.highlight_group.name)
call s:create_new_group(a:bufnr, a:color_info.highlight_group)
endif

if has('nvim')
call nvim_buf_add_highlight(a:bufnr, s:colorizer_ns_id,
\ a:color_info.highlight_group.name,
\ a:line_number,
\ a:color_info.col,
\ a:color_info.col + a:color_info.length,
\ )
else
call prop_add(a:line_number + 1, a:color_info.col + 1, {
\ 'type': a:color_info.highlight_group.name,
\ 'length': a:color_info.length,
\ 'bufnr': a:bufnr,
\ })
endif
endfunction

function! clap#plugin#colorizer#add_highlights(bufnr, highlights) abort
for [line_number, color_infos] in items(a:highlights)
call map(color_infos, 's:add_highlight(a:bufnr, str2nr(line_number), v:val)')
endfor
endfunction

function! clap#plugin#colorizer#clear_highlights(bufnr) abort
if has('nvim')
call nvim_buf_clear_namespace(a:bufnr, s:colorizer_ns_id, 0, -1)
else
call prop_remove({ 'types': s:types, 'all': v:true, 'bufnr': a:bufnr } )
endif
endfunction

let &cpoptions = s:save_cpo
unlet s:save_cpo
3 changes: 2 additions & 1 deletion crates/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ chrono = { version = "0.4", features = ["serde"] }
chrono-humanize = "0.2.3"
clap = { version = "4.2", features = ["derive"] }
colorsys = "0.6.7"
colors-transform = "0.2.11"
directories = "4.0"
futures = "0.3"
fuzzy-matcher = "0.3"
grep-matcher = "0.1"
grep-regex = "0.1"
grep-regex = "0.1.11"
grep-searcher = "0.1"
ignore = "0.4"
indicatif = "0.16"
Expand Down
20 changes: 9 additions & 11 deletions crates/linter/src/linters/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,14 @@ pub async fn run_gopls(source_file: &Path, workspace_root: &Path) -> std::io::Re

for line in stdout.split('\n') {
if !line.is_empty() {
for (_, [_path, line, column_start, column_end, message]) in
RE.captures_iter(line).map(|c| c.extract())
{
let Ok(line) = line.parse::<usize>() else {
continue;
};
let Ok(column_start) = column_start.parse::<usize>() else {
continue;
};
let Ok(column_end) = column_end.parse::<usize>() else {
for caps in RE.captures_iter(line) {
// [path, line, column_start, column_end, message]
let (Some(line), Some(column_start), Some(column_end), Some(message)) = (
caps.get(2).and_then(|m| m.as_str().parse::<usize>().ok()),
caps.get(3).and_then(|m| m.as_str().parse::<usize>().ok()),
caps.get(4).and_then(|m| m.as_str().parse::<usize>().ok()),
caps.get(5).map(|m| m.as_str().to_string()),
) else {
continue;
};
diagnostics.push(Diagnostic {
Expand All @@ -49,7 +47,7 @@ pub async fn run_gopls(source_file: &Path, workspace_root: &Path) -> std::io::Re
}],
code: Code::default(),
severity: Severity::Error,
message: message.to_string(),
message,
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/maple_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bytecount = { workspace = true }
chrono = { workspace = true }
chrono-humanize = { workspace = true }
clap = { workspace = true }
colors-transform = { workspace = true }
futures = { workspace = true }
# ripgrep for global search
grep-searcher = { workspace = true }
Expand All @@ -28,6 +29,7 @@ parking_lot = { workspace = true }
percent-encoding = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
rgb2ansi256 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
subprocess = { workspace = true }
Expand Down
8 changes: 8 additions & 0 deletions crates/maple_core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ impl Default for GitPluginConfig {
}
}

#[derive(Serialize, Deserialize, Debug, Default, Eq, PartialEq)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct ColorizerPluginConfig {
/// Whether to enable this plugin.
pub enable: bool,
}

#[derive(Serialize, Deserialize, Debug, Default, Eq, PartialEq)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct LinterPluginConfig {
Expand All @@ -160,6 +167,7 @@ pub struct LinterPluginConfig {
#[derive(Serialize, Deserialize, Debug, Default, Eq, PartialEq)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct PluginConfig {
pub colorizer: ColorizerPluginConfig,
pub cursorword: CursorWordConfig,
pub ctags: CtagsPluginConfig,
pub git: GitPluginConfig,
Expand Down
104 changes: 65 additions & 39 deletions crates/maple_core/src/stdio_server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ mod vim;

pub use self::input::InputHistory;
use self::input::{ActionEvent, Event, ProviderEvent};
use self::plugin::{
ActionType, ClapPlugin, CtagsPlugin, CursorWordPlugin, GitPlugin, LinterPlugin, MarkdownPlugin,
PluginId, SyntaxHighlighterPlugin, SystemPlugin,
};
use self::plugin::PluginId;
use self::provider::{create_provider, Context};
use self::service::ServiceManager;
use self::vim::initialize_filetype_map;
Expand All @@ -27,8 +24,8 @@ use std::time::Duration;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::time::Instant;

// Do the initialization on startup.
async fn initialize(
// Do the initialization on the Vim end on startup.
async fn initialize_client(
vim: Vim,
actions: Vec<&str>,
config_err: Option<toml::de::Error>,
Expand Down Expand Up @@ -66,34 +63,34 @@ async fn initialize(
Ok(())
}

/// Starts and keep running the server on top of stdio.
pub async fn start(config_err: Option<toml::de::Error>) {
// TODO: setup test framework using vim_message_sender.
let (vim_message_sender, vim_message_receiver) = tokio::sync::mpsc::unbounded_channel();

let rpc_client = Arc::new(RpcClient::new(
BufReader::new(std::io::stdin()),
BufWriter::new(std::io::stdout()),
vim_message_sender.clone(),
));
struct InitializedService {
callable_actions: Vec<&'static str>,
plugin_actions: HashMap<PluginId, Vec<String>>,
service_manager: ServiceManager,
}

let vim = Vim::new(rpc_client);
/// Create a new service, with plugins registered from the config file.
fn initialize_service(vim: Vim) -> InitializedService {
use self::plugin::{
ActionType, ClapPlugin, ColorizerPlugin, CtagsPlugin, CursorWordPlugin, GitPlugin,
LinterPlugin, MarkdownPlugin, SyntaxHighlighterPlugin, SystemPlugin,
};

let mut callable_action_methods = Vec::new();
let mut all_actions = HashMap::new();
let mut callable_actions = Vec::new();
let mut plugin_actions = HashMap::new();

let mut service_manager = ServiceManager::default();

let mut register_plugin = |plugin: Box<dyn ClapPlugin>, debounce: Option<Duration>| {
callable_action_methods.extend(
callable_actions.extend(
plugin
.actions(ActionType::Callable)
.iter()
.map(|a| a.method),
);

let (plugin_id, actions) = service_manager.register_plugin(plugin, debounce);
all_actions.insert(plugin_id, actions);
plugin_actions.insert(plugin_id, actions);
};

register_plugin(Box::new(SystemPlugin::new(vim.clone())), None);
Expand All @@ -102,6 +99,13 @@ pub async fn start(config_err: Option<toml::de::Error>) {

let plugin_config = &crate::config::config().plugin;

if plugin_config.colorizer.enable {
register_plugin(
Box::new(ColorizerPlugin::new(vim.clone())),
Some(Duration::from_millis(100)),
);
}

if plugin_config.linter.enable {
register_plugin(
Box::new(LinterPlugin::new(vim.clone())),
Expand All @@ -118,37 +122,59 @@ pub async fn start(config_err: Option<toml::de::Error>) {
}

if plugin_config.cursorword.enable {
register_plugin(Box::new(CursorWordPlugin::new(vim.clone())), None);
register_plugin(Box::new(CursorWordPlugin::new(vim)), None);
}

tokio::spawn({
let vim = vim.clone();
async move {
if let Err(e) = initialize(vim, callable_action_methods, config_err).await {
tracing::error!(error = ?e, "Failed to initialize Client")
}
}
});
InitializedService {
callable_actions,
plugin_actions,
service_manager,
}
}

/// Starts and keep running the server on top of stdio.
pub async fn start(config_err: Option<toml::de::Error>) {
// TODO: setup test framework using vim_message_sender.
let (vim_message_sender, vim_message_receiver) = tokio::sync::mpsc::unbounded_channel();

Client::new(vim, service_manager, all_actions)
let rpc_client = Arc::new(RpcClient::new(
BufReader::new(std::io::stdin()),
BufWriter::new(std::io::stdout()),
vim_message_sender.clone(),
));

let vim = Vim::new(rpc_client);

Backend::new(vim, config_err)
.run(vim_message_receiver)
.await;
}

#[derive(Clone)]
struct Client {
struct Backend {
vim: Vim,
plugin_actions: Arc<Mutex<HashMap<PluginId, Vec<String>>>>,
service_manager: Arc<Mutex<ServiceManager>>,
}

impl Client {
/// Creates a new instnace of [`Client`].
fn new(
vim: Vim,
service_manager: ServiceManager,
plugin_actions: HashMap<PluginId, Vec<String>>,
) -> Self {
impl Backend {
/// Creates a new instance of [`Backend`].
fn new(vim: Vim, config_err: Option<toml::de::Error>) -> Self {
let InitializedService {
callable_actions,
plugin_actions,
service_manager,
} = initialize_service(vim.clone());

tokio::spawn({
let vim = vim.clone();
async move {
if let Err(e) = initialize_client(vim, callable_actions, config_err).await {
tracing::error!(error = ?e, "Failed to initialize Client")
}
}
});

Self {
vim,
plugin_actions: Arc::new(Mutex::new(plugin_actions)),
Expand Down
Loading

0 comments on commit c8e024a

Please sign in to comment.