Add `client::zed_urls` module for constructing zed.dev URLs (#19391)

Marshall Bowers created

This PR adds a new `zed_urls` module to the `client` crate.

This module contains functions for constructing URLs to Zed properties,
such as zed.dev.

The URLs produced by this module will respect the server URL set via
settings or the `ZED_SERVER_URL` environment variable. This allows them
to correctly reflect the current environment (such as when testing Zed
against a local collab/zed.dev).

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs     |  8 +++-----
crates/client/src/client.rs                 |  1 +
crates/client/src/zed_urls.rs               | 19 +++++++++++++++++++
crates/language_model/src/provider/cloud.rs |  9 +++++----
crates/zed/src/zed.rs                       |  5 ++---
5 files changed, 30 insertions(+), 12 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -21,7 +21,7 @@ use crate::{
 use anyhow::Result;
 use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
 use assistant_tool::ToolRegistry;
-use client::{proto, Client, Status};
+use client::{proto, zed_urls, Client, Status};
 use collections::{BTreeSet, HashMap, HashSet};
 use editor::{
     actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
@@ -3675,7 +3675,6 @@ impl ContextEditor {
 
     fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
         const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
-        const ACCOUNT_URL: &str = "https://zed.dev/account";
 
         v_flex()
             .gap_0p5()
@@ -3700,7 +3699,7 @@ impl ContextEditor {
                     .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
                         |this, _, cx| {
                             this.last_error = None;
-                            cx.open_url(ACCOUNT_URL);
+                            cx.open_url(&zed_urls::account_url(cx));
                             cx.notify();
                         },
                     )))
@@ -3716,7 +3715,6 @@ impl ContextEditor {
 
     fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
         const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
-        const ACCOUNT_URL: &str = "https://zed.dev/account";
 
         v_flex()
             .gap_0p5()
@@ -3742,7 +3740,7 @@ impl ContextEditor {
                         Button::new("subscribe", "Update Monthly Spend Limit").on_click(
                             cx.listener(|this, _, cx| {
                                 this.last_error = None;
-                                cx.open_url(ACCOUNT_URL);
+                                cx.open_url(&zed_urls::account_url(cx));
                                 cx.notify();
                             }),
                         ),

crates/client/src/client.rs 🔗

@@ -4,6 +4,7 @@ pub mod test;
 mod socks;
 pub mod telemetry;
 pub mod user;
+pub mod zed_urls;
 
 use anyhow::{anyhow, bail, Context as _, Result};
 use async_recursion::async_recursion;

crates/client/src/zed_urls.rs 🔗

@@ -0,0 +1,19 @@
+//! Contains helper functions for constructing URLs to various Zed-related pages.
+//!
+//! These URLs will adapt to the configured server URL in order to construct
+//! links appropriate for the environment (e.g., by linking to a local copy of
+//! zed.dev in development).
+
+use gpui::AppContext;
+use settings::Settings;
+
+use crate::ClientSettings;
+
+fn server_url(cx: &AppContext) -> &str {
+    &ClientSettings::get_global(cx).server_url
+}
+
+/// Returns the URL to the account page on zed.dev.
+pub fn account_url(cx: &AppContext) -> String {
+    format!("{server_url}/account", server_url = server_url(cx))
+}

crates/language_model/src/provider/cloud.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
 use anthropic::AnthropicError;
 use anyhow::{anyhow, Result};
 use client::{
-    Client, PerformCompletionParams, UserStore, EXPIRED_LLM_TOKEN_HEADER_NAME,
+    zed_urls, Client, PerformCompletionParams, UserStore, EXPIRED_LLM_TOKEN_HEADER_NAME,
     MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME,
 };
 use collections::BTreeMap;
@@ -905,7 +905,6 @@ impl ConfigurationView {
 impl Render for ConfigurationView {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         const ZED_AI_URL: &str = "https://zed.dev/ai";
-        const ACCOUNT_SETTINGS_URL: &str = "https://zed.dev/account";
 
         let is_connected = !self.state.read(cx).is_signed_out();
         let plan = self.state.read(cx).user_store.read(cx).current_plan();
@@ -922,7 +921,7 @@ impl Render for ConfigurationView {
                 h_flex().child(
                     Button::new("manage_settings", "Manage Subscription")
                         .style(ButtonStyle::Tinted(TintColor::Accent))
-                        .on_click(cx.listener(|_, _, cx| cx.open_url(ACCOUNT_SETTINGS_URL))),
+                        .on_click(cx.listener(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))),
                 ),
             )
         } else if cx.has_flag::<ZedPro>() {
@@ -938,7 +937,9 @@ impl Render for ConfigurationView {
                         Button::new("upgrade", "Upgrade")
                             .style(ButtonStyle::Subtle)
                             .color(Color::Accent)
-                            .on_click(cx.listener(|_, _, cx| cx.open_url(ACCOUNT_SETTINGS_URL))),
+                            .on_click(
+                                cx.listener(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
+                            ),
                     ),
             )
         } else {

crates/zed/src/zed.rs 🔗

@@ -11,7 +11,7 @@ pub(crate) mod windows_only_instance;
 pub use app_menus::*;
 use assistant::PromptBuilder;
 use breadcrumbs::Breadcrumbs;
-use client::ZED_URL_SCHEME;
+use client::{zed_urls, ZED_URL_SCHEME};
 use collections::VecDeque;
 use command_palette_hooks::CommandPaletteFilter;
 use editor::ProposedChangesEditorToolbar;
@@ -419,8 +419,7 @@ pub fn initialize_workspace(
             )
             .register_action(
                 |_: &mut Workspace, _: &OpenAccountSettings, cx: &mut ViewContext<Workspace>| {
-                    let server_url = &client::ClientSettings::get_global(cx).server_url;
-                    cx.open_url(&format!("{server_url}/account"));
+                    cx.open_url(&zed_urls::account_url(cx));
                 },
             )
             .register_action(