Skip to content

Introduce GPUI inline element - #48057

Closed
pkondzior wants to merge 2 commits into
zed-industries:mainfrom
pkondzior:gpui-inline
Closed

Introduce GPUI inline element#48057
pkondzior wants to merge 2 commits into
zed-industries:mainfrom
pkondzior:gpui-inline

Conversation

@pkondzior

@pkondzior pkondzior commented Jan 30, 2026

Copy link
Copy Markdown
Contributor

Hi folks, I’d love a sanity check on whether this direction is a good fit for GPUI and worth taking to merge. I’ve built a full inline element that’s layout/styling‑equivalent to div(), but supports true inline flow (text + elements in one run, proper wrapping around inline boxes). This will let a lot of new futures to be built into the Markdown, Agent Panel text rendering or anything other that needs custom inline flow system.

Feature highlight:

  • Inline content model: text, hard breaks, and inline child elements (with logical lengths for cursor/selection semantics).
  • Style + interactivity parity: inline() implements Styled + InteractiveElement, so it supports the same style refinements, hover/click, and event wiring as div().
  • Per‑run text styling: explicit TextRun support for syntax‑like styling via text_runs/runs.
  • Precise layout + hit‑testing: InlineLayout exposes text/plain_text, word ranges, bounds, and index↔position mapping (caret positioning and selection are supported).
  • Truncation and ellipsis: truncate, text_overflow, and line_clamp are implemented end‑to‑end, including ellipsis styling and clipping for inline boxes.
  • Performance plumbing: inline layout is cached (InlineLayoutCache) and there’s a benchmark comparing inline vs div for mixed text/box content.
  • Inspector/debug support: inline layout carries line/box counts and truncation info.

Anything div() can do, inline() can do as well. The key difference is that div() can’t mix text and other div elements on the same line with wrapping, while inline() can.

div()
      .border_1()
      .border_color(gpui::black())
      .p(px(8.0))
      .flex()
      .flex_col()
      .gap(px(4.0))
      .child("Test 8: Interactive inline with hover and click")
      .child({
          inline()
              .text("Here is some text with a ")
              .border_1()
              .border_color(gpui::black())
              .child(
                  inline()
                      .h(px(40.0))
                      .border_1()
                      .border_color(gpui::blue())
                      .text_color(gpui::blue())
                      .text("clickable link")
                      .truncate()
                      .into_element()
                      .id("interactive-link")
                      .hover(|style| {
                          style
                              .bg(gpui::blue().opacity(0.1))
                              .border_color(gpui::red())
                              .cursor_pointer()
                      })
                      .on_click(|_event, _window, _cx| {
                          println!("Interactive inline clicked!");
                      })
              )
              .text(" embedded in the flow. Try hovering and clicking!")
      })

Here’s a video demo of the example app that’s included in the code.

Screen.Recording.2026-01-31.at.01.24.52.mov

Because inline() mixes text and child elements, and request_layout can’t re‑measure children during layout, I added request_layout_with_context to make that possible without extra passes. The implementation now uses an aggressive per‑frame cache (similar to text_system). There’s still room to optimize, but performance is already close to div().

Screenshot 2026-01-31 at 02 07 45

The dependent PR for native‑looking links is ready #48074 This work unlocks a first‑class inline() element in Markdown, enabling rich inline rendering. With the new link‑renderer API, I was able to build properly styled, interactive links that sit seamlessly inside text.

Mentions.Demo.1.mp4
    fn render_markdown(&self, markdown: Entity<Markdown>, style: MarkdownStyle) -> MarkdownElement {
        let workspace = self.workspace.clone();
        let link_workspace = workspace.clone();
        MarkdownElement::new(markdown, style)
            .link_renderer(LinkRenderer::Custom {
                render: Arc::new(move |label, ctx, _window, cx| {
                    Self::link_renderer(&link_workspace, label, ctx, cx)
                }),
            })
            .on_url_click(move |text, window, cx| Self::open_link(text, &workspace, window, cx))
    }

    fn link_renderer(
        link_workspace: &WeakEntity<Workspace>,
        label: LinkLabel,
        ctx: LinkRenderContext,
        cx: &mut App,
    ) -> AnyElement {
        let workspace = link_workspace.upgrade();
        let path_style = workspace.read(cx).path_style(cx);
        match MentionUri::parse(ctx.url.as_ref(), path_style) {
            Ok(mention) => MentionCrease::new(ctx.element_id.clone(), mention)
                .label(label.plain_text())
                .layer(ElevationIndex::ModalSurface)
                .is_toggled(ctx.is_selected)
                .into_any_element(),
            Err(_) => markdown::render_link_element(
                label,
                ctx.element_id.clone(),
                ctx.url.clone(),
                ctx.on_url_click.clone(),
            ),
        }
    }

With inline() in place, Markdown can swap any inline fragment for a custom GPUI element. That’s how MentionUri becomes a first‑class, styled, fully interactive link.

Closes #10916

Release Notes:

  • Added Inline element for GPUI

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jan 30, 2026
@pkondzior
pkondzior marked this pull request as ready for review January 31, 2026 06:44
@pkondzior

pkondzior commented Feb 13, 2026

Copy link
Copy Markdown
Contributor Author

Hey @benbrandt I understand why you had to close the other PR (#48074) that depended on this one. I still want to be clear that I intend to commit to getting inline rendering (the inline() element) landed, because I think it will be genuinely useful for both Zed and GPUI.

This PR also seemed to get a pretty positive / “good vibes” response from the community, which makes me think it’s solving a real pain point and not just an edge-case improvement.

The closed PR was a good proof of concept: inline() gives immediate, practical benefits for Agent UI by enabling more robust responses and a tighter feedback loop around project/code/context. I’ve also opened a discussion/feature request about making markdown links actionable as MentionUris (#48742), where I lay out the rationale for that specific feature. This is already working as a prototype, and it doesn’t degrade performance or bloat the UI, if anything, the opposite. It also opens the door to more improvements for Agent UI (because you can through this wrap any kind of GPUi element into text and make it interactive).

Beyond that, it also unlocks other long-standing needs, like:

Several of these have been top issues for a while, and they haven’t been solvable with the current Taffy-based rendering; the lack of inline support in Taffy is a known limitation across both projects.

I’ve put a lot of effort into this already, and I’m willing to put in more to get it into an acceptable shape. If there are specific changes you need in this PR (approach, API shape, tests, benchmarks, etc.), tell me what “done” looks like and I’ll make it happen.

//cc @mikayla-maki as I've saw Your comments on the other PR that tried to tackle this too #26307 (comment)

@MrSubidubi

Copy link
Copy Markdown
Member

Thank you very much for getting this up and working! This is definitely something we do want to have at some point, ultimatively, as you have shown, this will solve a lot of issues and improve things quite a lot.

That said, I am not too confident with the current approach. Things like
grafik

do not spark me with too much confidence, simillarly, there are a lot of other things that do not spark me with much confidence. The PR you linked here below achieved something similar with just ~+400 LoC, which was obviously not perfect, whereas this one is sitting at around 7000, which is increadibly hard to review and even more given the circumstances I just outlined.

I think a good way forward here would be to see how we can get some stuff in incrementally in smaller PRsso reviewing here get's easier and things can get in faster. After all, we do want to have this at one point in this way or another, so it definitely is worth pursuing, but not in the current form. Thus, I hope you can understand this decision, thank you for getting this PR up in the first place, we appreciate it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[GPUI] Support inline box text layout

2 participants