mirror of https://github.com/lapce/lapce.git
Merge pull request #1246 from MinusGix/list
Unify Palette/Completion with List widget
This commit is contained in:
commit
def69d09cb
|
@ -50,13 +50,13 @@
|
|||
pub const LAPCE_UI_COMMAND: Selector<LapceUICommand> =
|
||||
Selector::new("lapce.ui_command");
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LapceCommand {
|
||||
pub kind: CommandKind,
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CommandKind {
|
||||
Workbench(LapceWorkbenchCommand),
|
||||
Edit(EditCommand),
|
||||
|
@ -544,8 +544,8 @@ pub enum LapceUICommand {
|
|||
RunPaletteReferences(Vec<EditorLocation<Position>>),
|
||||
InitPaletteInput(String),
|
||||
UpdatePaletteInput(String),
|
||||
UpdatePaletteItems(String, Vec<PaletteItem>),
|
||||
FilterPaletteItems(String, String, Vec<PaletteItem>),
|
||||
UpdatePaletteItems(String, im::Vector<PaletteItem>),
|
||||
FilterPaletteItems(String, String, im::Vector<PaletteItem>),
|
||||
UpdateKeymapsFilter(String),
|
||||
ResetSettingsFile(String, String),
|
||||
UpdateSettingsFile(String, String, Value),
|
||||
|
@ -712,6 +712,10 @@ pub enum LapceUICommand {
|
|||
},
|
||||
FileExplorerRefresh,
|
||||
SetLanguage(String),
|
||||
|
||||
/// An item in a list was chosen
|
||||
/// This is typically targeted at the widget which contains the list
|
||||
ListItemSelected,
|
||||
}
|
||||
|
||||
/// This can't be an `FnOnce` because we only ever get a reference to
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use std::{fmt::Display, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Error;
|
||||
use druid::{Size, WidgetId};
|
||||
use druid::{EventCtx, Size, WidgetId};
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use itertools::Itertools;
|
||||
use lapce_core::movement::Movement;
|
||||
use lapce_core::command::FocusCommand;
|
||||
use lapce_rpc::{buffer::BufferId, plugin::PluginId};
|
||||
use lsp_types::{CompletionItem, CompletionResponse, Position};
|
||||
use regex::Regex;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::proxy::LapceProxy;
|
||||
use crate::{config::Config, list::ListData, proxy::LapceProxy};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Snippet {
|
||||
|
@ -240,94 +240,70 @@ pub struct CompletionData {
|
|||
pub offset: usize,
|
||||
pub buffer_id: BufferId,
|
||||
pub input: String,
|
||||
pub index: usize,
|
||||
pub input_items: im::HashMap<String, Arc<Vec<ScoredCompletionItem>>>,
|
||||
empty: Arc<Vec<ScoredCompletionItem>>,
|
||||
pub filtered_items: Arc<Vec<ScoredCompletionItem>>,
|
||||
pub input_items: im::HashMap<String, im::Vector<ScoredCompletionItem>>,
|
||||
empty: im::Vector<ScoredCompletionItem>,
|
||||
pub completion_list: ListData<ScoredCompletionItem, ()>,
|
||||
pub matcher: Arc<SkimMatcherV2>,
|
||||
/// The size of the completion list
|
||||
pub size: Size,
|
||||
/// The size of the documentation view
|
||||
pub documentation_size: Size,
|
||||
}
|
||||
|
||||
impl CompletionData {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(config: Arc<Config>) -> Self {
|
||||
let id = WidgetId::next();
|
||||
let mut completion_list = ListData::new(config, id, ());
|
||||
// TODO: Make this configurable
|
||||
completion_list.max_displayed_items = 15;
|
||||
Self {
|
||||
id: WidgetId::next(),
|
||||
id,
|
||||
scroll_id: WidgetId::next(),
|
||||
documentation_scroll_id: WidgetId::next(),
|
||||
request_id: 0,
|
||||
index: 0,
|
||||
offset: 0,
|
||||
status: CompletionStatus::Inactive,
|
||||
buffer_id: BufferId(0),
|
||||
input: "".to_string(),
|
||||
input_items: im::HashMap::new(),
|
||||
filtered_items: Arc::new(Vec::new()),
|
||||
completion_list,
|
||||
matcher: Arc::new(SkimMatcherV2::default().ignore_case()),
|
||||
size: Size::new(400.0, 300.0),
|
||||
// TODO: Make this configurable
|
||||
documentation_size: Size::new(400.0, 300.0),
|
||||
empty: Arc::new(Vec::new()),
|
||||
empty: im::Vector::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of entries that are displayable
|
||||
pub fn len(&self) -> usize {
|
||||
self.current_items().len()
|
||||
self.completion_list.items.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// We need the line height so that we can get the number displayed as a number, since
|
||||
/// we just render as many fit inside the `size` defined for completion.
|
||||
fn entry_count(&self, editor_line_height: usize) -> usize {
|
||||
((self.size.height / editor_line_height as f64).ceil() as usize)
|
||||
.saturating_sub(1)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
self.index = Movement::Down.update_index(self.index, self.len(), 1, true);
|
||||
}
|
||||
|
||||
pub fn next_page(&mut self, editor_line_height: usize) {
|
||||
let count = self.entry_count(editor_line_height);
|
||||
self.index =
|
||||
Movement::Down.update_index(self.index, self.len(), count, false);
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
self.index = Movement::Up.update_index(self.index, self.len(), 1, true);
|
||||
}
|
||||
|
||||
pub fn previous_page(&mut self, editor_line_height: usize) {
|
||||
let count = self.entry_count(editor_line_height);
|
||||
self.index = Movement::Up.update_index(self.index, self.len(), count, false);
|
||||
}
|
||||
|
||||
pub fn current_items(&self) -> &Arc<Vec<ScoredCompletionItem>> {
|
||||
pub fn current_items(&self) -> &im::Vector<ScoredCompletionItem> {
|
||||
if self.input.is_empty() {
|
||||
self.all_items()
|
||||
} else {
|
||||
&self.filtered_items
|
||||
&self.completion_list.items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_items(&self) -> &Arc<Vec<ScoredCompletionItem>> {
|
||||
pub fn all_items(&self) -> &im::Vector<ScoredCompletionItem> {
|
||||
self.input_items
|
||||
.get(&self.input)
|
||||
.filter(|items| !items.is_empty())
|
||||
.unwrap_or_else(move || self.input_items.get("").unwrap_or(&self.empty))
|
||||
}
|
||||
|
||||
pub fn current_item(&self) -> &ScoredCompletionItem {
|
||||
&self.current_items()[self.index]
|
||||
pub fn current_item(&self) -> Option<&ScoredCompletionItem> {
|
||||
self.completion_list
|
||||
.items
|
||||
.get(self.completion_list.selected_index)
|
||||
}
|
||||
|
||||
pub fn current(&self) -> &str {
|
||||
self.current_items()[self.index].item.label.as_str()
|
||||
pub fn current(&self) -> Option<&str> {
|
||||
self.current_item().map(|item| item.item.label.as_str())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -351,12 +327,12 @@ pub fn cancel(&mut self) {
|
|||
self.status = CompletionStatus::Inactive;
|
||||
self.input = "".to_string();
|
||||
self.input_items.clear();
|
||||
self.index = 0;
|
||||
self.completion_list.clear_items();
|
||||
}
|
||||
|
||||
pub fn update_input(&mut self, input: String) {
|
||||
self.input = input;
|
||||
self.index = 0;
|
||||
self.completion_list.selected_index = 0;
|
||||
if self.status == CompletionStatus::Inactive {
|
||||
return;
|
||||
}
|
||||
|
@ -379,7 +355,7 @@ pub fn receive(
|
|||
CompletionResponse::Array(items) => items,
|
||||
CompletionResponse::List(list) => list.items,
|
||||
};
|
||||
let items: Vec<ScoredCompletionItem> = items
|
||||
let items: im::Vector<ScoredCompletionItem> = items
|
||||
.iter()
|
||||
.map(|i| ScoredCompletionItem {
|
||||
item: i.to_owned(),
|
||||
|
@ -390,20 +366,21 @@ pub fn receive(
|
|||
})
|
||||
.collect();
|
||||
|
||||
self.input_items.insert(input, Arc::new(items));
|
||||
self.input_items.insert(input, items);
|
||||
self.filter_items();
|
||||
|
||||
if self.index >= self.len() {
|
||||
self.index = 0;
|
||||
if self.completion_list.selected_index >= self.len() {
|
||||
self.completion_list.selected_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_items(&mut self) {
|
||||
if self.input.is_empty() {
|
||||
self.completion_list.items = self.all_items().clone();
|
||||
return;
|
||||
}
|
||||
|
||||
let mut items: Vec<ScoredCompletionItem> = self
|
||||
let mut items: im::Vector<ScoredCompletionItem> = self
|
||||
.all_items()
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
|
@ -439,31 +416,15 @@ pub fn filter_items(&mut self) {
|
|||
.then_with(|| b.label_score.cmp(&a.label_score))
|
||||
.then_with(|| a.item.label.len().cmp(&b.item.label.len()))
|
||||
});
|
||||
self.filtered_items = Arc::new(items);
|
||||
self.completion_list.items = items;
|
||||
}
|
||||
|
||||
pub fn run_focus_command(&mut self, ctx: &mut EventCtx, command: &FocusCommand) {
|
||||
self.completion_list.run_focus_command(ctx, command);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CompletionData {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Completion {}
|
||||
|
||||
impl Completion {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Completion {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ScoredCompletionItem {
|
||||
pub item: CompletionItem,
|
||||
pub plugin_id: PluginId,
|
||||
|
|
|
@ -664,8 +664,8 @@ pub fn new(
|
|||
term_sender.clone(),
|
||||
event_sink.clone(),
|
||||
));
|
||||
let palette = Arc::new(PaletteData::new(proxy.clone()));
|
||||
let completion = Arc::new(CompletionData::new());
|
||||
let palette = Arc::new(PaletteData::new(config.clone(), proxy.clone()));
|
||||
let completion = Arc::new(CompletionData::new(config.clone()));
|
||||
let hover = Arc::new(HoverData::new());
|
||||
let rename = Arc::new(RenameData::new());
|
||||
let source_control = Arc::new(SourceControlData::new());
|
||||
|
@ -958,6 +958,7 @@ pub fn completion_origin(
|
|||
&self,
|
||||
text: &mut PietText,
|
||||
tab_size: Size,
|
||||
completion_size: Size,
|
||||
config: &Config,
|
||||
) -> Point {
|
||||
let line_height = self.config.editor.line_height() as f64;
|
||||
|
@ -986,10 +987,8 @@ pub fn completion_origin(
|
|||
let mut origin = *editor.window_origin.borrow()
|
||||
- self.window_origin.borrow().to_vec2()
|
||||
+ Vec2::new(point_below.x - line_height - 5.0, point_below.y);
|
||||
if origin.y + self.completion.size.height + 1.0 > tab_size.height {
|
||||
let height = self
|
||||
.completion
|
||||
.size
|
||||
if origin.y + completion_size.height + 1.0 > tab_size.height {
|
||||
let height = completion_size
|
||||
.height
|
||||
.min(self.completion.len() as f64 * line_height);
|
||||
origin.y = editor.window_origin.borrow().y
|
||||
|
@ -997,8 +996,8 @@ pub fn completion_origin(
|
|||
+ point_above.y
|
||||
- height;
|
||||
}
|
||||
if origin.x + self.completion.size.width + 1.0 > tab_size.width {
|
||||
origin.x = tab_size.width - self.completion.size.width - 1.0;
|
||||
if origin.x + completion_size.width + 1.0 > tab_size.width {
|
||||
origin.x = tab_size.width - completion_size.width - 1.0;
|
||||
}
|
||||
if origin.x <= 0.0 {
|
||||
origin.x = 0.0;
|
||||
|
|
|
@ -196,7 +196,7 @@ fn init_buffer_content_cmd(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EditorLocation<P: EditorPosition = usize> {
|
||||
pub path: PathBuf,
|
||||
pub position: Option<P>,
|
||||
|
@ -632,6 +632,52 @@ pub fn cancel_rename(&mut self, ctx: &mut EventCtx) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn completion_item_select(&mut self, ctx: &mut EventCtx) {
|
||||
let item = if let Some(item) = self.completion.current_item() {
|
||||
item.to_owned()
|
||||
} else {
|
||||
// There was no selected item, this may be due to a bug in failing to ensure that the index was valid.
|
||||
return;
|
||||
};
|
||||
|
||||
self.cancel_completion();
|
||||
if item.item.data.is_some() {
|
||||
let view_id = self.editor.view_id;
|
||||
let buffer_id = self.doc.id();
|
||||
let rev = self.doc.rev();
|
||||
let offset = self.editor.cursor.offset();
|
||||
let event_sink = ctx.get_external_handle();
|
||||
self.proxy.proxy_rpc.completion_resolve(
|
||||
item.plugin_id,
|
||||
item.item.clone(),
|
||||
move |result| {
|
||||
// let item = result.unwrap_or_else(|_| item.clone());
|
||||
let item =
|
||||
if let Ok(ProxyResponse::CompletionResolveResponse {
|
||||
item,
|
||||
}) = result
|
||||
{
|
||||
*item
|
||||
} else {
|
||||
item.item.clone()
|
||||
};
|
||||
let _ = event_sink.submit_command(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ResolveCompletion(
|
||||
buffer_id,
|
||||
rev,
|
||||
offset,
|
||||
Box::new(item),
|
||||
),
|
||||
Target::Widget(view_id),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let _ = self.apply_completion_item(&item.item);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the displayed autocompletion box
|
||||
/// Sends a request to the LSP for completion information
|
||||
fn update_completion(
|
||||
|
@ -1679,44 +1725,8 @@ fn run_focus_command(
|
|||
Target::Widget(self.palette.widget_id),
|
||||
));
|
||||
} else {
|
||||
let item = self.completion.current_item().to_owned();
|
||||
self.cancel_completion();
|
||||
if item.item.data.is_some() {
|
||||
let view_id = self.editor.view_id;
|
||||
let buffer_id = self.doc.id();
|
||||
let rev = self.doc.rev();
|
||||
let offset = self.editor.cursor.offset();
|
||||
let event_sink = ctx.get_external_handle();
|
||||
self.proxy.proxy_rpc.completion_resolve(
|
||||
item.plugin_id,
|
||||
item.item.clone(),
|
||||
move |result| {
|
||||
// let item = result.unwrap_or_else(|_| item.clone());
|
||||
let item = if let Ok(
|
||||
ProxyResponse::CompletionResolveResponse {
|
||||
item,
|
||||
},
|
||||
) = result
|
||||
{
|
||||
*item
|
||||
} else {
|
||||
item.item.clone()
|
||||
};
|
||||
let _ = event_sink.submit_command(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ResolveCompletion(
|
||||
buffer_id,
|
||||
rev,
|
||||
offset,
|
||||
Box::new(item),
|
||||
),
|
||||
Target::Widget(view_id),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let _ = self.apply_completion_item(&item.item);
|
||||
}
|
||||
let completion = Arc::make_mut(&mut self.completion);
|
||||
completion.run_focus_command(ctx, cmd);
|
||||
}
|
||||
}
|
||||
ListNext => {
|
||||
|
@ -1731,7 +1741,7 @@ fn run_focus_command(
|
|||
));
|
||||
} else {
|
||||
let completion = Arc::make_mut(&mut self.completion);
|
||||
completion.next();
|
||||
completion.run_focus_command(ctx, cmd);
|
||||
}
|
||||
}
|
||||
ListNextPage => {
|
||||
|
@ -1746,7 +1756,7 @@ fn run_focus_command(
|
|||
));
|
||||
} else {
|
||||
let completion = Arc::make_mut(&mut self.completion);
|
||||
completion.next_page(self.config.editor.line_height());
|
||||
completion.run_focus_command(ctx, cmd);
|
||||
}
|
||||
}
|
||||
ListPrevious => {
|
||||
|
@ -1761,7 +1771,7 @@ fn run_focus_command(
|
|||
));
|
||||
} else {
|
||||
let completion = Arc::make_mut(&mut self.completion);
|
||||
completion.previous();
|
||||
completion.run_focus_command(ctx, cmd);
|
||||
}
|
||||
}
|
||||
ListPreviousPage => {
|
||||
|
@ -1776,7 +1786,7 @@ fn run_focus_command(
|
|||
));
|
||||
} else {
|
||||
let completion = Arc::make_mut(&mut self.completion);
|
||||
completion.previous_page(self.config.editor.line_height());
|
||||
completion.run_focus_command(ctx, cmd);
|
||||
}
|
||||
}
|
||||
JumpToNextSnippetPlaceholder => {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
pub mod history;
|
||||
pub mod hover;
|
||||
pub mod keypress;
|
||||
pub mod list;
|
||||
pub mod markdown;
|
||||
pub mod menu;
|
||||
pub mod palette;
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use druid::{Command, Data, EventCtx, Target, WidgetId};
|
||||
use lapce_core::{command::FocusCommand, movement::Movement};
|
||||
|
||||
use crate::{
|
||||
command::{
|
||||
CommandExecuted, CommandKind, LapceCommand, LapceUICommand, LAPCE_UI_COMMAND,
|
||||
},
|
||||
config::{Config, GetConfig},
|
||||
};
|
||||
|
||||
// Note: when adding fields to this, make sure to think whether they need to be added to the `same`
|
||||
// implementation
|
||||
/// Note: all `T` are going to be required by the UI code to implement `ListPaint<D>`
|
||||
#[derive(Clone)]
|
||||
pub struct ListData<T: Clone, D: Data> {
|
||||
/// The id of the widget that contains the [`ListData`] which wishes to receive
|
||||
/// events (such as when an entry is selected)
|
||||
pub parent: WidgetId,
|
||||
|
||||
/// The items that can be selected from the list
|
||||
/// Note that since this is an `im::Vector`, cloning is cheap.
|
||||
pub items: im::Vector<T>,
|
||||
/// Extra data attached to the list for the item rendering to use.
|
||||
pub data: D,
|
||||
|
||||
/// The index of the item which is selected
|
||||
pub selected_index: usize,
|
||||
|
||||
/// The maximum number of items to render in the list.
|
||||
pub max_displayed_items: usize,
|
||||
|
||||
/// The line height of each list element
|
||||
/// Defaults to the editor line height if not set
|
||||
pub line_height: Option<usize>,
|
||||
|
||||
// These should be filled whenever you call into the `List` widget
|
||||
pub config: Arc<Config>,
|
||||
}
|
||||
impl<T: Clone, D: Data> ListData<T, D> {
|
||||
pub fn new(
|
||||
config: Arc<Config>,
|
||||
parent: WidgetId,
|
||||
held_data: D,
|
||||
) -> ListData<T, D> {
|
||||
ListData {
|
||||
parent,
|
||||
items: im::Vector::new(),
|
||||
data: held_data,
|
||||
selected_index: 0,
|
||||
max_displayed_items: 15,
|
||||
line_height: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the list data, giving it data needed to update it
|
||||
/// This is typically what you need to use to ensure that it has the
|
||||
/// appropriately updated data when passing the data to the list's widget functions
|
||||
/// Note that due to the usage of `Arc` and `im::Vector`, cloning is relatively cheap.
|
||||
pub fn clone_with(&self, config: Arc<Config>) -> ListData<T, D> {
|
||||
let mut data = self.clone();
|
||||
data.update_data(config);
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
pub fn update_data(&mut self, config: Arc<Config>) {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
pub fn line_height(&self) -> usize {
|
||||
self.line_height
|
||||
.unwrap_or_else(|| self.config.editor.line_height())
|
||||
}
|
||||
|
||||
/// The maximum number of items in the list that can be displayed
|
||||
/// This is limited by `max_displayed_items` *or* by the number of items
|
||||
pub fn max_display_count(&self) -> usize {
|
||||
let mut count = 0;
|
||||
for _ in self.items.iter() {
|
||||
count += 1;
|
||||
if count >= self.max_displayed_items {
|
||||
return self.max_displayed_items;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
pub fn clear_items(&mut self) {
|
||||
self.items.clear();
|
||||
self.selected_index = 0;
|
||||
}
|
||||
|
||||
/// Run a command, like those received from KeyPressFocus
|
||||
pub fn run_command(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
command: &LapceCommand,
|
||||
) -> CommandExecuted {
|
||||
match &command.kind {
|
||||
CommandKind::Focus(cmd) => self.run_focus_command(ctx, cmd),
|
||||
_ => CommandExecuted::No,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_focus_command(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
command: &FocusCommand,
|
||||
) -> CommandExecuted {
|
||||
match command {
|
||||
// ModalClose should be handled (if desired) by the containing widget
|
||||
FocusCommand::ListNext => {
|
||||
self.next();
|
||||
}
|
||||
FocusCommand::ListNextPage => {
|
||||
self.next_page();
|
||||
}
|
||||
FocusCommand::ListPrevious => {
|
||||
self.previous();
|
||||
}
|
||||
FocusCommand::ListPreviousPage => {
|
||||
self.previous_page();
|
||||
}
|
||||
FocusCommand::ListSelect => {
|
||||
self.select(ctx);
|
||||
}
|
||||
_ => return CommandExecuted::No,
|
||||
}
|
||||
CommandExecuted::Yes
|
||||
}
|
||||
|
||||
// TODO: Option for whether moving should be wrapping
|
||||
|
||||
pub fn next(&mut self) {
|
||||
self.selected_index = Movement::Down.update_index(
|
||||
self.selected_index,
|
||||
self.items.len(),
|
||||
1,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn next_page(&mut self) {
|
||||
self.selected_index = Movement::Down.update_index(
|
||||
self.selected_index,
|
||||
self.items.len(),
|
||||
self.max_displayed_items - 1,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
self.selected_index = Movement::Up.update_index(
|
||||
self.selected_index,
|
||||
self.items.len(),
|
||||
1,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn previous_page(&mut self) {
|
||||
self.selected_index = Movement::Up.update_index(
|
||||
self.selected_index,
|
||||
self.items.len(),
|
||||
self.max_displayed_items - 1,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn select(&self, ctx: &mut EventCtx) {
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ListItemSelected,
|
||||
Target::Widget(self.parent),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn current_selected_item(&self) -> Option<&T> {
|
||||
self.items.get(self.selected_index)
|
||||
}
|
||||
}
|
||||
impl<T: Clone + PartialEq + 'static, D: Data> Data for ListData<T, D> {
|
||||
fn same(&self, other: &Self) -> bool {
|
||||
// We don't compare the held Config, because that should be updated whenever
|
||||
// the widget is used
|
||||
|
||||
self.parent == other.parent
|
||||
&& self.items == other.items
|
||||
&& self.data.same(&other.data)
|
||||
&& self.selected_index.same(&other.selected_index)
|
||||
&& self.max_displayed_items.same(&other.max_displayed_items)
|
||||
&& self.line_height.same(&other.line_height)
|
||||
}
|
||||
}
|
||||
impl<T: Clone + PartialEq + 'static, D: Data> GetConfig for ListData<T, D> {
|
||||
fn get_config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
use lapce_core::command::{EditCommand, FocusCommand};
|
||||
use lapce_core::language::LapceLanguage;
|
||||
use lapce_core::mode::Mode;
|
||||
use lapce_core::movement::Movement;
|
||||
use lapce_rpc::proxy::ProxyResponse;
|
||||
use lsp_types::{DocumentSymbolResponse, Position, Range, SymbolKind};
|
||||
use std::cmp::Ordering;
|
||||
|
@ -22,6 +21,7 @@
|
|||
use crate::data::{LapceWorkspace, LapceWorkspaceType};
|
||||
use crate::document::BufferContent;
|
||||
use crate::editor::EditorLocation;
|
||||
use crate::list::ListData;
|
||||
use crate::panel::PanelKind;
|
||||
use crate::proxy::path_from_url;
|
||||
use crate::{
|
||||
|
@ -119,7 +119,7 @@ pub enum PaletteStatus {
|
|||
Done,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PaletteItemContent {
|
||||
File(PathBuf, PathBuf),
|
||||
Line(usize, String),
|
||||
|
@ -284,7 +284,7 @@ fn select(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PaletteItem {
|
||||
pub content: PaletteItemContent,
|
||||
pub filter_text: String,
|
||||
|
@ -332,22 +332,31 @@ fn with_mut<V, F: FnOnce(&mut PaletteViewData) -> V>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Data to be held by the palette list
|
||||
#[derive(Data, Clone)]
|
||||
pub struct PaletteListData {
|
||||
/// Should only be `None` when it hasn't been updated initially
|
||||
/// We need this just for some rendering, and not editing it.
|
||||
pub workspace: Option<Arc<LapceWorkspace>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PaletteData {
|
||||
pub widget_id: WidgetId,
|
||||
pub scroll_id: WidgetId,
|
||||
pub status: PaletteStatus,
|
||||
/// Holds information about the list, including the filtered items
|
||||
pub list_data: ListData<PaletteItem, PaletteListData>,
|
||||
pub proxy: Arc<LapceProxy>,
|
||||
pub palette_type: PaletteType,
|
||||
pub sender: Sender<(String, String, Vec<PaletteItem>)>,
|
||||
pub receiver: Option<Receiver<(String, String, Vec<PaletteItem>)>>,
|
||||
pub sender: Sender<(String, String, im::Vector<PaletteItem>)>,
|
||||
pub receiver: Option<Receiver<(String, String, im::Vector<PaletteItem>)>>,
|
||||
pub run_id: String,
|
||||
pub input: String,
|
||||
pub cursor: usize,
|
||||
pub index: usize,
|
||||
pub has_nonzero_default_index: bool,
|
||||
pub items: Vec<PaletteItem>,
|
||||
pub filtered_items: Vec<PaletteItem>,
|
||||
/// The unfiltered items list
|
||||
pub total_items: im::Vector<PaletteItem>,
|
||||
pub preview_editor: WidgetId,
|
||||
pub input_editor: WidgetId,
|
||||
}
|
||||
|
@ -361,38 +370,6 @@ fn check_condition(&self, condition: &str) -> bool {
|
|||
matches!(condition, "list_focus" | "palette_focus" | "modal_focus")
|
||||
}
|
||||
|
||||
// fn run_command(
|
||||
// &mut self,
|
||||
// ctx: &mut EventCtx,
|
||||
// command: &LapceCommand,
|
||||
// _count: Option<usize>,
|
||||
// _mods: Modifiers,
|
||||
// _env: &Env,
|
||||
// ) -> CommandExecuted {
|
||||
// match command {
|
||||
// LapceCommand::ModalClose => {
|
||||
// self.cancel(ctx);
|
||||
// }
|
||||
// LapceCommand::DeleteBackward => {
|
||||
// self.delete_backward(ctx);
|
||||
// }
|
||||
// LapceCommand::DeleteToBeginningOfLine => {
|
||||
// self.delete_to_beginning_of_line(ctx);
|
||||
// }
|
||||
// LapceCommand::ListNext => {
|
||||
// self.next(ctx);
|
||||
// }
|
||||
// LapceCommand::ListPrevious => {
|
||||
// self.previous(ctx);
|
||||
// }
|
||||
// LapceCommand::ListSelect => {
|
||||
// self.select(ctx);
|
||||
// }
|
||||
// _ => return CommandExecuted::No,
|
||||
// }
|
||||
// CommandExecuted::Yes
|
||||
// }
|
||||
|
||||
fn receive_char(&mut self, ctx: &mut EventCtx, c: &str) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.input.insert_str(palette.cursor, c);
|
||||
|
@ -408,28 +385,22 @@ fn run_command(
|
|||
_mods: Modifiers,
|
||||
_env: &Env,
|
||||
) -> CommandExecuted {
|
||||
let selected_index = self.palette.list_data.selected_index;
|
||||
|
||||
// Pass any commands, like list movement, to the selector list
|
||||
Arc::make_mut(&mut self.palette)
|
||||
.list_data
|
||||
.run_command(ctx, command);
|
||||
|
||||
// If the selection changed, then update the preview
|
||||
if selected_index != self.palette.list_data.selected_index {
|
||||
self.palette.preview(ctx);
|
||||
}
|
||||
|
||||
match &command.kind {
|
||||
CommandKind::Focus(cmd) => match cmd {
|
||||
FocusCommand::ModalClose => {
|
||||
self.cancel(ctx);
|
||||
}
|
||||
FocusCommand::ListNext => {
|
||||
self.next(ctx);
|
||||
}
|
||||
FocusCommand::ListNextPage => {
|
||||
self.next_page(ctx);
|
||||
}
|
||||
FocusCommand::ListPrevious => {
|
||||
self.previous(ctx);
|
||||
}
|
||||
FocusCommand::ListPreviousPage => {
|
||||
self.previous_page(ctx);
|
||||
}
|
||||
FocusCommand::ListSelect => {
|
||||
self.select(ctx);
|
||||
}
|
||||
_ => return CommandExecuted::No,
|
||||
},
|
||||
CommandKind::Focus(FocusCommand::ModalClose) => {
|
||||
self.cancel(ctx);
|
||||
}
|
||||
CommandKind::Edit(cmd) => match cmd {
|
||||
EditCommand::DeleteBackward => {
|
||||
self.delete_backward(ctx);
|
||||
|
@ -446,15 +417,21 @@ fn run_command(
|
|||
}
|
||||
|
||||
impl PaletteData {
|
||||
pub fn new(proxy: Arc<LapceProxy>) -> Self {
|
||||
pub fn new(config: Arc<Config>, proxy: Arc<LapceProxy>) -> Self {
|
||||
let (sender, receiver) = unbounded();
|
||||
let widget_id = WidgetId::next();
|
||||
let scroll_id = WidgetId::next();
|
||||
let preview_editor = WidgetId::next();
|
||||
let mut list_data =
|
||||
ListData::new(config, widget_id, PaletteListData { workspace: None });
|
||||
// TODO: Make these configurable
|
||||
list_data.line_height = Some(25);
|
||||
list_data.max_displayed_items = 15;
|
||||
Self {
|
||||
widget_id,
|
||||
scroll_id,
|
||||
status: PaletteStatus::Inactive,
|
||||
list_data,
|
||||
proxy,
|
||||
palette_type: PaletteType::File,
|
||||
sender,
|
||||
|
@ -462,10 +439,8 @@ pub fn new(proxy: Arc<LapceProxy>) -> Self {
|
|||
run_id: Uuid::new_v4().to_string(),
|
||||
input: "".to_string(),
|
||||
cursor: 0,
|
||||
index: 0,
|
||||
has_nonzero_default_index: false,
|
||||
items: Vec::new(),
|
||||
filtered_items: Vec::new(),
|
||||
total_items: im::Vector::new(),
|
||||
preview_editor,
|
||||
input_editor: WidgetId::next(),
|
||||
}
|
||||
|
@ -479,28 +454,20 @@ pub fn is_empty(&self) -> bool {
|
|||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn current_items(&self) -> &Vec<PaletteItem> {
|
||||
pub fn current_items(&self) -> &im::Vector<PaletteItem> {
|
||||
if self.get_input() == "" {
|
||||
&self.items
|
||||
&self.total_items
|
||||
} else {
|
||||
&self.filtered_items
|
||||
&self.list_data.items
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preview(&self, ctx: &mut EventCtx) {
|
||||
if let Some(item) = self.get_item() {
|
||||
if let Some(item) = self.list_data.current_selected_item() {
|
||||
item.content.select(ctx, true, self.preview_editor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_item(&self) -> Option<&PaletteItem> {
|
||||
let items = self.current_items();
|
||||
if items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(&items[self.index])
|
||||
}
|
||||
|
||||
pub fn get_input(&self) -> &str {
|
||||
match &self.palette_type {
|
||||
PaletteType::File => &self.input,
|
||||
|
@ -518,19 +485,15 @@ pub fn get_input(&self) -> &str {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Make this configurable
|
||||
/// The maximum number of palette items to display per 'page'
|
||||
pub const MAX_PALETTE_ITEMS: usize = 15;
|
||||
impl PaletteViewData {
|
||||
pub fn cancel(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.status = PaletteStatus::Inactive;
|
||||
palette.input = "".to_string();
|
||||
palette.cursor = 0;
|
||||
palette.index = 0;
|
||||
palette.palette_type = PaletteType::File;
|
||||
palette.items.clear();
|
||||
palette.filtered_items.clear();
|
||||
palette.total_items.clear();
|
||||
palette.list_data.clear_items();
|
||||
if let Some(active) = *self.main_split.active_tab {
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
|
@ -552,7 +515,7 @@ pub fn run_references(
|
|||
locations: &[EditorLocation<Position>],
|
||||
) {
|
||||
self.run(ctx, Some(PaletteType::Reference), None);
|
||||
let items: Vec<PaletteItem> = locations
|
||||
let items = locations
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let full_path = l.path.clone();
|
||||
|
@ -573,8 +536,9 @@ pub fn run_references(
|
|||
})
|
||||
.collect();
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = items;
|
||||
palette.total_items = items;
|
||||
palette.preview(ctx);
|
||||
self.fill_list();
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
|
@ -592,11 +556,10 @@ pub fn run(
|
|||
LapceUICommand::InitPaletteInput(palette.input.clone()),
|
||||
Target::Widget(*self.main_split.tab_id),
|
||||
));
|
||||
palette.items = Vec::new();
|
||||
palette.filtered_items = Vec::new();
|
||||
palette.total_items.clear();
|
||||
palette.list_data.clear_items();
|
||||
palette.run_id = Uuid::new_v4().to_string();
|
||||
palette.cursor = palette.input.len();
|
||||
palette.index = 0;
|
||||
|
||||
if let Some(active_editor_content) =
|
||||
self.main_split.active_editor().map(|e| e.content.clone())
|
||||
|
@ -653,6 +616,16 @@ pub fn run(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fill_list();
|
||||
}
|
||||
|
||||
/// Fill the list with the stored unfiltered total items
|
||||
fn fill_list(&mut self) {
|
||||
if self.palette.input.is_empty() {
|
||||
Arc::make_mut(&mut self.palette).list_data.items =
|
||||
self.palette.total_items.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_backward(&mut self, ctx: &mut EventCtx) {
|
||||
|
@ -696,51 +669,16 @@ pub fn delete_to_beginning_of_line(&mut self, ctx: &mut EventCtx) {
|
|||
self.update_palette(ctx);
|
||||
}
|
||||
|
||||
pub fn next(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.index =
|
||||
Movement::Down.update_index(palette.index, palette.len(), 1, true);
|
||||
palette.preview(ctx);
|
||||
}
|
||||
|
||||
pub fn next_page(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.index = Movement::Down.update_index(
|
||||
palette.index,
|
||||
palette.len(),
|
||||
MAX_PALETTE_ITEMS - 1,
|
||||
false,
|
||||
);
|
||||
palette.preview(ctx);
|
||||
}
|
||||
|
||||
pub fn previous(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.index =
|
||||
Movement::Up.update_index(palette.index, palette.len(), 1, true);
|
||||
palette.preview(ctx);
|
||||
}
|
||||
|
||||
pub fn previous_page(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.index = Movement::Up.update_index(
|
||||
palette.index,
|
||||
palette.len(),
|
||||
MAX_PALETTE_ITEMS - 1,
|
||||
false,
|
||||
);
|
||||
palette.preview(ctx);
|
||||
}
|
||||
|
||||
// TODO: This is a bit weird, its wanting to iterate over items, but it could be called before we fill the list!
|
||||
fn preselect_matching(&mut self, ctx: &mut EventCtx, matching: &str) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
if let Some((id, _)) = palette
|
||||
.items
|
||||
.total_items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, item)| item.filter_text == matching)
|
||||
{
|
||||
palette.index = id;
|
||||
palette.list_data.selected_index = id;
|
||||
palette.has_nonzero_default_index = true;
|
||||
palette.preview(ctx);
|
||||
}
|
||||
|
@ -759,7 +697,7 @@ pub fn select(&mut self, ctx: &mut EventCtx) {
|
|||
));
|
||||
}
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
if let Some(item) = palette.get_item() {
|
||||
if let Some(item) = palette.list_data.current_selected_item() {
|
||||
if item.content.select(ctx, false, palette.preview_editor) {
|
||||
self.cancel(ctx);
|
||||
}
|
||||
|
@ -803,14 +741,13 @@ pub fn update_input(&mut self, ctx: &mut EventCtx, input: String) {
|
|||
// Update the current input
|
||||
palette.input = input;
|
||||
|
||||
self.update_palette(ctx)
|
||||
self.update_palette(ctx);
|
||||
}
|
||||
|
||||
pub fn update_palette(&mut self, ctx: &mut EventCtx) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
|
||||
if !palette.has_nonzero_default_index {
|
||||
palette.index = 0;
|
||||
palette.list_data.selected_index = 0;
|
||||
}
|
||||
palette.has_nonzero_default_index = false;
|
||||
|
||||
|
@ -823,14 +760,17 @@ pub fn update_palette(&mut self, ctx: &mut EventCtx) {
|
|||
return;
|
||||
}
|
||||
|
||||
if self.palette.get_input() != "" {
|
||||
if self.palette.get_input() == "" {
|
||||
self.palette.preview(ctx);
|
||||
Arc::make_mut(&mut self.palette).list_data.items =
|
||||
self.palette.total_items.clone();
|
||||
} else {
|
||||
// Update the filtering with the input
|
||||
let _ = self.palette.sender.send((
|
||||
self.palette.run_id.clone(),
|
||||
self.palette.get_input().to_string(),
|
||||
self.palette.items.clone(),
|
||||
self.palette.total_items.clone(),
|
||||
));
|
||||
} else {
|
||||
self.palette.preview(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -841,7 +781,7 @@ fn get_files(&self, ctx: &mut EventCtx) {
|
|||
let event_sink = ctx.get_external_handle();
|
||||
self.palette.proxy.proxy_rpc.get_files(move |result| {
|
||||
if let Ok(ProxyResponse::GetFilesResponse { items }) = result {
|
||||
let items: Vec<PaletteItem> = items
|
||||
let items: im::Vector<PaletteItem> = items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(_index, path)| {
|
||||
|
@ -882,7 +822,7 @@ fn get_ssh_hosts(&mut self, _ctx: &mut EventCtx) {
|
|||
}
|
||||
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = hosts
|
||||
palette.total_items = hosts
|
||||
.iter()
|
||||
.map(|(user, host)| PaletteItem {
|
||||
content: PaletteItemContent::SshHost(
|
||||
|
@ -899,7 +839,7 @@ fn get_ssh_hosts(&mut self, _ctx: &mut EventCtx) {
|
|||
fn get_workspaces(&mut self, _ctx: &mut EventCtx) {
|
||||
let workspaces = Config::recent_workspaces().unwrap_or_default();
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = workspaces
|
||||
palette.total_items = workspaces
|
||||
.into_iter()
|
||||
.map(|w| {
|
||||
let text = w
|
||||
|
@ -930,7 +870,7 @@ fn get_workspaces(&mut self, _ctx: &mut EventCtx) {
|
|||
|
||||
fn get_themes(&mut self, _ctx: &mut EventCtx, config: &Config) {
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = config
|
||||
palette.total_items = config
|
||||
.available_themes
|
||||
.values()
|
||||
.sorted_by_key(|(n, _)| n)
|
||||
|
@ -947,7 +887,7 @@ fn get_languages(&mut self, _ctx: &mut EventCtx) {
|
|||
let palette = Arc::make_mut(&mut self.palette);
|
||||
let mut langs = LapceLanguage::languages();
|
||||
langs.push("Plain Text".to_string());
|
||||
palette.items = langs
|
||||
palette.total_items = langs
|
||||
.iter()
|
||||
.sorted()
|
||||
.map(|n| PaletteItem {
|
||||
|
@ -963,7 +903,7 @@ fn get_commands(&mut self, _ctx: &mut EventCtx) {
|
|||
const EXCLUDED_ITEMS: &[&str] = &["palette.command"];
|
||||
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = self
|
||||
palette.total_items = self
|
||||
.keypress
|
||||
.commands
|
||||
.iter()
|
||||
|
@ -989,7 +929,7 @@ fn get_lines(&mut self, _ctx: &mut EventCtx) {
|
|||
{
|
||||
let raw = terminal.raw.lock();
|
||||
let term = &raw.term;
|
||||
let mut items = Vec::new();
|
||||
let mut items = im::Vector::new();
|
||||
let mut last_row: Option<String> = None;
|
||||
let mut current_line = term.topmost_line().0;
|
||||
for line in term.topmost_line().0..term.bottommost_line().0 {
|
||||
|
@ -1020,11 +960,11 @@ fn get_lines(&mut self, _ctx: &mut EventCtx) {
|
|||
score: 0,
|
||||
indices: vec![],
|
||||
};
|
||||
items.push(item);
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = items;
|
||||
palette.total_items = items;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1038,7 +978,7 @@ fn get_lines(&mut self, _ctx: &mut EventCtx) {
|
|||
let last_line_number = doc.buffer().last_line() + 1;
|
||||
let last_line_number_len = last_line_number.to_string().len();
|
||||
let palette = Arc::make_mut(&mut self.palette);
|
||||
palette.items = doc
|
||||
palette.total_items = doc
|
||||
.buffer()
|
||||
.text()
|
||||
.lines(0..doc.buffer().len())
|
||||
|
@ -1083,7 +1023,7 @@ fn get_document_symbols(&mut self, ctx: &mut EventCtx) {
|
|||
.proxy_rpc
|
||||
.get_document_symbols(path, move |result| {
|
||||
if let Ok(ProxyResponse::GetDocumentSymbols { resp }) = result {
|
||||
let items: Vec<PaletteItem> = match resp {
|
||||
let items: im::Vector<PaletteItem> = match resp {
|
||||
DocumentSymbolResponse::Flat(symbols) => symbols
|
||||
.iter()
|
||||
.map(|s| {
|
||||
|
@ -1156,7 +1096,7 @@ fn get_workspace_symbols(&mut self, ctx: &mut EventCtx) {
|
|||
if let Ok(ProxyResponse::GetWorkspaceSymbols { symbols }) =
|
||||
result
|
||||
{
|
||||
let items: Vec<PaletteItem> = symbols
|
||||
let items: im::Vector<PaletteItem> = symbols
|
||||
.iter()
|
||||
.map(|s| {
|
||||
// TODO: Should we be using filter text?
|
||||
|
@ -1196,13 +1136,13 @@ fn get_workspace_symbols(&mut self, ctx: &mut EventCtx) {
|
|||
}
|
||||
|
||||
pub fn update_process(
|
||||
receiver: Receiver<(String, String, Vec<PaletteItem>)>,
|
||||
receiver: Receiver<(String, String, im::Vector<PaletteItem>)>,
|
||||
widget_id: WidgetId,
|
||||
event_sink: ExtEventSink,
|
||||
) {
|
||||
fn receive_batch(
|
||||
receiver: &Receiver<(String, String, Vec<PaletteItem>)>,
|
||||
) -> Result<(String, String, Vec<PaletteItem>)> {
|
||||
receiver: &Receiver<(String, String, im::Vector<PaletteItem>)>,
|
||||
) -> Result<(String, String, im::Vector<PaletteItem>)> {
|
||||
let (mut run_id, mut input, mut items) = receiver.recv()?;
|
||||
loop {
|
||||
match receiver.try_recv() {
|
||||
|
@ -1242,10 +1182,10 @@ fn receive_batch(
|
|||
fn filter_items(
|
||||
_run_id: &str,
|
||||
input: &str,
|
||||
items: Vec<PaletteItem>,
|
||||
items: im::Vector<PaletteItem>,
|
||||
matcher: &SkimMatcherV2,
|
||||
) -> Vec<PaletteItem> {
|
||||
let mut items: Vec<PaletteItem> = items
|
||||
) -> im::Vector<PaletteItem> {
|
||||
let mut items: im::Vector<PaletteItem> = items
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
if let Some((score, indices)) =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::Display;
|
||||
use std::{fmt::Display, sync::Arc};
|
||||
|
||||
use anyhow::Error;
|
||||
use druid::{
|
||||
|
@ -14,6 +14,7 @@
|
|||
completion::{CompletionData, CompletionStatus, ScoredCompletionItem},
|
||||
config::{Config, LapceTheme},
|
||||
data::LapceTabData,
|
||||
list::ListData,
|
||||
markdown::parse_markdown,
|
||||
rich_text::{RichText, RichTextBuilder},
|
||||
};
|
||||
|
@ -22,6 +23,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
list::{List, ListPaint},
|
||||
scroll::{LapceIdentityWrapper, LapceScroll},
|
||||
svg::completion_svg,
|
||||
};
|
||||
|
@ -182,8 +184,8 @@ pub struct CompletionContainer {
|
|||
id: WidgetId,
|
||||
scroll_id: WidgetId,
|
||||
completion: WidgetPod<
|
||||
LapceTabData,
|
||||
LapceIdentityWrapper<LapceScroll<LapceTabData, Completion>>,
|
||||
ListData<ScoredCompletionItem, ()>,
|
||||
List<ScoredCompletionItem, ()>,
|
||||
>,
|
||||
completion_content_size: Size,
|
||||
documentation: WidgetPod<
|
||||
|
@ -195,10 +197,7 @@ pub struct CompletionContainer {
|
|||
|
||||
impl CompletionContainer {
|
||||
pub fn new(data: &CompletionData) -> Self {
|
||||
let completion = LapceIdentityWrapper::wrap(
|
||||
LapceScroll::new(Completion::new()).vertical(),
|
||||
data.scroll_id,
|
||||
);
|
||||
let completion = List::new(data.scroll_id);
|
||||
let completion_doc = LapceIdentityWrapper::wrap(
|
||||
LapceScroll::new(CompletionDocumentation::new()).vertical(),
|
||||
data.documentation_scroll_id,
|
||||
|
@ -220,19 +219,15 @@ pub fn ensure_item_visible(
|
|||
env: &Env,
|
||||
) {
|
||||
let width = ctx.size().width;
|
||||
let line_height = data.config.editor.line_height() as f64;
|
||||
let line_height = data.completion.completion_list.line_height() as f64;
|
||||
|
||||
let rect = Size::new(width, line_height)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(
|
||||
0.0,
|
||||
data.completion.index as f64 * line_height,
|
||||
data.completion.completion_list.selected_index as f64 * line_height,
|
||||
));
|
||||
if self
|
||||
.completion
|
||||
.widget_mut()
|
||||
.inner_mut()
|
||||
.scroll_to_visible(rect, env)
|
||||
{
|
||||
if self.completion.widget_mut().scroll_to_visible(rect, env) {
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ResetFade,
|
||||
|
@ -248,9 +243,12 @@ pub fn ensure_item_top_visible(
|
|||
ctx: &mut EventCtx,
|
||||
data: &LapceTabData,
|
||||
) {
|
||||
let line_height = data.config.editor.line_height() as f64;
|
||||
let point = Point::new(0.0, data.completion.index as f64 * line_height);
|
||||
if self.completion.widget_mut().inner_mut().scroll_to(point) {
|
||||
let line_height = data.completion.completion_list.line_height() as f64;
|
||||
let point = Point::new(
|
||||
0.0,
|
||||
data.completion.completion_list.selected_index as f64 * line_height,
|
||||
);
|
||||
if self.completion.widget_mut().scroll_to(point) {
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ResetFade,
|
||||
|
@ -266,7 +264,8 @@ fn update_documentation(&mut self, data: &LapceTabData) {
|
|||
|
||||
let documentation = if data.config.editor.completion_show_documentation {
|
||||
let current_item = (!data.completion.is_empty())
|
||||
.then(|| data.completion.current_item());
|
||||
.then(|| data.completion.current_item())
|
||||
.flatten();
|
||||
|
||||
current_item.and_then(|item| item.item.documentation.as_ref())
|
||||
} else {
|
||||
|
@ -308,7 +307,35 @@ fn event(
|
|||
data: &mut LapceTabData,
|
||||
env: &Env,
|
||||
) {
|
||||
self.completion.event(ctx, event, data, env);
|
||||
match event {
|
||||
Event::Command(cmd) if cmd.is(LAPCE_UI_COMMAND) => {
|
||||
let command = cmd.get_unchecked(LAPCE_UI_COMMAND);
|
||||
if let LapceUICommand::ListItemSelected = command {
|
||||
if let Some(editor) = data
|
||||
.main_split
|
||||
.active
|
||||
.and_then(|active| data.main_split.editors.get(&active))
|
||||
.cloned()
|
||||
{
|
||||
let mut editor_data =
|
||||
data.editor_view_content(editor.view_id);
|
||||
let doc = editor_data.doc.clone();
|
||||
editor_data.completion_item_select(ctx);
|
||||
data.update_from_editor_buffer_data(
|
||||
editor_data,
|
||||
&editor,
|
||||
&doc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let completion = Arc::make_mut(&mut data.completion);
|
||||
completion.completion_list.update_data(data.config.clone());
|
||||
self.completion
|
||||
.event(ctx, event, &mut completion.completion_list, env);
|
||||
self.documentation.event(ctx, event, data, env);
|
||||
}
|
||||
|
||||
|
@ -319,7 +346,15 @@ fn lifecycle(
|
|||
data: &LapceTabData,
|
||||
env: &Env,
|
||||
) {
|
||||
self.completion.lifecycle(ctx, event, data, env);
|
||||
self.completion.lifecycle(
|
||||
ctx,
|
||||
event,
|
||||
&data
|
||||
.completion
|
||||
.completion_list
|
||||
.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
self.documentation.lifecycle(ctx, event, data, env);
|
||||
}
|
||||
|
||||
|
@ -353,14 +388,8 @@ fn update(
|
|||
if old_data.completion.input != data.completion.input
|
||||
|| old_data.completion.request_id != data.completion.request_id
|
||||
|| old_data.completion.status != data.completion.status
|
||||
|| !old_data
|
||||
.completion
|
||||
.current_items()
|
||||
.same(data.completion.current_items())
|
||||
|| !old_data
|
||||
.completion
|
||||
.filtered_items
|
||||
.same(&data.completion.filtered_items)
|
||||
|| old_data.completion.completion_list.items
|
||||
!= data.completion.completion_list.items
|
||||
{
|
||||
self.update_documentation(data);
|
||||
ctx.request_layout();
|
||||
|
@ -377,7 +406,9 @@ fn update(
|
|||
));
|
||||
}
|
||||
|
||||
if old_completion.index != completion.index {
|
||||
if old_completion.completion_list.selected_index
|
||||
!= completion.completion_list.selected_index
|
||||
{
|
||||
self.ensure_item_visible(ctx, data, env);
|
||||
self.update_documentation(data);
|
||||
ctx.request_paint();
|
||||
|
@ -393,20 +424,38 @@ fn update(
|
|||
{
|
||||
ctx.request_layout();
|
||||
}
|
||||
|
||||
self.completion.update(
|
||||
ctx,
|
||||
&data
|
||||
.completion
|
||||
.completion_list
|
||||
.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
ctx: &mut LayoutCtx,
|
||||
_bc: &BoxConstraints,
|
||||
bc: &BoxConstraints,
|
||||
data: &LapceTabData,
|
||||
env: &Env,
|
||||
) -> Size {
|
||||
let completion_size = data.completion.size;
|
||||
let bc = BoxConstraints::new(Size::ZERO, completion_size);
|
||||
self.completion_content_size = self.completion.layout(ctx, &bc, data, env);
|
||||
self.completion.set_origin(ctx, data, env, Point::ZERO);
|
||||
// TODO: Let this be configurable
|
||||
let width = 400.0;
|
||||
|
||||
let bc = BoxConstraints::tight(Size::new(width, bc.max().height));
|
||||
|
||||
let completion_list = data
|
||||
.completion
|
||||
.completion_list
|
||||
.clone_with(data.config.clone());
|
||||
self.completion_content_size =
|
||||
self.completion.layout(ctx, &bc, &completion_list, env);
|
||||
self.completion
|
||||
.set_origin(ctx, &completion_list, env, Point::ZERO);
|
||||
|
||||
// Position the documentation over the current completion item to the right
|
||||
let documentation_size = data.completion.documentation_size;
|
||||
|
@ -423,8 +472,10 @@ fn layout(
|
|||
ctx.set_paint_insets((10.0, 10.0, 10.0, 10.0));
|
||||
|
||||
Size::new(
|
||||
completion_size.width + documentation_size.width,
|
||||
completion_size.height.max(documentation_size.height),
|
||||
self.completion_content_size.width + documentation_size.width,
|
||||
self.completion_content_size
|
||||
.height
|
||||
.max(documentation_size.height),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -433,6 +484,15 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
|
|||
&& data.completion.len() > 0
|
||||
{
|
||||
let rect = self.completion_content_size.to_rect();
|
||||
|
||||
// Draw the background
|
||||
ctx.fill(
|
||||
rect,
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::COMPLETION_BACKGROUND),
|
||||
);
|
||||
|
||||
// Draw the shadow
|
||||
let shadow_width = data.config.ui.drop_shadow_width() as f64;
|
||||
if shadow_width > 0.0 {
|
||||
ctx.blurred_rect(
|
||||
|
@ -448,160 +508,91 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
|
|||
1.0,
|
||||
);
|
||||
}
|
||||
self.completion.paint(ctx, data, env);
|
||||
|
||||
// Draw the completion list over the background
|
||||
self.completion.paint(
|
||||
ctx,
|
||||
&data
|
||||
.completion
|
||||
.completion_list
|
||||
.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
|
||||
// Draw the documentation to the side
|
||||
self.documentation.paint(ctx, data, env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Completion {}
|
||||
|
||||
impl Completion {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Completion {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget<LapceTabData> for Completion {
|
||||
fn event(
|
||||
&mut self,
|
||||
_ctx: &mut EventCtx,
|
||||
_event: &Event,
|
||||
_data: &mut LapceTabData,
|
||||
impl<D: Data> ListPaint<D> for ScoredCompletionItem {
|
||||
fn paint(
|
||||
&self,
|
||||
ctx: &mut PaintCtx,
|
||||
data: &ListData<Self, D>,
|
||||
_env: &Env,
|
||||
line: usize,
|
||||
) {
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
_ctx: &mut LifeCycleCtx,
|
||||
_event: &LifeCycle,
|
||||
_data: &LapceTabData,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_ctx: &mut UpdateCtx,
|
||||
_old_data: &LapceTabData,
|
||||
_data: &LapceTabData,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_ctx: &mut LayoutCtx,
|
||||
bc: &BoxConstraints,
|
||||
data: &LapceTabData,
|
||||
_env: &Env,
|
||||
) -> Size {
|
||||
let line_height = data.config.editor.line_height() as f64;
|
||||
let height = data.completion.len();
|
||||
let height = height as f64 * line_height;
|
||||
Size::new(bc.max().width, height)
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, _env: &Env) {
|
||||
if data.completion.status == CompletionStatus::Inactive {
|
||||
return;
|
||||
}
|
||||
let line_height = data.config.editor.line_height() as f64;
|
||||
let rect = ctx.region().bounding_box();
|
||||
let size = ctx.size();
|
||||
|
||||
let _input = &data.completion.input;
|
||||
let items: &Vec<ScoredCompletionItem> = data.completion.current_items();
|
||||
|
||||
ctx.fill(
|
||||
rect,
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::COMPLETION_BACKGROUND),
|
||||
);
|
||||
|
||||
let start_line = (rect.y0 / line_height).floor() as usize;
|
||||
let end_line = (rect.y1 / line_height).ceil() as usize;
|
||||
|
||||
for line in start_line..end_line {
|
||||
if line >= items.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
if line == data.completion.index {
|
||||
ctx.fill(
|
||||
Rect::ZERO
|
||||
.with_origin(Point::new(0.0, line as f64 * line_height))
|
||||
.with_size(Size::new(size.width, line_height)),
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::COMPLETION_CURRENT),
|
||||
);
|
||||
}
|
||||
|
||||
let item = &items[line];
|
||||
|
||||
if let Some((svg, color)) = completion_svg(item.item.kind, &data.config)
|
||||
{
|
||||
let color = color.unwrap_or_else(|| {
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone()
|
||||
});
|
||||
let rect = Size::new(line_height, line_height)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(0.0, line_height * line as f64));
|
||||
ctx.fill(rect, &color.clone().with_alpha(0.3));
|
||||
|
||||
let width = 16.0;
|
||||
let height = 16.0;
|
||||
let rect =
|
||||
Size::new(width, height).to_rect().with_origin(Point::new(
|
||||
(line_height - width) / 2.0,
|
||||
(line_height - height) / 2.0 + line_height * line as f64,
|
||||
));
|
||||
ctx.draw_svg(&svg, rect, Some(&color));
|
||||
}
|
||||
|
||||
let focus_color =
|
||||
data.config.get_color_unchecked(LapceTheme::EDITOR_FOCUS);
|
||||
let content = item.item.label.as_str();
|
||||
|
||||
let mut text_layout = ctx
|
||||
.text()
|
||||
.new_text_layout(content.to_string())
|
||||
.font(
|
||||
FontFamily::new_unchecked(
|
||||
data.config.editor.font_family.clone(),
|
||||
),
|
||||
data.config.editor.font_size as f64,
|
||||
)
|
||||
.text_color(
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone(),
|
||||
);
|
||||
for i in &item.indices {
|
||||
let i = *i;
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::Weight(FontWeight::BOLD),
|
||||
);
|
||||
}
|
||||
let text_layout = text_layout.build().unwrap();
|
||||
let y = line_height * line as f64 + text_layout.y_offset(line_height);
|
||||
let point = Point::new(line_height + 5.0, y);
|
||||
ctx.draw_text(&text_layout, point);
|
||||
let line_height = data.line_height() as f64;
|
||||
if line == data.selected_index {
|
||||
ctx.fill(
|
||||
Rect::ZERO
|
||||
.with_origin(Point::new(0.0, line as f64 * line_height))
|
||||
.with_size(Size::new(size.width, line_height)),
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::COMPLETION_CURRENT),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((svg, color)) = completion_svg(self.item.kind, &data.config) {
|
||||
let color = color.unwrap_or_else(|| {
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone()
|
||||
});
|
||||
let rect = Size::new(line_height, line_height)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(0.0, line_height * line as f64));
|
||||
ctx.fill(rect, &color.clone().with_alpha(0.3));
|
||||
|
||||
let width = 16.0;
|
||||
let height = 16.0;
|
||||
let rect = Size::new(width, height).to_rect().with_origin(Point::new(
|
||||
(line_height - width) / 2.0,
|
||||
(line_height - height) / 2.0 + line_height * line as f64,
|
||||
));
|
||||
ctx.draw_svg(&svg, rect, Some(&color));
|
||||
}
|
||||
|
||||
let focus_color = data.config.get_color_unchecked(LapceTheme::EDITOR_FOCUS);
|
||||
let content = self.item.label.as_str();
|
||||
|
||||
let mut text_layout = ctx
|
||||
.text()
|
||||
.new_text_layout(content.to_string())
|
||||
.font(
|
||||
FontFamily::new_unchecked(data.config.editor.font_family.clone()),
|
||||
data.config.editor.font_size as f64,
|
||||
)
|
||||
.text_color(
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone(),
|
||||
);
|
||||
for i in &self.indices {
|
||||
let i = *i;
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout
|
||||
.range_attribute(i..i + 1, TextAttribute::Weight(FontWeight::BOLD));
|
||||
}
|
||||
let text_layout = text_layout.build().unwrap();
|
||||
let y = line_height * line as f64 + text_layout.y_offset(line_height);
|
||||
let point = Point::new(line_height + 5.0, y);
|
||||
ctx.draw_text(&text_layout, point);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
pub mod find;
|
||||
pub mod hover;
|
||||
pub mod keymap;
|
||||
pub mod list;
|
||||
mod logging;
|
||||
pub mod palette;
|
||||
pub mod panel;
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use druid::{
|
||||
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
|
||||
PaintCtx, Point, Rect, RenderContext, Size, UpdateCtx, Widget, WidgetId,
|
||||
WidgetPod,
|
||||
};
|
||||
use lapce_data::{config::LapceTheme, list::ListData};
|
||||
|
||||
use crate::scroll::{LapceIdentityWrapper, LapceScroll};
|
||||
|
||||
pub struct DropdownButton<T: Clone + ListPaint<D> + PartialEq + 'static, D: Data> {
|
||||
pub list: WidgetPod<ListData<T, D>, List<T, D>>,
|
||||
}
|
||||
|
||||
// TODO: Support optional multi-select
|
||||
|
||||
/// Contains a list of choices of type `T`
|
||||
/// The type must be cloneable, list-paintable, and able to be compared.
|
||||
/// `D` is associated data that users of `List` may need, since the painting is only given
|
||||
/// `&ListData<T, D>` for use.
|
||||
///
|
||||
/// We let the `KeyPressFocus` be handled by the containing widget, since widgets like palette
|
||||
/// want focus for themselves. You can call `ListData::run_command` to use its sensible
|
||||
/// defaults for movement and the like.
|
||||
pub struct List<T: Clone + ListPaint<D> + PartialEq + 'static, D: Data> {
|
||||
content_rect: Rect,
|
||||
// I don't see a way to break this apart that doesn't make it less clear
|
||||
#[allow(clippy::type_complexity)]
|
||||
content: WidgetPod<
|
||||
ListData<T, D>,
|
||||
LapceIdentityWrapper<LapceScroll<ListData<T, D>, ListContent<T, D>>>,
|
||||
>,
|
||||
}
|
||||
impl<T: Clone + ListPaint<D> + PartialEq + 'static, D: Data> List<T, D> {
|
||||
pub fn new(scroll_id: WidgetId) -> List<T, D> {
|
||||
let content = LapceIdentityWrapper::wrap(
|
||||
LapceScroll::new(ListContent::new()).vertical(),
|
||||
scroll_id,
|
||||
);
|
||||
List {
|
||||
content_rect: Rect::ZERO,
|
||||
content: WidgetPod::new(content),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_to(&mut self, point: Point) -> bool {
|
||||
self.content.widget_mut().inner_mut().scroll_to(point)
|
||||
}
|
||||
|
||||
pub fn scroll_to_visible(&mut self, rect: Rect, env: &Env) -> bool {
|
||||
self.content
|
||||
.widget_mut()
|
||||
.inner_mut()
|
||||
.scroll_to_visible(rect, env)
|
||||
}
|
||||
}
|
||||
impl<T: Clone + ListPaint<D> + PartialEq + 'static, D: Data> Widget<ListData<T, D>>
|
||||
for List<T, D>
|
||||
{
|
||||
fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
event: &Event,
|
||||
data: &mut ListData<T, D>,
|
||||
env: &Env,
|
||||
) {
|
||||
self.content.event(ctx, event, data, env);
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
ctx: &mut LifeCycleCtx,
|
||||
event: &LifeCycle,
|
||||
data: &ListData<T, D>,
|
||||
env: &Env,
|
||||
) {
|
||||
self.content.lifecycle(ctx, event, data, env);
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
ctx: &mut UpdateCtx,
|
||||
_old_data: &ListData<T, D>,
|
||||
data: &ListData<T, D>,
|
||||
env: &Env,
|
||||
) {
|
||||
self.content.update(ctx, data, env);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
ctx: &mut LayoutCtx,
|
||||
bc: &BoxConstraints,
|
||||
data: &ListData<T, D>,
|
||||
env: &Env,
|
||||
) -> Size {
|
||||
// TODO: Allow restricting the max height? Technically that can just be done by restricting the
|
||||
// maximum number of displayed items
|
||||
|
||||
// The width are given by whatever widget contains the list
|
||||
let width = bc.max().width;
|
||||
|
||||
let line_height = data.line_height() as f64;
|
||||
|
||||
let count = data.max_display_count();
|
||||
// The height of the rendered entries
|
||||
let height = count as f64 * line_height;
|
||||
|
||||
// TODO: Have an option to let us fill the rest of the space with empty background
|
||||
// Since some lists may want to take up all the space they're given
|
||||
|
||||
// Create a bc which only contains the list we're actually rendering
|
||||
let bc = BoxConstraints::tight(Size::new(width, height));
|
||||
|
||||
let content_size = self.content.layout(ctx, &bc, data, env);
|
||||
self.content.set_origin(ctx, data, env, Point::ZERO);
|
||||
let mut content_height = content_size.height;
|
||||
// Add padding to the bottom
|
||||
if content_height > 0.0 {
|
||||
content_height += 5.0;
|
||||
}
|
||||
|
||||
let self_size = Size::new(content_size.width, content_height);
|
||||
|
||||
self.content_rect = self_size.to_rect().with_origin(Point::ZERO);
|
||||
|
||||
self_size
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut PaintCtx, data: &ListData<T, D>, env: &Env) {
|
||||
let rect = self.content_rect;
|
||||
// TODO: Put whether shadows should appear on the list behind a variable
|
||||
let shadow_width = data.config.ui.drop_shadow_width() as f64;
|
||||
if shadow_width > 0.0 {
|
||||
ctx.blurred_rect(
|
||||
rect,
|
||||
shadow_width,
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::LAPCE_DROPDOWN_SHADOW),
|
||||
);
|
||||
} else {
|
||||
ctx.stroke(
|
||||
rect.inflate(0.5, 0.5),
|
||||
data.config.get_color_unchecked(LapceTheme::LAPCE_BORDER),
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
// TODO: We could have the user of this provide a custom color
|
||||
// Or we could say that they have to fill it? Since something like
|
||||
// palette also wants to draw their background behind the other bits
|
||||
// and so, if we painted here, we'd double-paint
|
||||
// which would be annoying for transparent colors.
|
||||
// ctx.fill(
|
||||
// rect,
|
||||
// data.config
|
||||
// .get_color_unchecked(LapceTheme::PALETTE_BACKGROUND),
|
||||
// );
|
||||
|
||||
self.content.paint(ctx, data, env);
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual list of entries
|
||||
struct ListContent<T: Clone + ListPaint<D> + 'static, D: Data> {
|
||||
/// The line the mouse was last down upon
|
||||
mouse_down: usize,
|
||||
_marker: PhantomData<(*const T, *const D)>,
|
||||
}
|
||||
impl<T: Clone + ListPaint<D> + 'static, D: Data> ListContent<T, D> {
|
||||
pub fn new() -> ListContent<T, D> {
|
||||
ListContent {
|
||||
mouse_down: 0,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Clone + ListPaint<D> + 'static, D: Data> Widget<ListData<T, D>>
|
||||
for ListContent<T, D>
|
||||
{
|
||||
fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
event: &Event,
|
||||
data: &mut ListData<T, D>,
|
||||
_env: &Env,
|
||||
) {
|
||||
match event {
|
||||
Event::MouseMove(_) => {
|
||||
ctx.set_cursor(&druid::Cursor::Pointer);
|
||||
ctx.set_handled();
|
||||
}
|
||||
Event::MouseDown(mouse_event) => {
|
||||
let line =
|
||||
(mouse_event.pos.y / data.line_height() as f64).floor() as usize;
|
||||
self.mouse_down = line;
|
||||
ctx.set_handled();
|
||||
}
|
||||
Event::MouseUp(mouse_event) => {
|
||||
// TODO: function for translating mouse pos to the line, so that we don't repeat
|
||||
// this calculation; which makes it harder to change later
|
||||
let line =
|
||||
(mouse_event.pos.y / data.line_height() as f64).floor() as usize;
|
||||
if line == self.mouse_down {
|
||||
data.selected_index = line;
|
||||
data.select(ctx);
|
||||
}
|
||||
ctx.set_handled();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
_ctx: &mut LifeCycleCtx,
|
||||
_event: &LifeCycle,
|
||||
_data: &ListData<T, D>,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_ctx: &mut UpdateCtx,
|
||||
_old_data: &ListData<T, D>,
|
||||
_data: &ListData<T, D>,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_ctx: &mut LayoutCtx,
|
||||
bc: &BoxConstraints,
|
||||
data: &ListData<T, D>,
|
||||
_env: &Env,
|
||||
) -> Size {
|
||||
let line_height = data.line_height() as f64;
|
||||
// We include the total number of items because we should be in a scroll widget
|
||||
// and that needs the overall height, not just the rendered height.
|
||||
let height = line_height * data.items.len() as f64;
|
||||
|
||||
Size::new(bc.max().width, height)
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut PaintCtx, data: &ListData<T, D>, env: &Env) {
|
||||
let rect = ctx.region().bounding_box();
|
||||
let size = ctx.size();
|
||||
|
||||
let line_height = data.line_height() as f64;
|
||||
|
||||
let start_line = (rect.y0 / line_height).floor() as usize;
|
||||
let end_line = (rect.y1 / line_height).ceil() as usize;
|
||||
let count = end_line - start_line;
|
||||
|
||||
// Get the items, skip over all items before the start line, and
|
||||
// ignore all items after the end line
|
||||
for (line, item) in
|
||||
data.items.iter().enumerate().skip(start_line).take(count)
|
||||
{
|
||||
if line == data.selected_index {
|
||||
// Create a rect covering the entry at the selected index
|
||||
let bg_rect = Rect::ZERO
|
||||
.with_origin(Point::new(0.0, line as f64 * line_height))
|
||||
.with_size(Size::new(size.width, line_height));
|
||||
|
||||
// TODO: Give this its own theme name entry
|
||||
ctx.fill(
|
||||
bg_rect,
|
||||
data.config.get_color_unchecked(LapceTheme::PALETTE_CURRENT),
|
||||
);
|
||||
}
|
||||
|
||||
item.paint(ctx, data, env, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for painting relatively simple elements that are put in a list
|
||||
/// They don't get a say in their layout or custom handling of events
|
||||
///
|
||||
/// Takes an immutable reference, due to `data` containing this entry
|
||||
pub trait ListPaint<D: Data>: Sized + Clone {
|
||||
fn paint(
|
||||
&self,
|
||||
ctx: &mut PaintCtx,
|
||||
data: &ListData<Self, D>,
|
||||
env: &Env,
|
||||
line: usize,
|
||||
);
|
||||
}
|
|
@ -12,21 +12,21 @@
|
|||
};
|
||||
use druid::{FontWeight, Modifiers};
|
||||
use lapce_data::command::LAPCE_COMMAND;
|
||||
use lapce_data::config::Config;
|
||||
use lapce_data::data::LapceWorkspaceType;
|
||||
use lapce_data::palette::{PaletteItemContent, MAX_PALETTE_ITEMS};
|
||||
use lapce_data::list::ListData;
|
||||
use lapce_data::palette::{PaletteItem, PaletteItemContent, PaletteListData};
|
||||
use lapce_data::{
|
||||
command::{LapceUICommand, LAPCE_UI_COMMAND},
|
||||
config::LapceTheme,
|
||||
data::LapceTabData,
|
||||
keypress::KeyPressFocus,
|
||||
palette::{PaletteStatus, PaletteType, PaletteViewData, PaletteViewLens},
|
||||
palette::{PaletteStatus, PaletteType, PaletteViewData},
|
||||
};
|
||||
use lsp_types::SymbolKind;
|
||||
|
||||
use crate::list::{List, ListPaint};
|
||||
use crate::{
|
||||
editor::view::LapceEditorView,
|
||||
scroll::{LapceIdentityWrapper, LapceScroll},
|
||||
svg::{file_svg, symbol_svg},
|
||||
};
|
||||
|
||||
|
@ -135,13 +135,16 @@ fn event(
|
|||
LapceUICommand::UpdatePaletteItems(run_id, items) => {
|
||||
let palette = Arc::make_mut(&mut data.palette);
|
||||
if &palette.run_id == run_id {
|
||||
palette.items = items.to_owned();
|
||||
palette.total_items = items.clone();
|
||||
palette.preview(ctx);
|
||||
if palette.get_input() != "" {
|
||||
if palette.get_input() == "" {
|
||||
palette.list_data.items =
|
||||
palette.total_items.clone();
|
||||
} else {
|
||||
let _ = palette.sender.send((
|
||||
palette.run_id.clone(),
|
||||
palette.get_input().to_string(),
|
||||
palette.items.clone(),
|
||||
palette.total_items.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +157,13 @@ fn event(
|
|||
let palette = Arc::make_mut(&mut data.palette);
|
||||
if &palette.run_id == run_id && palette.get_input() == input
|
||||
{
|
||||
palette.filtered_items = filtered_items.to_owned();
|
||||
palette.list_data.items = filtered_items.clone();
|
||||
palette.preview(ctx);
|
||||
}
|
||||
}
|
||||
LapceUICommand::ListItemSelected => {
|
||||
data.palette_view_data().select(ctx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -215,14 +221,11 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
|
|||
|
||||
struct PaletteContainer {
|
||||
content_rect: Rect,
|
||||
line_height: f64,
|
||||
input: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
content: WidgetPod<
|
||||
LapceTabData,
|
||||
LapceIdentityWrapper<
|
||||
LapceScroll<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
>,
|
||||
ListData<PaletteItem, PaletteListData>,
|
||||
List<PaletteItem, PaletteListData>,
|
||||
>,
|
||||
preview: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
}
|
||||
|
@ -239,11 +242,7 @@ pub fn new(data: &LapceTabData) -> Self {
|
|||
.hide_header()
|
||||
.hide_gutter()
|
||||
.padding((10.0, 5.0, 10.0, 5.0));
|
||||
let content = LapceIdentityWrapper::wrap(
|
||||
LapceScroll::new(PaletteContent::new().lens(PaletteViewLens).boxed())
|
||||
.vertical(),
|
||||
data.palette.scroll_id,
|
||||
);
|
||||
let content = List::new(data.palette.scroll_id);
|
||||
let preview =
|
||||
LapceEditorView::new(preview_editor.view_id, WidgetId::next(), None);
|
||||
Self {
|
||||
|
@ -251,7 +250,6 @@ pub fn new(data: &LapceTabData) -> Self {
|
|||
input: WidgetPod::new(input.boxed()),
|
||||
content: WidgetPod::new(content),
|
||||
preview: WidgetPod::new(preview.boxed()),
|
||||
line_height: 25.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,19 +260,17 @@ fn ensure_item_visible(
|
|||
env: &Env,
|
||||
) {
|
||||
let width = ctx.size().width;
|
||||
// TODO: This function could just be on List?
|
||||
let line_height = data.palette.list_data.line_height() as f64;
|
||||
let rect =
|
||||
Size::new(width, self.line_height)
|
||||
Size::new(width, line_height as f64)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(
|
||||
0.0,
|
||||
data.palette.index as f64 * self.line_height,
|
||||
data.palette.list_data.selected_index as f64
|
||||
* line_height as f64,
|
||||
));
|
||||
if self
|
||||
.content
|
||||
.widget_mut()
|
||||
.inner_mut()
|
||||
.scroll_to_visible(rect, env)
|
||||
{
|
||||
if self.content.widget_mut().scroll_to_visible(rect, env) {
|
||||
ctx.submit_command(Command::new(
|
||||
LAPCE_UI_COMMAND,
|
||||
LapceUICommand::ResetFade,
|
||||
|
@ -293,7 +289,11 @@ fn event(
|
|||
env: &Env,
|
||||
) {
|
||||
self.input.event(ctx, event, data, env);
|
||||
self.content.event(ctx, event, data, env);
|
||||
|
||||
let palette = Arc::make_mut(&mut data.palette);
|
||||
palette.list_data.update_data(data.config.clone());
|
||||
self.content.event(ctx, event, &mut palette.list_data, env);
|
||||
|
||||
self.preview.event(ctx, event, data, env);
|
||||
}
|
||||
|
||||
|
@ -305,7 +305,12 @@ fn lifecycle(
|
|||
env: &Env,
|
||||
) {
|
||||
self.input.lifecycle(ctx, event, data, env);
|
||||
self.content.lifecycle(ctx, event, data, env);
|
||||
self.content.lifecycle(
|
||||
ctx,
|
||||
event,
|
||||
&data.palette.list_data.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
self.preview.lifecycle(ctx, event, data, env);
|
||||
}
|
||||
|
||||
|
@ -317,13 +322,18 @@ fn update(
|
|||
env: &Env,
|
||||
) {
|
||||
if old_data.palette.input != data.palette.input
|
||||
|| old_data.palette.index != data.palette.index
|
||||
|| old_data.palette.list_data.selected_index
|
||||
!= data.palette.list_data.selected_index
|
||||
|| old_data.palette.run_id != data.palette.run_id
|
||||
{
|
||||
self.ensure_item_visible(ctx, data, env);
|
||||
}
|
||||
self.input.update(ctx, data, env);
|
||||
self.content.update(ctx, data, env);
|
||||
self.content.update(
|
||||
ctx,
|
||||
&data.palette.list_data.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
self.preview.update(ctx, data, env);
|
||||
}
|
||||
|
||||
|
@ -337,28 +347,29 @@ fn layout(
|
|||
let width = bc.max().width;
|
||||
let max_height = bc.max().height;
|
||||
|
||||
let bc = BoxConstraints::tight(Size::new(width, bc.max().height));
|
||||
let bc = BoxConstraints::tight(Size::new(width, max_height));
|
||||
|
||||
let input_size = self.input.layout(ctx, &bc, data, env);
|
||||
self.input.set_origin(ctx, data, env, Point::ZERO);
|
||||
|
||||
let height = MAX_PALETTE_ITEMS.min(data.palette.len());
|
||||
let height = self.line_height * height as f64;
|
||||
let height = max_height - input_size.height;
|
||||
let bc = BoxConstraints::tight(Size::new(width, height));
|
||||
let content_size = self.content.layout(ctx, &bc, data, env);
|
||||
self.content
|
||||
.set_origin(ctx, data, env, Point::new(0.0, input_size.height));
|
||||
let mut content_height = content_size.height;
|
||||
if content_height > 0.0 {
|
||||
content_height += 5.0;
|
||||
}
|
||||
let content_size =
|
||||
self.content.layout(ctx, &bc, &data.palette.list_data, env);
|
||||
self.content.set_origin(
|
||||
ctx,
|
||||
&data.palette.list_data.clone_with(data.config.clone()),
|
||||
env,
|
||||
Point::new(0.0, input_size.height),
|
||||
);
|
||||
|
||||
let max_preview_height = max_height
|
||||
- input_size.height
|
||||
- MAX_PALETTE_ITEMS as f64 * self.line_height
|
||||
- data.palette.list_data.max_displayed_items as f64
|
||||
* data.palette.list_data.line_height() as f64
|
||||
- 5.0;
|
||||
let preview_height = if data.palette.palette_type.has_preview() {
|
||||
if content_height > 0.0 {
|
||||
if content_size.height > 0.0 {
|
||||
max_preview_height
|
||||
} else {
|
||||
0.0
|
||||
|
@ -380,13 +391,15 @@ fn layout(
|
|||
ctx,
|
||||
data,
|
||||
env,
|
||||
Point::new(preview_width / 2.0, input_size.height + content_height),
|
||||
Point::new(preview_width / 2.0, input_size.height + content_size.height),
|
||||
);
|
||||
|
||||
ctx.set_paint_insets(4000.0);
|
||||
|
||||
let self_size =
|
||||
Size::new(width, input_size.height + content_height + preview_height);
|
||||
let self_size = Size::new(
|
||||
width,
|
||||
input_size.height + content_size.height + preview_height,
|
||||
);
|
||||
self.content_rect = Size::new(width, self_size.height)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(0.0, 0.0));
|
||||
|
@ -424,11 +437,13 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &LapceTabData, env: &Env) {
|
|||
return;
|
||||
}
|
||||
|
||||
self.content.paint(ctx, data, env);
|
||||
self.content.paint(
|
||||
ctx,
|
||||
&data.palette.list_data.clone_with(data.config.clone()),
|
||||
env,
|
||||
);
|
||||
|
||||
if !data.palette.current_items().is_empty()
|
||||
&& data.palette.palette_type.has_preview()
|
||||
{
|
||||
if !data.palette.is_empty() && data.palette.palette_type.has_preview() {
|
||||
let rect = self.preview.layout_rect();
|
||||
ctx.fill(
|
||||
rect,
|
||||
|
@ -544,420 +559,6 @@ fn paint(&mut self, ctx: &mut PaintCtx, data: &PaletteViewData, _env: &Env) {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PaletteContent {
|
||||
mouse_down: usize,
|
||||
line_height: f64,
|
||||
}
|
||||
|
||||
impl PaletteContent {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
mouse_down: 0,
|
||||
line_height: 25.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_palette_item(
|
||||
palette_item_content: &PaletteItemContent,
|
||||
ctx: &mut PaintCtx,
|
||||
workspace_path: Option<&Path>,
|
||||
line: usize,
|
||||
indices: &[usize],
|
||||
line_height: f64,
|
||||
config: &Config,
|
||||
) {
|
||||
let (svg, text, text_indices, hint, hint_indices) =
|
||||
match palette_item_content {
|
||||
PaletteItemContent::File(path, _) => {
|
||||
Self::file_paint_items(path, indices)
|
||||
}
|
||||
PaletteItemContent::DocumentSymbol {
|
||||
kind,
|
||||
name,
|
||||
container_name,
|
||||
..
|
||||
} => {
|
||||
let text = name.to_string();
|
||||
let hint =
|
||||
container_name.clone().unwrap_or_else(|| "".to_string());
|
||||
let text_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < text.len() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i >= text.len() {
|
||||
Some(i - text.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(symbol_svg(kind), text, text_indices, hint, hint_indices)
|
||||
}
|
||||
PaletteItemContent::WorkspaceSymbol {
|
||||
kind,
|
||||
name,
|
||||
location,
|
||||
..
|
||||
} => Self::file_paint_symbols(
|
||||
&location.path,
|
||||
indices,
|
||||
workspace_path,
|
||||
name.as_str(),
|
||||
*kind,
|
||||
),
|
||||
PaletteItemContent::Line(_, text) => {
|
||||
(None, text.clone(), indices.to_vec(), "".to_string(), vec![])
|
||||
}
|
||||
PaletteItemContent::ReferenceLocation(rel_path, _location) => {
|
||||
Self::file_paint_items(rel_path, indices)
|
||||
}
|
||||
PaletteItemContent::Workspace(w) => {
|
||||
let text = w.path.as_ref().unwrap().to_str().unwrap();
|
||||
let text = match &w.kind {
|
||||
LapceWorkspaceType::Local => text.to_string(),
|
||||
LapceWorkspaceType::RemoteSSH(user, host) => {
|
||||
format!("[{user}@{host}] {text}")
|
||||
}
|
||||
LapceWorkspaceType::RemoteWSL => {
|
||||
format!("[wsl] {text}")
|
||||
}
|
||||
};
|
||||
(None, text, indices.to_vec(), "".to_string(), vec![])
|
||||
}
|
||||
PaletteItemContent::Command(command) => (
|
||||
None,
|
||||
command
|
||||
.kind
|
||||
.desc()
|
||||
.map(|m| m.to_string())
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
indices.to_vec(),
|
||||
"".to_string(),
|
||||
vec![],
|
||||
),
|
||||
PaletteItemContent::Theme(theme) => (
|
||||
None,
|
||||
theme.to_string(),
|
||||
indices.to_vec(),
|
||||
"".to_string(),
|
||||
vec![],
|
||||
),
|
||||
PaletteItemContent::Language(name) => (
|
||||
None,
|
||||
name.to_string(),
|
||||
indices.to_vec(),
|
||||
"".to_string(),
|
||||
vec![],
|
||||
),
|
||||
PaletteItemContent::TerminalLine(_line, content) => (
|
||||
None,
|
||||
content.clone(),
|
||||
indices.to_vec(),
|
||||
"".to_string(),
|
||||
vec![],
|
||||
),
|
||||
PaletteItemContent::SshHost(user, host) => (
|
||||
None,
|
||||
format!("{user}@{host}"),
|
||||
indices.to_vec(),
|
||||
"".to_string(),
|
||||
vec![],
|
||||
),
|
||||
};
|
||||
|
||||
if let Some(svg) = svg.as_ref() {
|
||||
let width = 14.0;
|
||||
let height = 14.0;
|
||||
let rect = Size::new(width, height).to_rect().with_origin(Point::new(
|
||||
(line_height - width) / 2.0 + 5.0,
|
||||
(line_height - height) / 2.0 + line_height * line as f64,
|
||||
));
|
||||
ctx.draw_svg(svg, rect, None);
|
||||
}
|
||||
|
||||
let svg_x = match palette_item_content {
|
||||
&PaletteItemContent::Line(_, _) | &PaletteItemContent::Workspace(_) => {
|
||||
0.0
|
||||
}
|
||||
_ => line_height,
|
||||
};
|
||||
|
||||
let focus_color = config.get_color_unchecked(LapceTheme::EDITOR_FOCUS);
|
||||
|
||||
let full_text = if hint.is_empty() {
|
||||
text.clone()
|
||||
} else {
|
||||
text.clone() + " " + &hint
|
||||
};
|
||||
let mut text_layout = ctx
|
||||
.text()
|
||||
.new_text_layout(full_text.clone())
|
||||
.font(config.ui.font_family(), config.ui.font_size() as f64)
|
||||
.text_color(
|
||||
config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone(),
|
||||
);
|
||||
for &i_start in &text_indices {
|
||||
let i_end = full_text
|
||||
.char_indices()
|
||||
.find(|(i, _)| *i == i_start)
|
||||
.map(|(_, c)| c.len_utf8() + i_start);
|
||||
let i_end = if let Some(i_end) = i_end {
|
||||
i_end
|
||||
} else {
|
||||
// Log a warning, but continue as we don't want to crash on a bug
|
||||
log::warn!(
|
||||
"Invalid text indices in palette: text: '{}', i_start: {}",
|
||||
text,
|
||||
i_start
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
text_layout = text_layout.range_attribute(
|
||||
i_start..i_end,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout.range_attribute(
|
||||
i_start..i_end,
|
||||
TextAttribute::Weight(FontWeight::BOLD),
|
||||
);
|
||||
}
|
||||
|
||||
if !hint.is_empty() {
|
||||
text_layout = text_layout
|
||||
.range_attribute(
|
||||
text.len() + 1..full_text.len(),
|
||||
TextAttribute::FontSize(13.0),
|
||||
)
|
||||
.range_attribute(
|
||||
text.len() + 1..full_text.len(),
|
||||
TextAttribute::TextColor(
|
||||
config.get_color_unchecked(LapceTheme::EDITOR_DIM).clone(),
|
||||
),
|
||||
);
|
||||
for i in &hint_indices {
|
||||
let i = *i + text.len() + 1;
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::Weight(FontWeight::BOLD),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let text_layout = text_layout.build().unwrap();
|
||||
let x = svg_x + 5.0;
|
||||
let y = line_height * line as f64 + text_layout.y_offset(line_height);
|
||||
let point = Point::new(x, y);
|
||||
ctx.draw_text(&text_layout, point);
|
||||
}
|
||||
|
||||
fn file_paint_symbols(
|
||||
path: &Path,
|
||||
indices: &[usize],
|
||||
workspace_path: Option<&Path>,
|
||||
name: &str,
|
||||
kind: SymbolKind,
|
||||
) -> (Option<Svg>, String, Vec<usize>, String, Vec<usize>) {
|
||||
let text = name.to_string();
|
||||
let hint = path.to_string_lossy();
|
||||
// Remove the workspace prefix from the path
|
||||
let hint = workspace_path
|
||||
.and_then(Path::to_str)
|
||||
.and_then(|x| hint.strip_prefix(x))
|
||||
.map(|x| x.strip_prefix('/').unwrap_or(x))
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| hint.to_string());
|
||||
let text_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < text.len() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i >= text.len() {
|
||||
Some(i - text.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(symbol_svg(&kind), text, text_indices, hint, hint_indices)
|
||||
}
|
||||
|
||||
fn file_paint_items(
|
||||
path: &Path,
|
||||
indices: &[usize],
|
||||
) -> (Option<Svg>, String, Vec<usize>, String, Vec<usize>) {
|
||||
let (svg, _) = file_svg(path);
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let folder = path
|
||||
.parent()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let folder_len = folder.len();
|
||||
let text_indices: Vec<usize> = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if folder_len > 0 {
|
||||
if i > folder_len {
|
||||
Some(i - folder_len - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(i)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices: Vec<usize> = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < folder_len {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(Some(svg), file_name, text_indices, folder, hint_indices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaletteContent {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget<PaletteViewData> for PaletteContent {
|
||||
fn event(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
event: &Event,
|
||||
data: &mut PaletteViewData,
|
||||
_env: &Env,
|
||||
) {
|
||||
match event {
|
||||
Event::MouseMove(_mouse_event) => {
|
||||
ctx.set_cursor(&druid::Cursor::Pointer);
|
||||
}
|
||||
Event::MouseDown(mouse_event) => {
|
||||
let line = (mouse_event.pos.y / self.line_height).floor() as usize;
|
||||
self.mouse_down = line;
|
||||
ctx.set_handled();
|
||||
}
|
||||
Event::MouseUp(mouse_event) => {
|
||||
let line = (mouse_event.pos.y / self.line_height).floor() as usize;
|
||||
if line == self.mouse_down {
|
||||
let palette = Arc::make_mut(&mut data.palette);
|
||||
palette.index = line;
|
||||
data.select(ctx);
|
||||
}
|
||||
ctx.set_handled();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn lifecycle(
|
||||
&mut self,
|
||||
_ctx: &mut LifeCycleCtx,
|
||||
_event: &LifeCycle,
|
||||
_data: &PaletteViewData,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_ctx: &mut UpdateCtx,
|
||||
_old_data: &PaletteViewData,
|
||||
_data: &PaletteViewData,
|
||||
_env: &Env,
|
||||
) {
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_ctx: &mut LayoutCtx,
|
||||
bc: &BoxConstraints,
|
||||
data: &PaletteViewData,
|
||||
_env: &Env,
|
||||
) -> Size {
|
||||
let height = self.line_height * data.palette.len() as f64;
|
||||
Size::new(bc.max().width, height)
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut PaintCtx, data: &PaletteViewData, _env: &Env) {
|
||||
let rect = ctx.region().bounding_box();
|
||||
let size = ctx.size();
|
||||
|
||||
let items = data.palette.current_items();
|
||||
|
||||
let start_line = (rect.y0 / self.line_height).floor() as usize;
|
||||
let end_line = (rect.y1 / self.line_height).ceil() as usize;
|
||||
|
||||
let workspace_path = data.workspace.path.as_deref();
|
||||
for line in start_line..end_line {
|
||||
if line >= items.len() {
|
||||
break;
|
||||
}
|
||||
if line == data.palette.index {
|
||||
ctx.fill(
|
||||
Rect::ZERO
|
||||
.with_origin(Point::new(0.0, line as f64 * self.line_height))
|
||||
.with_size(Size::new(size.width, self.line_height)),
|
||||
data.config.get_color_unchecked(LapceTheme::PALETTE_CURRENT),
|
||||
);
|
||||
}
|
||||
|
||||
let item = &items[line];
|
||||
|
||||
Self::paint_palette_item(
|
||||
&item.content,
|
||||
ctx,
|
||||
workspace_path,
|
||||
line,
|
||||
&item.indices,
|
||||
self.line_height,
|
||||
&data.config,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PalettePreview {}
|
||||
|
||||
impl PalettePreview {
|
||||
|
@ -1016,3 +617,340 @@ fn layout(
|
|||
|
||||
fn paint(&mut self, _ctx: &mut PaintCtx, _data: &PaletteViewData, _env: &Env) {}
|
||||
}
|
||||
|
||||
struct PaletteItemPaintInfo {
|
||||
svg: Option<Svg>,
|
||||
text: String,
|
||||
text_indices: Vec<usize>,
|
||||
hint: String,
|
||||
hint_indices: Vec<usize>,
|
||||
}
|
||||
impl PaletteItemPaintInfo {
|
||||
/// Construct paint info when there is only known text and text indices
|
||||
fn new_text(text: String, text_indices: Vec<usize>) -> PaletteItemPaintInfo {
|
||||
PaletteItemPaintInfo {
|
||||
svg: None,
|
||||
text,
|
||||
text_indices,
|
||||
hint: String::new(),
|
||||
hint_indices: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListPaint<PaletteListData> for PaletteItem {
|
||||
fn paint(
|
||||
&self,
|
||||
ctx: &mut PaintCtx,
|
||||
data: &ListData<Self, PaletteListData>,
|
||||
_env: &Env,
|
||||
line: usize,
|
||||
) {
|
||||
let PaletteItemPaintInfo {
|
||||
svg,
|
||||
text,
|
||||
text_indices,
|
||||
hint,
|
||||
hint_indices,
|
||||
} = match &self.content {
|
||||
PaletteItemContent::File(path, _) => {
|
||||
file_paint_items(path, &self.indices)
|
||||
}
|
||||
PaletteItemContent::DocumentSymbol {
|
||||
kind,
|
||||
name,
|
||||
container_name,
|
||||
..
|
||||
} => {
|
||||
let text = name.to_string();
|
||||
let hint = container_name.clone().unwrap_or_else(|| "".to_string());
|
||||
let text_indices = self
|
||||
.indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < text.len() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices = self
|
||||
.indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i >= text.len() {
|
||||
Some(i - text.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
PaletteItemPaintInfo {
|
||||
svg: symbol_svg(kind),
|
||||
text,
|
||||
text_indices,
|
||||
hint,
|
||||
hint_indices,
|
||||
}
|
||||
}
|
||||
PaletteItemContent::WorkspaceSymbol {
|
||||
kind,
|
||||
name,
|
||||
location,
|
||||
..
|
||||
} => file_paint_symbols(
|
||||
&location.path,
|
||||
&self.indices,
|
||||
data.data
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| workspace.path.as_deref()),
|
||||
name.as_str(),
|
||||
*kind,
|
||||
),
|
||||
PaletteItemContent::Line(_, text) => {
|
||||
PaletteItemPaintInfo::new_text(text.clone(), self.indices.to_vec())
|
||||
}
|
||||
PaletteItemContent::ReferenceLocation(rel_path, _location) => {
|
||||
file_paint_items(rel_path, &self.indices)
|
||||
}
|
||||
PaletteItemContent::Workspace(w) => {
|
||||
let text = w.path.as_ref().unwrap().to_str().unwrap();
|
||||
let text = match &w.kind {
|
||||
LapceWorkspaceType::Local => text.to_string(),
|
||||
LapceWorkspaceType::RemoteSSH(user, host) => {
|
||||
format!("[{user}@{host}] {text}")
|
||||
}
|
||||
LapceWorkspaceType::RemoteWSL => {
|
||||
format!("[wsl] {text}")
|
||||
}
|
||||
};
|
||||
PaletteItemPaintInfo::new_text(text, self.indices.to_vec())
|
||||
}
|
||||
PaletteItemContent::Command(command) => {
|
||||
let text = command
|
||||
.kind
|
||||
.desc()
|
||||
.map(|m| m.to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
PaletteItemPaintInfo::new_text(text, self.indices.to_vec())
|
||||
}
|
||||
PaletteItemContent::Theme(theme) => PaletteItemPaintInfo::new_text(
|
||||
theme.to_string(),
|
||||
self.indices.to_vec(),
|
||||
),
|
||||
PaletteItemContent::Language(name) => PaletteItemPaintInfo::new_text(
|
||||
name.to_string(),
|
||||
self.indices.to_vec(),
|
||||
),
|
||||
PaletteItemContent::TerminalLine(_line, content) => {
|
||||
PaletteItemPaintInfo::new_text(
|
||||
content.clone(),
|
||||
self.indices.to_vec(),
|
||||
)
|
||||
}
|
||||
PaletteItemContent::SshHost(user, host) => {
|
||||
PaletteItemPaintInfo::new_text(
|
||||
format!("{user}@{host}"),
|
||||
self.indices.to_vec(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let line_height = data.line_height() as f64;
|
||||
|
||||
if let Some(svg) = svg.as_ref() {
|
||||
let width = 14.0;
|
||||
let height = 14.0;
|
||||
let rect = Size::new(width, height).to_rect().with_origin(Point::new(
|
||||
(line_height - width) / 2.0 + 5.0,
|
||||
(line_height - height) / 2.0 + line_height * line as f64,
|
||||
));
|
||||
ctx.draw_svg(svg, rect, None);
|
||||
}
|
||||
|
||||
let svg_x = match &self.content {
|
||||
&PaletteItemContent::Line(_, _) | &PaletteItemContent::Workspace(_) => {
|
||||
0.0
|
||||
}
|
||||
_ => line_height,
|
||||
};
|
||||
|
||||
let focus_color = data.config.get_color_unchecked(LapceTheme::EDITOR_FOCUS);
|
||||
|
||||
let full_text = if hint.is_empty() {
|
||||
text.clone()
|
||||
} else {
|
||||
text.clone() + " " + &hint
|
||||
};
|
||||
let mut text_layout = ctx
|
||||
.text()
|
||||
.new_text_layout(full_text.clone())
|
||||
.font(
|
||||
data.config.ui.font_family(),
|
||||
data.config.ui.font_size() as f64,
|
||||
)
|
||||
.text_color(
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_FOREGROUND)
|
||||
.clone(),
|
||||
);
|
||||
for &i_start in &text_indices {
|
||||
let i_end = full_text
|
||||
.char_indices()
|
||||
.find(|(i, _)| *i == i_start)
|
||||
.map(|(_, c)| c.len_utf8() + i_start);
|
||||
let i_end = if let Some(i_end) = i_end {
|
||||
i_end
|
||||
} else {
|
||||
// Log a warning, but continue as we don't want to crash on a bug
|
||||
log::warn!(
|
||||
"Invalid text indices in palette: text: '{}', i_start: {}",
|
||||
text,
|
||||
i_start
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
text_layout = text_layout.range_attribute(
|
||||
i_start..i_end,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout.range_attribute(
|
||||
i_start..i_end,
|
||||
TextAttribute::Weight(FontWeight::BOLD),
|
||||
);
|
||||
}
|
||||
|
||||
if !hint.is_empty() {
|
||||
text_layout = text_layout
|
||||
.range_attribute(
|
||||
text.len() + 1..full_text.len(),
|
||||
TextAttribute::FontSize(13.0),
|
||||
)
|
||||
.range_attribute(
|
||||
text.len() + 1..full_text.len(),
|
||||
TextAttribute::TextColor(
|
||||
data.config
|
||||
.get_color_unchecked(LapceTheme::EDITOR_DIM)
|
||||
.clone(),
|
||||
),
|
||||
);
|
||||
for i in &hint_indices {
|
||||
let i = *i + text.len() + 1;
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::TextColor(focus_color.clone()),
|
||||
);
|
||||
text_layout = text_layout.range_attribute(
|
||||
i..i + 1,
|
||||
TextAttribute::Weight(FontWeight::BOLD),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let text_layout = text_layout.build().unwrap();
|
||||
let x = svg_x + 5.0;
|
||||
let y = line_height * line as f64 + text_layout.y_offset(line_height);
|
||||
let point = Point::new(x, y);
|
||||
ctx.draw_text(&text_layout, point);
|
||||
}
|
||||
}
|
||||
|
||||
fn file_paint_symbols(
|
||||
path: &Path,
|
||||
indices: &[usize],
|
||||
workspace_path: Option<&Path>,
|
||||
name: &str,
|
||||
kind: SymbolKind,
|
||||
) -> PaletteItemPaintInfo {
|
||||
let text = name.to_string();
|
||||
let hint = path.to_string_lossy();
|
||||
// Remove the workspace prefix from the path
|
||||
let hint = workspace_path
|
||||
.and_then(Path::to_str)
|
||||
.and_then(|x| hint.strip_prefix(x))
|
||||
.map(|x| x.strip_prefix('/').unwrap_or(x))
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(|| hint.to_string());
|
||||
let text_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < text.len() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i >= text.len() {
|
||||
Some(i - text.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
PaletteItemPaintInfo {
|
||||
svg: symbol_svg(&kind),
|
||||
text,
|
||||
text_indices,
|
||||
hint,
|
||||
hint_indices,
|
||||
}
|
||||
}
|
||||
|
||||
fn file_paint_items(path: &Path, indices: &[usize]) -> PaletteItemPaintInfo {
|
||||
let (svg, _) = file_svg(path);
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let folder = path
|
||||
.parent()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let folder_len = folder.len();
|
||||
let text_indices: Vec<usize> = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if folder_len > 0 {
|
||||
if i > folder_len {
|
||||
Some(i - folder_len - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(i)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let hint_indices: Vec<usize> = indices
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
let i = *i;
|
||||
if i < folder_len {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
PaletteItemPaintInfo {
|
||||
svg: Some(svg),
|
||||
text: file_name,
|
||||
text_indices,
|
||||
hint: folder,
|
||||
hint_indices,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ pub struct LapceTab {
|
|||
id: WidgetId,
|
||||
pub title: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
main_split: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
completion: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
completion: WidgetPod<LapceTabData, CompletionContainer>,
|
||||
hover: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
rename: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
status: WidgetPod<LapceTabData, Box<dyn Widget<LapceTabData>>>,
|
||||
|
@ -179,7 +179,7 @@ pub fn new(data: &mut LapceTabData) -> Self {
|
|||
id: data.id,
|
||||
title,
|
||||
main_split: WidgetPod::new(main_split.boxed()),
|
||||
completion: WidgetPod::new(completion.boxed()),
|
||||
completion: WidgetPod::new(completion),
|
||||
hover: WidgetPod::new(hover.boxed()),
|
||||
rename: WidgetPod::new(rename.boxed()),
|
||||
picker: WidgetPod::new(picker.boxed()),
|
||||
|
@ -2197,9 +2197,13 @@ fn layout(
|
|||
.set_origin(ctx, data, env, main_split_origin);
|
||||
|
||||
if data.completion.status != CompletionStatus::Inactive {
|
||||
let completion_origin =
|
||||
data.completion_origin(ctx.text(), self_size, &data.config);
|
||||
self.completion.layout(ctx, bc, data, env);
|
||||
let completion_size = self.completion.layout(ctx, bc, data, env);
|
||||
let completion_origin = data.completion_origin(
|
||||
ctx.text(),
|
||||
self_size,
|
||||
completion_size,
|
||||
&data.config,
|
||||
);
|
||||
self.completion
|
||||
.set_origin(ctx, data, env, completion_origin);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue