Compare commits
8 Commits
test-threa
...
v0.209.0-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2793dd77ad | ||
|
|
10720d64a3 | ||
|
|
2adb979ed7 | ||
|
|
bec6cd94a4 | ||
|
|
f6c0fa43ef | ||
|
|
62da0dc402 | ||
|
|
154405ff3b | ||
|
|
b558313181 |
@@ -1,11 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, path::Path};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result};
|
||||
use gpui::{App, SharedString, Task};
|
||||
use project::agent_server_store::CODEX_NAME;
|
||||
use fs::Fs;
|
||||
use gpui::{App, AppContext as _, SharedString, Task};
|
||||
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
||||
use settings::{SettingsStore, update_settings_file};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Codex;
|
||||
@@ -30,6 +35,27 @@ impl AgentServer for Codex {
|
||||
ui::IconName::AiOpenAi
|
||||
}
|
||||
|
||||
fn default_mode(&self, cx: &mut App) -> Option<acp::SessionModeId> {
|
||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
||||
settings.get::<AllAgentServersSettings>(None).codex.clone()
|
||||
});
|
||||
|
||||
settings
|
||||
.as_ref()
|
||||
.and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
|
||||
}
|
||||
|
||||
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
update_settings_file(fs, cx, |settings, _| {
|
||||
settings
|
||||
.agent_servers
|
||||
.get_or_insert_default()
|
||||
.codex
|
||||
.get_or_insert_default()
|
||||
.default_mode = mode_id.map(|m| m.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn connect(
|
||||
&self,
|
||||
root_dir: Option<&Path>,
|
||||
|
||||
@@ -1045,9 +1045,6 @@ impl AcpThreadView {
|
||||
return;
|
||||
};
|
||||
|
||||
self.message_editor
|
||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
|
||||
let connection = thread.read(cx).connection().clone();
|
||||
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
|
||||
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
||||
@@ -1058,6 +1055,9 @@ impl AcpThreadView {
|
||||
.iter()
|
||||
.any(|command| command.name == "logout");
|
||||
if can_login && !logout_supported {
|
||||
self.message_editor
|
||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||
|
||||
let this = cx.weak_entity();
|
||||
let agent = self.agent.clone();
|
||||
window.defer(cx, |window, cx| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{Entity, WeakEntity};
|
||||
use gpui::{Corner, Entity, WeakEntity};
|
||||
use project::debugger::session::{ThreadId, ThreadStatus};
|
||||
use ui::{CommonAnimationExt, ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||
use util::{maybe, truncate_and_trailoff};
|
||||
@@ -211,6 +211,7 @@ impl DebugPanel {
|
||||
this
|
||||
}),
|
||||
)
|
||||
.attach(Corner::BottomLeft)
|
||||
.style(DropdownStyle::Ghost)
|
||||
.handle(self.session_picker_menu_handle.clone());
|
||||
|
||||
@@ -322,6 +323,7 @@ impl DebugPanel {
|
||||
this
|
||||
}),
|
||||
)
|
||||
.attach(Corner::BottomLeft)
|
||||
.disabled(session_terminated)
|
||||
.style(DropdownStyle::Ghost)
|
||||
.handle(self.thread_picker_menu_handle.clone()),
|
||||
|
||||
@@ -57,29 +57,65 @@ impl RustLspAdapter {
|
||||
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
enum LibcType {
|
||||
Gnu,
|
||||
Musl,
|
||||
}
|
||||
|
||||
impl RustLspAdapter {
|
||||
#[cfg(target_os = "linux")]
|
||||
fn build_arch_server_name_linux() -> String {
|
||||
enum LibcType {
|
||||
Gnu,
|
||||
Musl,
|
||||
async fn determine_libc_type() -> LibcType {
|
||||
use futures::pin_mut;
|
||||
use smol::process::Command;
|
||||
|
||||
async fn from_ldd_version() -> Option<LibcType> {
|
||||
let ldd_output = Command::new("ldd").arg("--version").output().await.ok()?;
|
||||
let ldd_version = String::from_utf8_lossy(&ldd_output.stdout);
|
||||
|
||||
if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") {
|
||||
Some(LibcType::Gnu)
|
||||
} else if ldd_version.contains("musl") {
|
||||
Some(LibcType::Musl)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let has_musl = std::fs::exists(&format!("/lib/ld-musl-{}.so.1", std::env::consts::ARCH))
|
||||
.unwrap_or(false);
|
||||
let has_gnu = std::fs::exists(&format!("/lib/ld-linux-{}.so.1", std::env::consts::ARCH))
|
||||
.unwrap_or(false);
|
||||
if let Some(libc_type) = from_ldd_version().await {
|
||||
return libc_type;
|
||||
}
|
||||
|
||||
let libc_type = match (has_musl, has_gnu) {
|
||||
let Ok(dir_entries) = smol::fs::read_dir("/lib").await else {
|
||||
// defaulting to gnu because nix doesn't have /lib files due to not following FHS
|
||||
return LibcType::Gnu;
|
||||
};
|
||||
let dir_entries = dir_entries.filter_map(async move |e| e.ok());
|
||||
pin_mut!(dir_entries);
|
||||
|
||||
let mut has_musl = false;
|
||||
let mut has_gnu = false;
|
||||
|
||||
while let Some(entry) = dir_entries.next().await {
|
||||
let file_name = entry.file_name();
|
||||
let file_name = file_name.to_string_lossy();
|
||||
if file_name.starts_with("ld-musl-") {
|
||||
has_musl = true;
|
||||
} else if file_name.starts_with("ld-linux-") {
|
||||
has_gnu = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (has_musl, has_gnu) {
|
||||
(true, _) => LibcType::Musl,
|
||||
(_, true) => LibcType::Gnu,
|
||||
_ => {
|
||||
// defaulting to gnu because nix doesn't have either of those files due to not following FHS
|
||||
LibcType::Gnu
|
||||
}
|
||||
};
|
||||
_ => LibcType::Gnu,
|
||||
}
|
||||
}
|
||||
|
||||
let libc = match libc_type {
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn build_arch_server_name_linux() -> String {
|
||||
let libc = match Self::determine_libc_type().await {
|
||||
LibcType::Musl => "musl",
|
||||
LibcType::Gnu => "gnu",
|
||||
};
|
||||
@@ -87,7 +123,7 @@ impl RustLspAdapter {
|
||||
format!("{}-{}", Self::ARCH_SERVER_NAME, libc)
|
||||
}
|
||||
|
||||
fn build_asset_name() -> String {
|
||||
async fn build_asset_name() -> String {
|
||||
let extension = match Self::GITHUB_ASSET_KIND {
|
||||
AssetKind::TarGz => "tar.gz",
|
||||
AssetKind::Gz => "gz",
|
||||
@@ -95,7 +131,7 @@ impl RustLspAdapter {
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let arch_server_name = Self::build_arch_server_name_linux();
|
||||
let arch_server_name = Self::build_arch_server_name_linux().await;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let arch_server_name = Self::ARCH_SERVER_NAME.to_string();
|
||||
|
||||
@@ -447,7 +483,7 @@ impl LspInstaller for RustLspAdapter {
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let asset_name = Self::build_asset_name();
|
||||
let asset_name = Self::build_asset_name().await;
|
||||
let asset = release
|
||||
.assets
|
||||
.into_iter()
|
||||
|
||||
@@ -14,12 +14,13 @@ use super::dap_command::{
|
||||
TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
|
||||
};
|
||||
use super::dap_store::DapStore;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use base64::Engine;
|
||||
use collections::{HashMap, HashSet, IndexMap};
|
||||
use dap::adapters::{DebugAdapterBinary, DebugAdapterName};
|
||||
use dap::messages::Response;
|
||||
use dap::requests::{Request, RunInTerminal, StartDebugging};
|
||||
use dap::transport::TcpTransport;
|
||||
use dap::{
|
||||
Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
|
||||
SteppingGranularity, StoppedEvent, VariableReference,
|
||||
@@ -47,12 +48,14 @@ use remote::RemoteClient;
|
||||
use rpc::ErrorExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use smol::net::TcpListener;
|
||||
use smol::net::{TcpListener, TcpStream};
|
||||
use std::any::TypeId;
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
use std::u64;
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -63,6 +66,7 @@ use std::{
|
||||
};
|
||||
use task::TaskContext;
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
use url::Url;
|
||||
use util::command::new_smol_command;
|
||||
use util::{ResultExt, debug_panic, maybe};
|
||||
use worktree::Worktree;
|
||||
@@ -2768,31 +2772,42 @@ impl Session {
|
||||
|
||||
let mut console_output = self.console_output(cx);
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let (dap_port, _child) =
|
||||
if remote_client.read_with(cx, |client, _| client.shares_network_interface())? {
|
||||
(request.server_port, None)
|
||||
} else {
|
||||
let port = {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.context("getting port for DAP")?;
|
||||
listener.local_addr()?.port()
|
||||
};
|
||||
let child = remote_client.update(cx, |client, _| {
|
||||
let command = client.build_forward_port_command(
|
||||
port,
|
||||
"localhost".into(),
|
||||
request.server_port,
|
||||
)?;
|
||||
let child = new_smol_command(command.program)
|
||||
.args(command.args)
|
||||
.envs(command.env)
|
||||
.spawn()
|
||||
.context("spawning port forwarding process")?;
|
||||
anyhow::Ok(child)
|
||||
})??;
|
||||
(port, Some(child))
|
||||
};
|
||||
let forward_ports_process = if remote_client
|
||||
.read_with(cx, |client, _| client.shares_network_interface())?
|
||||
{
|
||||
request.other.insert(
|
||||
"proxyUri".into(),
|
||||
format!("127.0.0.1:{}", request.server_port).into(),
|
||||
);
|
||||
None
|
||||
} else {
|
||||
let port = TcpTransport::unused_port(Ipv4Addr::LOCALHOST)
|
||||
.await
|
||||
.context("getting port for DAP")?;
|
||||
request
|
||||
.other
|
||||
.insert("proxyUri".into(), format!("127.0.0.1:{port}").into());
|
||||
let mut port_forwards = vec![(port, "localhost".to_owned(), request.server_port)];
|
||||
|
||||
if let Some(value) = request.params.get("url")
|
||||
&& let Some(url) = value.as_str()
|
||||
&& let Some(url) = Url::parse(url).ok()
|
||||
&& let Some(frontend_port) = url.port()
|
||||
{
|
||||
port_forwards.push((frontend_port, "localhost".to_owned(), frontend_port));
|
||||
}
|
||||
|
||||
let child = remote_client.update(cx, |client, _| {
|
||||
let command = client.build_forward_ports_command(port_forwards)?;
|
||||
let child = new_smol_command(command.program)
|
||||
.args(command.args)
|
||||
.envs(command.env)
|
||||
.spawn()
|
||||
.context("spawning port forwarding process")?;
|
||||
anyhow::Ok(child)
|
||||
})??;
|
||||
Some(child)
|
||||
};
|
||||
|
||||
let mut companion_process = None;
|
||||
let companion_port =
|
||||
@@ -2814,14 +2829,17 @@ impl Session {
|
||||
}
|
||||
}
|
||||
};
|
||||
this.update(cx, |this, cx| {
|
||||
this.companion_port = Some(companion_port);
|
||||
let Some(mut child) = companion_process else {
|
||||
return;
|
||||
};
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
|
||||
let mut background_tasks = Vec::new();
|
||||
if let Some(mut forward_ports_process) = forward_ports_process {
|
||||
background_tasks.push(cx.spawn(async move |_| {
|
||||
forward_ports_process.status().await.log_err();
|
||||
}));
|
||||
};
|
||||
if let Some(mut companion_process) = companion_process {
|
||||
if let Some(stderr) = companion_process.stderr.take() {
|
||||
let mut console_output = console_output.clone();
|
||||
this.background_tasks.push(cx.spawn(async move |_, _| {
|
||||
background_tasks.push(cx.spawn(async move |_| {
|
||||
let mut stderr = BufReader::new(stderr);
|
||||
let mut line = String::new();
|
||||
while let Ok(n) = stderr.read_line(&mut line).await
|
||||
@@ -2835,9 +2853,9 @@ impl Session {
|
||||
}
|
||||
}));
|
||||
}
|
||||
this.background_tasks.push(cx.spawn({
|
||||
background_tasks.push(cx.spawn({
|
||||
let mut console_output = console_output.clone();
|
||||
async move |_, _| match child.status().await {
|
||||
async move |_| match companion_process.status().await {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
console_output
|
||||
@@ -2860,17 +2878,33 @@ impl Session {
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}))
|
||||
})?;
|
||||
}));
|
||||
}
|
||||
|
||||
request
|
||||
.other
|
||||
.insert("proxyUri".into(), format!("127.0.0.1:{dap_port}").into());
|
||||
// TODO pass wslInfo as needed
|
||||
|
||||
let companion_address = format!("127.0.0.1:{companion_port}");
|
||||
let mut companion_started = false;
|
||||
for _ in 0..10 {
|
||||
if TcpStream::connect(&companion_address).await.is_ok() {
|
||||
companion_started = true;
|
||||
break;
|
||||
}
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(100))
|
||||
.await;
|
||||
}
|
||||
if !companion_started {
|
||||
console_output
|
||||
.send("Browser companion failed to start".into())
|
||||
.await
|
||||
.ok();
|
||||
bail!("Browser companion failed to start");
|
||||
}
|
||||
|
||||
let response = http_client
|
||||
.post_json(
|
||||
&format!("http://127.0.0.1:{companion_port}/launch-and-attach"),
|
||||
&format!("http://{companion_address}/launch-and-attach"),
|
||||
serde_json::to_string(&request)
|
||||
.context("serializing request")?
|
||||
.into(),
|
||||
@@ -2895,6 +2929,11 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.background_tasks.extend(background_tasks);
|
||||
this.companion_port = Some(companion_port);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
});
|
||||
self.background_tasks.push(cx.spawn(async move |_, _| {
|
||||
@@ -2926,15 +2965,16 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct LaunchBrowserInCompanionParams {
|
||||
server_port: u16,
|
||||
params: HashMap<String, serde_json::Value>,
|
||||
#[serde(flatten)]
|
||||
other: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct KillCompanionBrowserParams {
|
||||
launch_id: u64,
|
||||
|
||||
@@ -1385,14 +1385,21 @@ impl RemoteServerProjects {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.update_settings_file(cx, move |setting, _| {
|
||||
setting
|
||||
.wsl_connections
|
||||
.get_or_insert(Default::default())
|
||||
.push(settings::WslConnection {
|
||||
distro_name: SharedString::from(connection_options.distro_name),
|
||||
user: connection_options.user,
|
||||
let connections = setting.wsl_connections.get_or_insert(Default::default());
|
||||
|
||||
let distro_name = SharedString::from(connection_options.distro_name);
|
||||
let user = connection_options.user;
|
||||
|
||||
if !connections
|
||||
.iter()
|
||||
.any(|conn| conn.distro_name == distro_name && conn.user == user)
|
||||
{
|
||||
connections.push(settings::WslConnection {
|
||||
distro_name,
|
||||
user,
|
||||
projects: BTreeSet::new(),
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -836,16 +836,14 @@ impl RemoteClient {
|
||||
connection.build_command(program, args, env, working_dir, port_forward)
|
||||
}
|
||||
|
||||
pub fn build_forward_port_command(
|
||||
pub fn build_forward_ports_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
forwards: Vec<(u16, String, u16)>,
|
||||
) -> Result<CommandTemplate> {
|
||||
let Some(connection) = self.remote_connection() else {
|
||||
return Err(anyhow!("no ssh connection"));
|
||||
};
|
||||
connection.build_forward_port_command(local_port, host, remote_port)
|
||||
connection.build_forward_ports_command(forwards)
|
||||
}
|
||||
|
||||
pub fn upload_directory(
|
||||
@@ -1116,11 +1114,9 @@ pub(crate) trait RemoteConnection: Send + Sync {
|
||||
working_dir: Option<String>,
|
||||
port_forward: Option<(u16, String, u16)>,
|
||||
) -> Result<CommandTemplate>;
|
||||
fn build_forward_port_command(
|
||||
fn build_forward_ports_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
remote: String,
|
||||
remote_port: u16,
|
||||
forwards: Vec<(u16, String, u16)>,
|
||||
) -> Result<CommandTemplate>;
|
||||
fn connection_options(&self) -> RemoteConnectionOptions;
|
||||
fn path_style(&self) -> PathStyle;
|
||||
@@ -1551,19 +1547,17 @@ mod fake {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
fn build_forward_ports_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
forwards: Vec<(u16, String, u16)>,
|
||||
) -> anyhow::Result<CommandTemplate> {
|
||||
Ok(CommandTemplate {
|
||||
program: "ssh".into(),
|
||||
args: vec![
|
||||
"-N".into(),
|
||||
"-L".into(),
|
||||
format!("{local_port}:{host}:{remote_port}"),
|
||||
],
|
||||
args: std::iter::once("-N".to_owned())
|
||||
.chain(forwards.into_iter().map(|(local_port, host, remote_port)| {
|
||||
format!("{local_port}:{host}:{remote_port}")
|
||||
}))
|
||||
.collect(),
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,19 +146,20 @@ impl RemoteConnection for SshRemoteConnection {
|
||||
)
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
fn build_forward_ports_command(
|
||||
&self,
|
||||
local_port: u16,
|
||||
host: String,
|
||||
remote_port: u16,
|
||||
forwards: Vec<(u16, String, u16)>,
|
||||
) -> Result<CommandTemplate> {
|
||||
let Self { socket, .. } = self;
|
||||
let mut args = socket.ssh_args();
|
||||
args.push("-N".into());
|
||||
for (local_port, host, remote_port) in forwards {
|
||||
args.push("-L".into());
|
||||
args.push(format!("{local_port}:{host}:{remote_port}"));
|
||||
}
|
||||
Ok(CommandTemplate {
|
||||
program: "ssh".into(),
|
||||
args: vec![
|
||||
"-N".into(),
|
||||
"-L".into(),
|
||||
format!("{local_port}:{host}:{remote_port}"),
|
||||
],
|
||||
args,
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -485,11 +485,9 @@ impl RemoteConnection for WslRemoteConnection {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_forward_port_command(
|
||||
fn build_forward_ports_command(
|
||||
&self,
|
||||
_: u16,
|
||||
_: String,
|
||||
_: u16,
|
||||
_: Vec<(u16, String, u16)>,
|
||||
) -> anyhow::Result<CommandTemplate> {
|
||||
Err(anyhow!("WSL shares a network interface with the host"))
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ impl Render for PlatformTitleBar {
|
||||
})
|
||||
// this border is to avoid a transparent gap in the rounded corners
|
||||
.mt(px(-1.))
|
||||
.mb(px(-1.))
|
||||
.border(px(1.))
|
||||
.border_color(titlebar_color),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{Corner, Entity, Pixels, Point};
|
||||
|
||||
use crate::{ContextMenu, PopoverMenu, prelude::*};
|
||||
use crate::{ButtonLike, ContextMenu, PopoverMenu, prelude::*};
|
||||
|
||||
use super::PopoverMenuHandle;
|
||||
|
||||
@@ -137,36 +137,52 @@ impl RenderOnce for DropdownMenu {
|
||||
let full_width = self.full_width;
|
||||
let trigger_size = self.trigger_size;
|
||||
|
||||
let button = match self.label {
|
||||
LabelKind::Text(text) => Button::new(self.id.clone(), text)
|
||||
.style(button_style)
|
||||
.when(self.chevron, |this| {
|
||||
this.icon(IconName::ChevronUpDown)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
})
|
||||
.when(full_width, |this| this.full_width())
|
||||
.size(trigger_size)
|
||||
.disabled(self.disabled),
|
||||
LabelKind::Element(_element) => Button::new(self.id.clone(), "")
|
||||
.style(button_style)
|
||||
.when(self.chevron, |this| {
|
||||
this.icon(IconName::ChevronUpDown)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
})
|
||||
.when(full_width, |this| this.full_width())
|
||||
.size(trigger_size)
|
||||
.disabled(self.disabled),
|
||||
}
|
||||
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index));
|
||||
let (text_button, element_button) = match self.label {
|
||||
LabelKind::Text(text) => (
|
||||
Some(
|
||||
Button::new(self.id.clone(), text)
|
||||
.style(button_style)
|
||||
.when(self.chevron, |this| {
|
||||
this.icon(IconName::ChevronUpDown)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
})
|
||||
.when(full_width, |this| this.full_width())
|
||||
.size(trigger_size)
|
||||
.disabled(self.disabled)
|
||||
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)),
|
||||
),
|
||||
None,
|
||||
),
|
||||
LabelKind::Element(element) => (
|
||||
None,
|
||||
Some(
|
||||
ButtonLike::new(self.id.clone())
|
||||
.child(element)
|
||||
.style(button_style)
|
||||
.when(self.chevron, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::ChevronUpDown)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
})
|
||||
.when(full_width, |this| this.full_width())
|
||||
.size(trigger_size)
|
||||
.disabled(self.disabled)
|
||||
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
PopoverMenu::new((self.id.clone(), "popover"))
|
||||
.full_width(self.full_width)
|
||||
.menu(move |_window, _cx| Some(self.menu.clone()))
|
||||
.trigger(button)
|
||||
.when_some(text_button, |this, text_button| this.trigger(text_button))
|
||||
.when_some(element_button, |this, element_button| {
|
||||
this.trigger(element_button)
|
||||
})
|
||||
.attach(match self.attach {
|
||||
Some(attach) => attach,
|
||||
None => Corner::BottomRight,
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
Reference in New Issue
Block a user