Add LSP completion/code actions/rename/signature help infrastructure, fix TypeScript LSP, rebrand app identifiers

- LSP Layer: Add completion, completion_resolve, signature_help, code_action,
  prepare_rename, and rename methods to TextDocumentService and LspServerModel
- Types: Add CompletionItemData, CompletionResult, SignatureHelpResult,
  CodeActionData, PrepareRenameResult, RenameResult, FileEdits
- Feature Flags: Add LspCompletion, LspCodeActions, LspRename, LspSignatureHelp
- Client Capabilities: Declare completion, signature help, rename, and code
  action capabilities so servers advertise these features
- Completion UI: Add completion state machine with debounced triggers, fuzzy
  filtering, positioned overlay menu, and edit application
- TypeScript LSP: Switch to npx for running typescript-language-server (handles
  download/caching automatically, survives node version switches)
- PATH Resolution: Add interactive shell PATH fallback for LSP server discovery
  and spawning (fixes nvm/fnm/volta users)
- App Identity: Rebrand from com.samsung.Galaxy/dev.warp.WarpOss to
  samsung.galaxy.GalaxyOss across bundle IDs, URL schemes, and plists
- Add scripts/reset-galaxy.sh for clean slate testing
- Galaxy status messages and other in-progress work

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-05-18 15:57:47 -05:00
co-authored by Claude Opus 4.6
parent f37a744692
commit a75bc99852
15 changed files with 769 additions and 162 deletions
+5 -5
View File
@@ -935,7 +935,7 @@ cloud_mode_input_v2 = ["cloud_mode"]
[package.metadata.bundle.bin.galaxy-oss]
category = "public.app-category.developer-tools"
copyright = "© 2026, Samsung Electronics Co., Ltd."
identifier = "com.samsung.Galaxy"
identifier = "samsung.galaxy.GalaxyOss"
name = "Galaxy"
resources = ["assets/onboarding"]
icon = ["channels/oss/icon/no-padding/512x512.png", "channels/oss/icon/no-padding/icon.ico"]
@@ -944,7 +944,7 @@ short_description = "Galaxy - AI-powered terminal for development teams."
[package.metadata.bundle.bin.galaxy-stable]
category = "public.app-category.developer-tools"
copyright = "© 2026, Samsung Electronics Co., Ltd."
identifier = "com.samsung.Galaxy-Stable"
identifier = "samsung.galaxy.GalaxyStable"
name = "Galaxy"
resources = ["assets/onboarding"]
short_description = "Galaxy - AI-powered terminal, stable build."
@@ -952,7 +952,7 @@ short_description = "Galaxy - AI-powered terminal, stable build."
[package.metadata.bundle.bin.galaxy-preview]
category = "public.app-category.developer-tools"
copyright = "© 2026, Samsung Electronics Co., Ltd."
identifier = "com.samsung.Galaxy-Preview"
identifier = "samsung.galaxy.GalaxyPreview"
name = "Galaxy Preview"
resources = ["assets/onboarding"]
short_description = "Galaxy - AI-powered terminal, preview build."
@@ -960,7 +960,7 @@ short_description = "Galaxy - AI-powered terminal, preview build."
[package.metadata.bundle.bin.galaxy-dev]
category = "public.app-category.developer-tools"
copyright = "© 2026, Samsung Electronics Co., Ltd."
identifier = "com.samsung.Galaxy-Dev"
identifier = "samsung.galaxy.GalaxyDev"
name = "Galaxy Dev"
resources = ["assets/onboarding"]
short_description = "Galaxy - AI-powered terminal, developer build."
@@ -968,7 +968,7 @@ short_description = "Galaxy - AI-powered terminal, developer build."
[package.metadata.bundle.bin.galaxy-local]
category = "public.app-category.developer-tools"
copyright = "© 2026, Samsung Electronics Co., Ltd."
identifier = "com.samsung.Galaxy-Local"
identifier = "samsung.galaxy.GalaxyLocal"
name = "Galaxy Local"
resources = ["assets/onboarding"]
short_description = "Galaxy - AI-powered terminal, local build."
+1 -1
View File
@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.samsung.GalaxyDockTilePlugin</string>
<string>samsung.galaxy.GalaxyDockTilePlugin</string>
<key>CFBundleExecutable</key>
<string>WarpDockTilePlugin</string>
<key>CFBundleName</key>
+3 -2
View File
@@ -6,7 +6,8 @@ use super::{
view_impl::common::{
render_switch_control_to_user_button, render_warping_indicator,
render_warping_indicator_base, ButtonProps, ForceRefreshButtonProps, MaybeShimmeringText,
WarpingIndicatorProps, WarpingProps, LOAD_OUTPUT_MESSAGE, WAITING_FOR_USER_INPUT_MESSAGE,
WarpingIndicatorProps, WarpingProps, random_load_output_message,
WAITING_FOR_USER_INPUT_MESSAGE,
},
};
use crate::{
@@ -812,7 +813,7 @@ impl BlocklistAIStatusBar {
);
let default_warping_text = fallback_warping_text
.as_deref()
.unwrap_or(LOAD_OUTPUT_MESSAGE)
.unwrap_or(random_load_output_message())
.to_owned();
let secondary_element = if fallback_warping_text.is_some() {
Some(render_fallback_explanation(model.as_ref(), app))
+48 -1
View File
@@ -129,7 +129,54 @@ const IMAGE_SOURCE_LINK_LINE_INDEX: usize = 1;
const ERROR_APOLOGY_TEXT: &str = "I'm sorry, I couldn't complete that request.";
const INTERNAL_WARP_ERROR: &str = "Internal Warp error.";
pub const LOAD_OUTPUT_MESSAGE: &str = "Warping...";
const GALAXY_STATUS_MESSAGES: &[&str] = &[
"Warping through the Galaxy...",
"Guarding the Galaxy...",
"Exploring the Galaxy...",
"Charting the Galaxy...",
"Traversing the Galaxy...",
"Navigating the Galaxy...",
"Scanning the Galaxy...",
"Hitchhiking across the Galaxy...",
"Assembling the Galaxy...",
"Aligning stars in the Galaxy...",
"Consulting the Galaxy...",
"Galaxy brain activated...",
"Orbiting the Galaxy...",
"Bending the Galaxy...",
"Summoning the Galaxy...",
"Decoding the Galaxy...",
"Harnessing Galaxy power...",
"Riding the Galaxy stream...",
"Tuning into the Galaxy...",
"Surfing the Galaxy waves...",
"Mapping the Galaxy...",
"Galaxy engines engaged...",
"Weaving through the Galaxy...",
"Channeling the Galaxy...",
"Phasing through the Galaxy...",
"Galaxy hyperdrive online...",
"Syncing with the Galaxy...",
"Galaxy core spinning up...",
"Drifting through the Galaxy...",
"Galaxy warp field stable...",
"Tapping into Galaxy energy...",
"Galaxy neurons firing...",
"Quantum leaping the Galaxy...",
"Galaxy thrusters ignited...",
"Calculating Galaxy trajectory...",
"Galaxy flux capacitor charged...",
"Piercing the Galaxy veil...",
"Galaxy signal acquired...",
"Folding Galaxy spacetime...",
"Galaxy coordinates locked...",
];
pub fn random_load_output_message() -> &'static str {
use rand::Rng;
let idx = rand::thread_rng().gen_range(0..GALAXY_STATUS_MESSAGES.len());
GALAXY_STATUS_MESSAGES[idx]
}
pub const LOAD_OUTPUT_MESSAGE_FOR_ADJUSTING: &str = "Adjusting tasks...";
pub const LOAD_OUTPUT_MESSAGE_FOR_PASSIVE_CODE_GEN: &str = "Generating fix...";
pub const LOAD_OUTPUT_MESSAGE_FOR_CREATING_DIFF: &str = "Creating diff...";
+1 -1
View File
@@ -46,7 +46,7 @@ embed_plist::embed_info_plist_bytes!(r#"
<key>CFBundleExecutable</key>
<string>galaxy-local</string>
<key>CFBundleIdentifier</key>
<string>com.samsung.Galaxy-Local</string>
<string>samsung.galaxy.GalaxyLocal</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
+1 -1
View File
@@ -43,7 +43,7 @@ embed_plist::embed_info_plist_bytes!(r#"
<key>CFBundleExecutable</key>
<string>galaxy-oss</string>
<key>CFBundleIdentifier</key>
<string>com.samsung.Galaxy</string>
<string>samsung.galaxy.GalaxyOss</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
+513
View File
@@ -0,0 +1,513 @@
use std::time::Duration;
use futures::stream::AbortHandle;
use galaxy_core::features::FeatureFlag;
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::color::internal_colors;
use galaxyui::elements::{
Border, ChildAnchor, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex,
MainAxisSize, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius,
Shrinkable, Text,
};
use galaxyui::{AppContext, Element, SingletonEntity, ViewContext};
use lsp::{CompletionItemData, CompletionKind, CompletionResult, CompletionTrigger};
use pathfinder_geometry::vector::Vector2F;
use string_offset::CharOffset;
use vec1::Vec1;
use super::local_code_editor::LocalCodeEditorView;
pub const COMPLETION_DEBOUNCE_PERIOD: Duration = Duration::from_millis(50);
const COMPLETION_MENU_MAX_HEIGHT: f32 = 220.;
const COMPLETION_MENU_WIDTH: f32 = 340.;
const MAX_VISIBLE_ITEMS: usize = 10;
/// State machine for the completion feature.
pub(super) enum CompletionState {
/// No completion session active.
Idle,
/// Waiting for the LSP response.
Requesting {
abort_handle: AbortHandle,
trigger_offset: CharOffset,
},
/// Completion menu is visible with results.
Showing {
items: Vec<CompletionItemData>,
filtered_indices: Vec<usize>,
selected_index: usize,
trigger_offset: CharOffset,
is_incomplete: bool,
},
}
impl Default for CompletionState {
fn default() -> Self {
Self::Idle
}
}
impl CompletionState {
pub fn is_showing(&self) -> bool {
matches!(self, Self::Showing { .. })
}
pub fn dismiss(&mut self) -> bool {
if matches!(self, Self::Idle) {
return false;
}
if let Self::Requesting { abort_handle, .. } = self {
abort_handle.abort();
}
*self = Self::Idle;
true
}
pub fn selected_item(&self) -> Option<&CompletionItemData> {
match self {
Self::Showing {
items,
filtered_indices,
selected_index,
..
} => filtered_indices
.get(*selected_index)
.and_then(|&idx| items.get(idx)),
_ => None,
}
}
pub fn move_selection(&mut self, delta: i32) {
if let Self::Showing {
filtered_indices,
selected_index,
..
} = self
{
if filtered_indices.is_empty() {
return;
}
let len = filtered_indices.len() as i32;
let new_idx = (*selected_index as i32 + delta).rem_euclid(len);
*selected_index = new_idx as usize;
}
}
pub fn filter(&mut self, query: &str) {
if let Self::Showing {
items,
filtered_indices,
selected_index,
..
} = self
{
if query.is_empty() {
*filtered_indices = (0..items.len()).collect();
} else {
let query_lower = query.to_lowercase();
*filtered_indices = items
.iter()
.enumerate()
.filter(|(_, item)| {
let filter_text = item.effective_filter_text().to_lowercase();
fuzzy_match(&filter_text, &query_lower)
})
.map(|(idx, _)| idx)
.collect();
}
if *selected_index >= filtered_indices.len() {
*selected_index = 0;
}
}
}
}
fn fuzzy_match(target: &str, query: &str) -> bool {
let mut target_chars = target.chars();
for query_char in query.chars() {
loop {
match target_chars.next() {
Some(tc) if tc == query_char => break,
Some(_) => continue,
None => return false,
}
}
}
true
}
impl LocalCodeEditorView {
pub(super) fn is_completion_enabled() -> bool {
FeatureFlag::LspCompletion.is_enabled()
}
/// Handle a user typing event — potentially trigger completion.
pub(super) fn on_content_changed_for_completion(&mut self, ctx: &mut ViewContext<Self>) {
if !Self::is_completion_enabled() {
return;
}
if self.lsp_server.is_none() {
return;
}
let cursor_offset = self.editor().as_ref(ctx).cursor_head_offset(ctx);
if cursor_offset == CharOffset::from(0) {
return;
}
let char_before = self
.editor()
.as_ref(ctx)
.char_at(cursor_offset - CharOffset::from(1), ctx);
let trigger = match char_before {
Some('.') => Some(CompletionTrigger::TriggerCharacter(".".into())),
Some(':') => {
if cursor_offset >= CharOffset::from(2) {
let prev_char = self
.editor()
.as_ref(ctx)
.char_at(cursor_offset - CharOffset::from(2), ctx);
if prev_char == Some(':') {
Some(CompletionTrigger::TriggerCharacter("::".into()))
} else {
None
}
} else {
None
}
}
Some(c) if c.is_alphanumeric() || c == '_' => {
let _ = self.completion_debounce_tx.try_send(cursor_offset);
return;
}
_ => {
if self.completion_state.dismiss() {
ctx.notify();
}
return;
}
};
if let Some(trigger) = trigger {
self.request_completion(cursor_offset, trigger, ctx);
}
}
/// Request completions from the LSP server.
pub(super) fn request_completion(
&mut self,
trigger_offset: CharOffset,
trigger: CompletionTrigger,
ctx: &mut ViewContext<Self>,
) {
if !Self::is_completion_enabled() {
return;
}
let Some(file_path) = self.file_path() else {
return;
};
let Some(lsp_server) = &self.lsp_server else {
return;
};
let lsp_position = self
.editor()
.as_ref(ctx)
.offset_to_lsp_position(trigger_offset, ctx);
let future = match lsp_server
.as_ref(ctx)
.completion(file_path.to_path_buf(), lsp_position, trigger)
{
Ok(future) => future,
Err(e) => {
log::warn!("Failed to call lsp.completion: {e}");
return;
}
};
self.completion_state.dismiss();
let abort_handle = ctx
.spawn(future, move |me, result, ctx| {
me.handle_completion_response(result, trigger_offset, ctx);
})
.abort_handle();
self.completion_state = CompletionState::Requesting {
abort_handle,
trigger_offset,
};
}
/// Handle the debounced completion trigger (from typing).
pub(super) fn request_completion_debounced(
&mut self,
offset: CharOffset,
ctx: &mut ViewContext<Self>,
) {
if let CompletionState::Showing { trigger_offset, .. } = &self.completion_state {
let query = self.get_completion_filter_query(*trigger_offset, ctx);
self.completion_state.filter(&query);
ctx.notify();
return;
}
self.request_completion(offset, CompletionTrigger::Invoked, ctx);
}
fn handle_completion_response(
&mut self,
result: anyhow::Result<Option<CompletionResult>>,
trigger_offset: CharOffset,
ctx: &mut ViewContext<Self>,
) {
let completion_result = match result {
Ok(Some(result)) if !result.items.is_empty() => result,
_ => {
self.completion_state = CompletionState::Idle;
ctx.notify();
return;
}
};
let filtered_indices: Vec<usize> = (0..completion_result.items.len()).collect();
self.completion_state = CompletionState::Showing {
items: completion_result.items,
filtered_indices,
selected_index: 0,
trigger_offset,
is_incomplete: completion_result.is_incomplete,
};
let query = self.get_completion_filter_query(trigger_offset, ctx);
self.completion_state.filter(&query);
ctx.notify();
}
fn get_completion_filter_query(
&self,
trigger_offset: CharOffset,
ctx: &ViewContext<Self>,
) -> String {
let editor = self.editor().as_ref(ctx);
let cursor_offset = editor.cursor_head_offset(ctx);
if cursor_offset <= trigger_offset {
return String::new();
}
// Access the buffer to get text in range
editor
.buffer_text_in_range(trigger_offset..cursor_offset, ctx)
.unwrap_or_default()
}
/// Confirm the currently selected completion item.
pub(super) fn confirm_completion(&mut self, ctx: &mut ViewContext<Self>) -> bool {
let (insert_text, text_edit_range, trigger_offset) = match &self.completion_state {
CompletionState::Showing {
items,
filtered_indices,
selected_index,
trigger_offset,
..
} => {
let Some(&idx) = filtered_indices.get(*selected_index) else {
return false;
};
let item = &items[idx];
(
item.insert_text.clone(),
item.text_edit_range.clone(),
*trigger_offset,
)
}
_ => return false,
};
self.completion_state = CompletionState::Idle;
self.editor.update(ctx, |editor, ctx| {
let edit_range = if let Some(range) = text_edit_range {
let start = editor.lsp_location_to_offset(&range.start, ctx);
let end = editor.lsp_location_to_offset(&range.end, ctx);
start..end
} else {
let cursor = editor.cursor_head_offset(ctx);
trigger_offset..cursor
};
if let Ok(edits) = Vec1::try_from_vec(vec![(insert_text, edit_range)]) {
editor.apply_edits(edits, ctx);
}
});
ctx.notify();
true
}
/// Render the completion menu overlay.
pub(super) fn render_completion_menu(&self, app: &AppContext) -> Option<Box<dyn Element>> {
let CompletionState::Showing {
items,
filtered_indices,
selected_index,
..
} = &self.completion_state
else {
return None;
};
if filtered_indices.is_empty() {
return None;
}
let appearance = Appearance::as_ref(app);
let theme = appearance.theme();
let visible_count = filtered_indices.len().min(MAX_VISIBLE_ITEMS);
let mut content_column = Flex::column();
for (display_idx, &item_idx) in
filtered_indices.iter().enumerate().take(visible_count)
{
let item = &items[item_idx];
let is_selected = display_idx == *selected_index;
content_column.add_child(render_completion_item(item, is_selected, appearance));
}
let constrained_content = ConstrainedBox::new(content_column.finish())
.with_width(COMPLETION_MENU_WIDTH)
.with_max_height(COMPLETION_MENU_MAX_HEIGHT)
.finish();
let menu = Container::new(constrained_content)
.with_background(theme.background())
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
.with_border(Border::all(1.).with_border_fill(internal_colors::neutral_4(theme)))
.finish();
Some(menu)
}
/// Compute the positioning for the completion menu (below cursor).
pub(super) fn completion_menu_positioning(
&self,
app: &AppContext,
) -> Option<OffsetPositioning> {
let trigger_offset = match &self.completion_state {
CompletionState::Showing { trigger_offset, .. } => *trigger_offset,
_ => return None,
};
let bounds = self
.editor()
.as_ref(app)
.character_bounds_in_viewport(trigger_offset, app)?;
Some(OffsetPositioning::offset_from_parent(
Vector2F::new(bounds.origin_x(), bounds.max_y()),
ParentOffsetBounds::ParentByPosition,
ParentAnchor::TopLeft,
ChildAnchor::TopLeft,
))
}
}
fn render_completion_item(
item: &CompletionItemData,
is_selected: bool,
appearance: &Appearance,
) -> Box<dyn Element> {
let theme = appearance.theme();
let icon_text: &'static str = match item.kind {
Some(CompletionKind::Function) | Some(CompletionKind::Method) => "fn",
Some(CompletionKind::Variable) => "var",
Some(CompletionKind::Field) | Some(CompletionKind::Property) => "fld",
Some(CompletionKind::Class) | Some(CompletionKind::Struct) => "str",
Some(CompletionKind::Interface) => "ifc",
Some(CompletionKind::Module) => "mod",
Some(CompletionKind::Enum) => "enm",
Some(CompletionKind::EnumMember) => "emb",
Some(CompletionKind::Constant) => "cst",
Some(CompletionKind::Keyword) => "kw",
Some(CompletionKind::Snippet) => "snp",
Some(CompletionKind::TypeParameter) => "typ",
_ => " ",
};
let label = item.label.clone();
let detail = item.detail.clone();
let mut row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_main_axis_size(MainAxisSize::Min);
row.add_child(
Container::new(
Text::new_inline(
icon_text,
appearance.monospace_font_family(),
appearance.monospace_font_size() * 0.85,
)
.with_color(theme.disabled_ui_text_color().into())
.finish(),
)
.with_padding_left(4.)
.with_padding_right(6.)
.finish(),
);
row.add_child(
Shrinkable::new(
1.,
Text::new(
label,
appearance.monospace_font_family(),
appearance.monospace_font_size(),
)
.with_color(theme.active_ui_text_color().into())
.finish(),
)
.finish(),
);
if let Some(detail) = detail {
row.add_child(
Container::new(
Shrinkable::new(
2.,
Text::new(
detail,
appearance.monospace_font_family(),
appearance.monospace_font_size() * 0.85,
)
.with_color(theme.disabled_ui_text_color().into())
.finish(),
)
.finish(),
)
.with_padding_left(8.)
.finish(),
);
}
let mut container = Container::new(row.finish())
.with_vertical_padding(3.)
.with_horizontal_padding(4.);
if is_selected {
container = container.with_background(internal_colors::neutral_2(theme));
} else {
container = container.with_background(theme.background());
}
container.finish()
}
+10
View File
@@ -1660,6 +1660,16 @@ impl CodeEditorView {
self.model.as_ref(ctx).selections(ctx).first().head
}
/// Returns text in the given character offset range from the buffer.
pub fn buffer_text_in_range(
&self,
range: Range<CharOffset>,
ctx: &AppContext,
) -> Option<String> {
let buffer = self.model.as_ref(ctx).buffer().as_ref(ctx);
Some(buffer.text_in_range(range).into_string())
}
pub fn hovered_symbol_range<'a>(
&'a self,
ctx: &'a AppContext,
+28 -1
View File
@@ -90,6 +90,7 @@ use super::editor::{
scroll::{ScrollPosition, ScrollTrigger},
view::{CodeEditorEvent, CodeEditorView},
};
use super::completion::{CompletionState, COMPLETION_DEBOUNCE_PERIOD};
use super::find_references_view::{FindReferencesView, FindReferencesViewEvent};
use super::language_server_extension::ProcessedDiagnostic;
use super::lsp_telemetry::LspTelemetryEvent;
@@ -303,6 +304,10 @@ pub struct LocalCodeEditorView {
pub(super) diagnostic_decorations: Vec<Decoration>,
/// View for the find references feature.
find_references_view: Option<ViewHandle<FindReferencesView>>,
/// State for the LSP completion menu.
pub(super) completion_state: CompletionState,
/// Channel for debouncing completion requests triggered by typing.
pub(super) completion_debounce_tx: async_channel::Sender<CharOffset>,
}
impl LocalCodeEditorView {
@@ -337,6 +342,9 @@ impl LocalCodeEditorView {
if origin.from_user() {
me.was_edited = true;
ctx.emit(LocalCodeEditorEvent::UserEdited);
// Trigger completion on user typing
me.on_content_changed_for_completion(ctx);
}
}
CodeEditorEvent::VimEscapeInNormalMode => {
@@ -473,6 +481,14 @@ impl LocalCodeEditorView {
|_, _| {},
);
// Set up debounce for completion requests (shorter period than hover)
let (completion_debounce_tx, completion_debounce_rx) = async_channel::unbounded();
ctx.spawn_stream_local(
debounce(COMPLETION_DEBOUNCE_PERIOD, completion_debounce_rx),
|me, offset, ctx| me.request_completion_debounced(offset, ctx),
|_, _| {},
);
let model = Self {
editor,
diff_type,
@@ -495,6 +511,8 @@ impl LocalCodeEditorView {
processed_diagnostics: Vec::new(),
diagnostic_decorations: Vec::new(),
find_references_view: None,
completion_state: CompletionState::default(),
completion_debounce_tx,
};
if let Some(display_mode) = display_mode {
@@ -1915,7 +1933,8 @@ impl LocalCodeEditorView {
fn dismiss_lsp_overlays(&mut self, ctx: &mut ViewContext<Self>) -> bool {
let had_refs = self.close_find_references_card(ctx);
let had_hover = self.lsp_hover_state.clear();
had_refs || had_hover
let had_completion = self.completion_state.dismiss();
had_refs || had_hover || had_completion
}
/// Perform goto definition at the cursor position and navigate directly.
@@ -2181,6 +2200,14 @@ impl View for LocalCodeEditorView {
}
}
// Render completion menu if active
if let (Some(completion_menu), Some(positioning)) = (
self.render_completion_menu(app),
self.completion_menu_positioning(app),
) {
stack.add_positioned_overlay_child(completion_menu, positioning);
}
// Render LSP hover tooltip if available (render last so it appears on top)
if let (Some(hover_tooltip), Some(positioning)) = (
self.render_hover_tooltip(app),
+2
View File
@@ -6,6 +6,8 @@ use std::any::Any;
use std::fmt::Debug;
use std::ops::AddAssign;
#[cfg(not(target_family = "wasm"))]
pub mod completion;
#[cfg(not(target_family = "wasm"))]
pub mod find_references_view;
#[cfg(not(target_family = "wasm"))]