mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
Command Sequences, Blocking CLI Commands, New Plugin APIs (#4546)
* fix deadlocks * initial logging to plugin implementation * only log actions, include keys as Action::Write * send actions to plugins as a new Event::UserAction * add Action::Write metadata to plugin API * run_action plugin command and permission * block-until-exit/success/failure in the cli * fix tests * add NewBlockingPane to the plugin API * run actions in a different thread so as not to block plugin execution * add custom context to run_action flow * fixes for floating pane z_index sorting and unknown protobuf events not crashing plugins * always render the frame of pinned panes * macros module * add macros to config * KDL macro parsing * thread macros to the plugin thread * get/update/delete macros from plugins * add macros updated event * add ability to rename macros * fix deadlock * fix z_indices with pinned unselectable panes bug * allow plugins to opt-in to show_cursor * change show_cursor(bool) to show_cursor(Option<coordinates>) * test * upgrade strum to allow proper enum iteration and case insensitive fromstr implementation for Actions * better cwd tracking for exited command panes * add initial panes to NewTab Action (no-op for now) * add initial panes to cli newtab action * make initial panes work * add close_on_exit and start_suspended to the cli * properly convert newtab action (potentially breaking for plugins) * allow first pane in tab to block * make distinction between opening panes with the last active client and the current pane in the cli * improve some apis * fix fixed size closing pane bug * fix plugin cursor coordinates * fix issues with launch-or-focus-plugin having to do with in-place and suppressed panes * fix: set affected pane in replace pane * allow setting a pane in place when it is suppressed and being focused (tests failing) * fix tests * some fixes for unsuppression of panes * some fixes for completion_tx in new tab action * allow replace/suppressed rather than replace/close only * fixes for inserting suppressed panes without conflicts * pane focus history and explicit unsuppress plugin api * fix: prefer explicitly provided pane id in placement in actions that provide it * fix floating pane coordinate change and z_index * fix plugin cache dir to persist between sessions * fix some stack focusing issues when adding panes without a client id * make sure pinned panes are always above non-pinned floating panes * allow plugins to copy to clipboard * initial text styling additions implementations - buggy * fix dimming issues * remove extras, stay with just dim and unbold * unbold and dim as indices * allow ui components to render success/error colors * allow plugins to send sigint/sigkill to processes, as well as receive their pid * ui component fixes and send originating plugin context with run_action * originating plugin for new tab too * make plugn show pane command also expand stacked panes * add sequence plugin * remove macros * remove snapshots * add shortcut for sequence plugin * fix plugin tests * plugin tests for new apis * update snapshots for sequence shortcut * cleanups and formatting * Event::CwdChanged * moar cleanups * remove some warnings * remove mroe warnings * remove dead code * remove more dead code * fix minor bugs in sequence plugin * update sequence help and format codebase * fix conditional blocking cli commands * fix initial-command new-tab cli flag * add client_ids to CwdChanged Event * last fixes * add pr
This commit is contained in:
+122
-18
@@ -10,8 +10,8 @@ use zellij_utils::input::actions::Action;
|
||||
pub use zellij_utils::plugin_api;
|
||||
use zellij_utils::plugin_api::event::ProtobufPaneScrollbackResponse;
|
||||
use zellij_utils::plugin_api::plugin_command::{
|
||||
CreateTokenResponse, ListTokensResponse, ProtobufPluginCommand, RenameWebTokenResponse,
|
||||
RevokeAllWebTokensResponse, RevokeTokenResponse,
|
||||
CreateTokenResponse, ListTokensResponse, ProtobufGetPanePidResponse, ProtobufPluginCommand,
|
||||
RenameWebTokenResponse, RevokeAllWebTokensResponse, RevokeTokenResponse,
|
||||
};
|
||||
use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion};
|
||||
|
||||
@@ -48,6 +48,17 @@ pub fn set_selectable(selectable: bool) {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Shows the cursor at specific coordinates or hides it
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cursor_position` - None to hide cursor, Some((x, y)) to show at coordinates
|
||||
pub fn show_cursor(cursor_position: Option<(usize, usize)>) {
|
||||
let plugin_command = PluginCommand::ShowCursor(cursor_position);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
pub fn request_permission(permissions: &[PermissionType]) {
|
||||
let plugin_command = PluginCommand::RequestPluginPermissions(permissions.into());
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
@@ -395,8 +406,9 @@ pub fn show_self(should_float_if_hidden: bool) {
|
||||
}
|
||||
|
||||
/// Show the pane (unsuppress it if it is suppressed) with the specified [PaneId], focus it and switch to its tab
|
||||
pub fn show_pane_with_id(pane_id: PaneId, should_float_if_hidden: bool) {
|
||||
let plugin_command = PluginCommand::ShowPaneWithId(pane_id, should_float_if_hidden);
|
||||
pub fn show_pane_with_id(pane_id: PaneId, should_float_if_hidden: bool, should_focus_pane: bool) {
|
||||
let plugin_command =
|
||||
PluginCommand::ShowPaneWithId(pane_id, should_float_if_hidden, should_focus_pane);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
@@ -561,6 +573,17 @@ pub fn write_chars(chars: &str) {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Copy arbitrary text to the user's clipboard
|
||||
///
|
||||
/// Respects the user's configured clipboard destination (system clipboard or primary selection).
|
||||
/// Requires the WriteToClipboard permission.
|
||||
pub fn copy_to_clipboard(text: impl Into<String>) {
|
||||
let plugin_command = PluginCommand::CopyToClipboard(text.into());
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Focused the previously focused tab (regardless of the tab position)
|
||||
pub fn toggle_tab() {
|
||||
let plugin_command = PluginCommand::ToggleTab;
|
||||
@@ -774,16 +797,32 @@ pub fn close_plugin_pane(plugin_pane_id: u32) {
|
||||
}
|
||||
|
||||
/// Changes the focus to the terminal pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled).
|
||||
pub fn focus_terminal_pane(terminal_pane_id: u32, should_float_if_hidden: bool) {
|
||||
let plugin_command = PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden);
|
||||
pub fn focus_terminal_pane(
|
||||
terminal_pane_id: u32,
|
||||
should_float_if_hidden: bool,
|
||||
should_be_in_place_if_hidden: bool,
|
||||
) {
|
||||
let plugin_command = PluginCommand::FocusTerminalPane(
|
||||
terminal_pane_id,
|
||||
should_float_if_hidden,
|
||||
should_be_in_place_if_hidden,
|
||||
);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Changes the focus to the plugin pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled).
|
||||
pub fn focus_plugin_pane(plugin_pane_id: u32, should_float_if_hidden: bool) {
|
||||
let plugin_command = PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden);
|
||||
pub fn focus_plugin_pane(
|
||||
plugin_pane_id: u32,
|
||||
should_float_if_hidden: bool,
|
||||
should_be_in_place_if_hidden: bool,
|
||||
) {
|
||||
let plugin_command = PluginCommand::FocusPluginPane(
|
||||
plugin_pane_id,
|
||||
should_float_if_hidden,
|
||||
should_be_in_place_if_hidden,
|
||||
);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
@@ -1023,14 +1062,22 @@ pub fn resize_pane_with_id(resize_strategy: ResizeStrategy, pane_id: PaneId) {
|
||||
}
|
||||
|
||||
/// Changes the focus to the pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled).
|
||||
pub fn focus_pane_with_id(pane_id: PaneId, should_float_if_hidden: bool) {
|
||||
pub fn focus_pane_with_id(
|
||||
pane_id: PaneId,
|
||||
should_float_if_hidden: bool,
|
||||
should_be_in_place_if_hidden: bool,
|
||||
) {
|
||||
let plugin_command = match pane_id {
|
||||
PaneId::Terminal(terminal_pane_id) => {
|
||||
PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden)
|
||||
},
|
||||
PaneId::Plugin(plugin_pane_id) => {
|
||||
PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden)
|
||||
},
|
||||
PaneId::Terminal(terminal_pane_id) => PluginCommand::FocusTerminalPane(
|
||||
terminal_pane_id,
|
||||
should_float_if_hidden,
|
||||
should_be_in_place_if_hidden,
|
||||
),
|
||||
PaneId::Plugin(plugin_pane_id) => PluginCommand::FocusPluginPane(
|
||||
plugin_pane_id,
|
||||
should_float_if_hidden,
|
||||
should_be_in_place_if_hidden,
|
||||
),
|
||||
};
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
@@ -1102,6 +1149,48 @@ pub fn write_chars_to_pane_id(chars: &str, pane_id: PaneId) {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Send SIGINT to the process running inside a terminal pane identified by this PaneId
|
||||
pub fn send_sigint_to_pane_id(pane_id: PaneId) {
|
||||
let plugin_command = PluginCommand::SendSigintToPaneId(pane_id);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Send SIGKILL to the process running inside a terminal pane identified by this PaneId
|
||||
pub fn send_sigkill_to_pane_id(pane_id: PaneId) {
|
||||
let plugin_command = PluginCommand::SendSigkillToPaneId(pane_id);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Get the PID of the process running inside a terminal pane
|
||||
pub fn get_pane_pid(pane_id: PaneId) -> Result<i32, String> {
|
||||
let plugin_command = PluginCommand::GetPanePid { pane_id };
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
|
||||
// Read response from stdin
|
||||
let response_bytes =
|
||||
bytes_from_stdin().map_err(|e| format!("Failed to read response from stdin: {:?}", e))?;
|
||||
|
||||
// Decode protobuf response
|
||||
let protobuf_response = ProtobufGetPanePidResponse::decode(response_bytes.as_slice())
|
||||
.map_err(|e| format!("Failed to decode protobuf response: {}", e))?;
|
||||
|
||||
// Convert to Rust type
|
||||
let response = GetPanePidResponse::try_from(protobuf_response)
|
||||
.map_err(|e| format!("Failed to convert protobuf response: {}", e))?;
|
||||
|
||||
// Convert Result enum to actual Result type
|
||||
match response {
|
||||
GetPanePidResponse::Ok(pid) => Ok(pid),
|
||||
GetPanePidResponse::Err(error_msg) => Err(error_msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch the position of the pane with this id with a different pane
|
||||
pub fn move_pane_with_pane_id(pane_id: PaneId) {
|
||||
let plugin_command = PluginCommand::MovePaneWithPaneId(pane_id);
|
||||
@@ -1504,9 +1593,16 @@ pub fn clear_key_presses_intercepts() {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
pub fn replace_pane_with_existing_pane(pane_id_to_replace: PaneId, existing_pane_id: PaneId) {
|
||||
let plugin_command =
|
||||
PluginCommand::ReplacePaneWithExistingPane(pane_id_to_replace, existing_pane_id);
|
||||
pub fn replace_pane_with_existing_pane(
|
||||
pane_id_to_replace: PaneId,
|
||||
existing_pane_id: PaneId,
|
||||
suppress_replaced_pane: bool,
|
||||
) {
|
||||
let plugin_command = PluginCommand::ReplacePaneWithExistingPane(
|
||||
pane_id_to_replace,
|
||||
existing_pane_id,
|
||||
suppress_replaced_pane,
|
||||
);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
@@ -1580,6 +1676,14 @@ pub fn post_message_to_plugin(plugin_message: PluginMessage) {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
pub fn run_action(action: Action, context: BTreeMap<String, String>) {
|
||||
// TODO: also accept reference
|
||||
let plugin_command = PluginCommand::RunAction(action, context);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
#[link(wasm_import_module = "zellij")]
|
||||
extern "C" {
|
||||
fn host_run_plugin_command();
|
||||
|
||||
@@ -38,6 +38,58 @@ impl NestedListItem {
|
||||
self.content = self.content.color_range(index_level, indices);
|
||||
self
|
||||
}
|
||||
pub fn error_color_indices(mut self, indices: Vec<usize>) -> Self {
|
||||
self.content = self.content.error_color_indices(indices);
|
||||
self
|
||||
}
|
||||
pub fn error_color_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
self.content = self.content.error_color_range(indices);
|
||||
self
|
||||
}
|
||||
pub fn error_color_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
self.content = self.content.error_color_substring(substr);
|
||||
self
|
||||
}
|
||||
pub fn error_color_nth_substring<S: AsRef<str>>(
|
||||
mut self,
|
||||
substr: S,
|
||||
occurrence_index: usize,
|
||||
) -> Self {
|
||||
self.content = self
|
||||
.content
|
||||
.error_color_nth_substring(substr, occurrence_index);
|
||||
self
|
||||
}
|
||||
pub fn error_color_all(mut self) -> Self {
|
||||
self.content = self.content.error_color_all();
|
||||
self
|
||||
}
|
||||
pub fn success_color_indices(mut self, indices: Vec<usize>) -> Self {
|
||||
self.content = self.content.success_color_indices(indices);
|
||||
self
|
||||
}
|
||||
pub fn success_color_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
self.content = self.content.success_color_range(indices);
|
||||
self
|
||||
}
|
||||
pub fn success_color_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
self.content = self.content.success_color_substring(substr);
|
||||
self
|
||||
}
|
||||
pub fn success_color_nth_substring<S: AsRef<str>>(
|
||||
mut self,
|
||||
substr: S,
|
||||
occurrence_index: usize,
|
||||
) -> Self {
|
||||
self.content = self
|
||||
.content
|
||||
.success_color_nth_substring(substr, occurrence_index);
|
||||
self
|
||||
}
|
||||
pub fn success_color_all(mut self) -> Self {
|
||||
self.content = self.content.success_color_all();
|
||||
self
|
||||
}
|
||||
pub fn serialize(&self) -> String {
|
||||
let mut serialized = String::new();
|
||||
for _ in 0..self.indentation_level {
|
||||
|
||||
@@ -29,6 +29,220 @@ impl Text {
|
||||
self.opaque = true;
|
||||
self
|
||||
}
|
||||
pub fn dim_indices(mut self, mut indices: Vec<usize>) -> Self {
|
||||
const DIM_LEVEL: usize = 4;
|
||||
self.pad_indices(DIM_LEVEL);
|
||||
self.indices
|
||||
.get_mut(DIM_LEVEL)
|
||||
.map(|i| i.append(&mut indices));
|
||||
self
|
||||
}
|
||||
pub fn dim_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
const DIM_LEVEL: usize = 4;
|
||||
self.pad_indices(DIM_LEVEL);
|
||||
let start = match indices.start_bound() {
|
||||
Bound::Unbounded => 0,
|
||||
Bound::Included(s) => *s,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let end = match indices.end_bound() {
|
||||
Bound::Unbounded => self.text.chars().count(),
|
||||
Bound::Included(s) => *s + 1,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let indices = (start..end).into_iter();
|
||||
self.indices
|
||||
.get_mut(DIM_LEVEL)
|
||||
.map(|i| i.append(&mut indices.into_iter().collect()));
|
||||
self
|
||||
}
|
||||
pub fn dim_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
let abs_pos = start + pos;
|
||||
self = self.dim_range(abs_pos..abs_pos + substr.chars().count());
|
||||
start = abs_pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn dim_all(self) -> Self {
|
||||
const DIM_LEVEL: usize = 4;
|
||||
self.color_range(DIM_LEVEL, ..)
|
||||
}
|
||||
pub fn unbold_indices(mut self, mut indices: Vec<usize>) -> Self {
|
||||
const UNBOLD_LEVEL: usize = 5;
|
||||
self.pad_indices(UNBOLD_LEVEL);
|
||||
self.indices
|
||||
.get_mut(UNBOLD_LEVEL)
|
||||
.map(|i| i.append(&mut indices));
|
||||
self
|
||||
}
|
||||
pub fn unbold_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
const UNBOLD_LEVEL: usize = 5;
|
||||
self.pad_indices(UNBOLD_LEVEL);
|
||||
let start = match indices.start_bound() {
|
||||
Bound::Unbounded => 0,
|
||||
Bound::Included(s) => *s,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let end = match indices.end_bound() {
|
||||
Bound::Unbounded => self.text.chars().count(),
|
||||
Bound::Included(s) => *s + 1,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let indices = (start..end).into_iter();
|
||||
self.indices
|
||||
.get_mut(UNBOLD_LEVEL)
|
||||
.map(|i| i.append(&mut indices.into_iter().collect()));
|
||||
self
|
||||
}
|
||||
pub fn unbold_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
let abs_pos = start + pos;
|
||||
self = self.unbold_range(abs_pos..abs_pos + substr.chars().count());
|
||||
start = abs_pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn unbold_all(self) -> Self {
|
||||
const UNBOLD_LEVEL: usize = 5;
|
||||
self.color_range(UNBOLD_LEVEL, ..)
|
||||
}
|
||||
pub fn error_color_indices(mut self, mut indices: Vec<usize>) -> Self {
|
||||
const ERROR_COLOR_LEVEL: usize = 6;
|
||||
self.pad_indices(ERROR_COLOR_LEVEL);
|
||||
self.indices
|
||||
.get_mut(ERROR_COLOR_LEVEL)
|
||||
.map(|i| i.append(&mut indices));
|
||||
self
|
||||
}
|
||||
pub fn error_color_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
const ERROR_COLOR_LEVEL: usize = 6;
|
||||
self.pad_indices(ERROR_COLOR_LEVEL);
|
||||
let start = match indices.start_bound() {
|
||||
Bound::Unbounded => 0,
|
||||
Bound::Included(s) => *s,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let end = match indices.end_bound() {
|
||||
Bound::Unbounded => self.text.chars().count(),
|
||||
Bound::Included(s) => *s + 1,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let indices = (start..end).into_iter();
|
||||
self.indices
|
||||
.get_mut(ERROR_COLOR_LEVEL)
|
||||
.map(|i| i.append(&mut indices.into_iter().collect()));
|
||||
self
|
||||
}
|
||||
pub fn error_color_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
let abs_pos = start + pos;
|
||||
self = self.error_color_range(abs_pos..abs_pos + substr.chars().count());
|
||||
start = abs_pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn error_color_nth_substring<S: AsRef<str>>(
|
||||
self,
|
||||
substr: S,
|
||||
occurrence_index: usize,
|
||||
) -> Self {
|
||||
const ERROR_COLOR_LEVEL: usize = 6;
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
let mut count = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
if count == occurrence_index {
|
||||
let abs_pos = start + pos;
|
||||
return self.color_range(ERROR_COLOR_LEVEL, abs_pos..abs_pos + substr.len());
|
||||
}
|
||||
count += 1;
|
||||
start = start + pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn error_color_all(self) -> Self {
|
||||
const ERROR_COLOR_LEVEL: usize = 6;
|
||||
self.color_range(ERROR_COLOR_LEVEL, ..)
|
||||
}
|
||||
pub fn success_color_indices(mut self, mut indices: Vec<usize>) -> Self {
|
||||
const SUCCESS_COLOR_LEVEL: usize = 7;
|
||||
self.pad_indices(SUCCESS_COLOR_LEVEL);
|
||||
self.indices
|
||||
.get_mut(SUCCESS_COLOR_LEVEL)
|
||||
.map(|i| i.append(&mut indices));
|
||||
self
|
||||
}
|
||||
pub fn success_color_range<R: RangeBounds<usize>>(mut self, indices: R) -> Self {
|
||||
const SUCCESS_COLOR_LEVEL: usize = 7;
|
||||
self.pad_indices(SUCCESS_COLOR_LEVEL);
|
||||
let start = match indices.start_bound() {
|
||||
Bound::Unbounded => 0,
|
||||
Bound::Included(s) => *s,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let end = match indices.end_bound() {
|
||||
Bound::Unbounded => self.text.chars().count(),
|
||||
Bound::Included(s) => *s + 1,
|
||||
Bound::Excluded(s) => *s,
|
||||
};
|
||||
let indices = (start..end).into_iter();
|
||||
self.indices
|
||||
.get_mut(SUCCESS_COLOR_LEVEL)
|
||||
.map(|i| i.append(&mut indices.into_iter().collect()));
|
||||
self
|
||||
}
|
||||
pub fn success_color_substring<S: AsRef<str>>(mut self, substr: S) -> Self {
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
let abs_pos = start + pos;
|
||||
self = self.success_color_range(abs_pos..abs_pos + substr.chars().count());
|
||||
start = abs_pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn success_color_nth_substring<S: AsRef<str>>(
|
||||
self,
|
||||
substr: S,
|
||||
occurrence_index: usize,
|
||||
) -> Self {
|
||||
const SUCCESS_COLOR_LEVEL: usize = 7;
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
let mut count = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
if count == occurrence_index {
|
||||
let abs_pos = start + pos;
|
||||
return self.color_range(SUCCESS_COLOR_LEVEL, abs_pos..abs_pos + substr.len());
|
||||
}
|
||||
count += 1;
|
||||
start = start + pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
pub fn success_color_all(self) -> Self {
|
||||
const SUCCESS_COLOR_LEVEL: usize = 7;
|
||||
self.color_range(SUCCESS_COLOR_LEVEL, ..)
|
||||
}
|
||||
pub fn color_indices(mut self, index_level: usize, mut indices: Vec<usize>) -> Self {
|
||||
self.pad_indices(index_level);
|
||||
self.indices
|
||||
@@ -54,16 +268,17 @@ impl Text {
|
||||
.map(|i| i.append(&mut indices.into_iter().collect()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color_substring<S: AsRef<str>>(mut self, index_level: usize, substr: S) -> Self {
|
||||
let substr = substr.as_ref();
|
||||
let mut start = 0;
|
||||
|
||||
while let Some(pos) = self.text[start..].find(substr) {
|
||||
let abs_pos = start + pos;
|
||||
self = self.color_range(index_level, abs_pos..abs_pos + substr.chars().count());
|
||||
let char_start = self.text[..abs_pos].chars().count();
|
||||
let char_end = char_start + substr.chars().count();
|
||||
self = self.color_range(index_level, char_start..char_end);
|
||||
start = abs_pos + substr.len();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user