<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Warp</title>
    <description>The latest articles on Forem by Warp (@warpdotdev).</description>
    <link>https://forem.com/warpdotdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5382%2Fc1b7ce03-8068-4f25-adfc-c5fcf95acae4.png</url>
      <title>Forem: Warp</title>
      <link>https://forem.com/warpdotdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/warpdotdev"/>
    <language>en</language>
    <item>
      <title>Introducing Warp: The Terminal for the 21st Century</title>
      <dc:creator>Warp</dc:creator>
      <pubDate>Sun, 08 May 2022 18:04:33 +0000</pubDate>
      <link>https://forem.com/warpdotdev/introducing-warp-the-terminal-for-the-21st-century-3m3c</link>
      <guid>https://forem.com/warpdotdev/introducing-warp-the-terminal-for-the-21st-century-3m3c</guid>
      <description>&lt;h2&gt;
  
  
  Introducing Warp
&lt;/h2&gt;

&lt;p&gt;Today, I’m proud to officially introduce Warp, a from-first-principles reinvention of the terminal to make it work better for developers and teams. As of today, Warp is in public beta and any Mac user can download and use it for free.&lt;/p&gt;

&lt;p&gt;We are also excited to announce that we’ve raised some funds to grow Warp ($23M), both from wonderful firms (&lt;a href="https://www.gv.com"&gt;GV&lt;/a&gt;, &lt;a href="https://neo.com"&gt;Neo&lt;/a&gt;, &lt;a href="https://www.boxgroup.com"&gt;BoxGroup&lt;/a&gt;) and world-class operators like Dylan Field (who led our Series A), Elad Gil, Jeff Weiner, and Marc Benioff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Reinvent the Terminal?
&lt;/h2&gt;

&lt;p&gt;Walk by any developer’s desk and you are likely to see two apps open: their code editor and their terminal (sometimes the code editor is the terminal!).  &lt;/p&gt;

&lt;p&gt;Both are crucial to developer productivity.  The code editor is where developers write code; the terminal is where they do pretty much everything else, from building code to running and deploying it, interacting with source control, configuring their cloud systems, and more.&lt;/p&gt;

&lt;p&gt;And yet only one of these two apps – the code editor – has experienced meaningful product improvement over the past 40 years. Compared to using VS Code, using the terminal is like stepping back in time to the 1970s. Only &lt;a href="https://insights.stackoverflow.com/survey/2021#section-most-popular-technologies-integrated-development-environment"&gt;70% of developers use VSCode&lt;/a&gt;, while 100% use the terminal. So why is the terminal experience still so lackluster?&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Product Vision
&lt;/h2&gt;

&lt;p&gt;At Warp, our product vision is to bring the terminal into the present in order to help developers build the future.&lt;/p&gt;

&lt;p&gt;‍We are doing this by fixing the two biggest pain points that exist in today’s terminals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Terminals are hard to use&lt;/li&gt;
&lt;li&gt;They don’t work for teams&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are pain points that I’ve personally experienced again and again in my twenty years as an engineer, and I’m sure readers feel the same.&lt;/p&gt;

&lt;p&gt;‍To get good at using a terminal before Warp, users had to do all sorts of complex configuration, master arcane key shortcuts, and memorize abstruse commands. Even then, seemingly simple things like copying a command’s output or positioning the mouse cursor were still difficult.&lt;/p&gt;

&lt;p&gt;‍Warp makes input and output easy, and removes the need for most configuration. Input works like a modern text-editor, and output works like a data notebook. Moreover, Warp makes command-entry fast and fun by suggesting commands for commonly used tools and providing built-in workflows that save developers time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b7OF7Sit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652031521114/xY6eCQ762.gif%2520align%3D%2522center%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b7OF7Sit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1652031521114/xY6eCQ762.gif%2520align%3D%2522center%2522" alt="image_1.gif" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;‍In short, Warp makes the terminal work for the developer, rather than the other way around.&lt;/p&gt;

&lt;p&gt;Secondly, up until Warp, terminals have been inherently single-user, local applications.  But as I learned when I was leading engineering on Google Docs – and as Dylan showed with Figma – every productivity application is more powerful when it’s collaborative.  This is true 100% of the time – from Figma to GDocs to Notion to Front – and I’m confident that the terminal is no exception.&lt;/p&gt;

&lt;p&gt;Terminal “collaboration” means not just GDocs-style real-time collaboration, but asynchronous collaboration through sharing commands, settings, and history. It means increased knowledge-sharing through wikis and READMEs that run directly in the terminal. It means making the terminal safer and more secure via integrated password-management and audit logging. It means making the terminal a more extensible and customizable platform, with a nice modern ecosystem.&lt;/p&gt;

&lt;p&gt;Finally, all of this needs to be built with speed and compatibility in mind – Warp is made in Rust with GPU-accelerated graphics, and works with existing shells like zsh, fish and bash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go Try Warp!
&lt;/h2&gt;

&lt;p&gt;Nine months ago we launched Warp in private beta behind a waitlist.  Since then, thousands of developers have made Warp their daily driver, given us feedback, and allowed us to greatly improve the product.  &lt;/p&gt;

&lt;p&gt;Today, we are removing our waitlist and launching our public beta.  We are calling it a “beta” because we know there are still some issues to smooth out, but we are confident that even today the experience is meaningfully better than in other terminals.&lt;/p&gt;

&lt;p&gt;If you use a Mac, please give it a shot and let us know how it goes. Otherwise, sign up here to be notified when Warp is ready for your platform.  &lt;/p&gt;

&lt;p&gt;Welcome to the future of the terminal.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>shell</category>
      <category>beginners</category>
      <category>zsh</category>
    </item>
    <item>
      <title>How we designed themes for the terminal - a peek into our process</title>
      <dc:creator>elvis kahoro</dc:creator>
      <pubDate>Thu, 17 Mar 2022 05:16:23 +0000</pubDate>
      <link>https://forem.com/warpdotdev/how-we-designed-themes-for-the-terminal-a-peek-into-our-process-2kn4</link>
      <guid>https://forem.com/warpdotdev/how-we-designed-themes-for-the-terminal-a-peek-into-our-process-2kn4</guid>
      <description>&lt;p&gt;It’s no secret that people love to customize their UIs. With the rampant adoption of light mode and dark mode and the long history of themes in terminals and IDEs, the power to make your UI fit your functionality or aesthetic needs is huge. Here at Warp we knew how important it is to create a great theming system for our users. Some key goals were to ensure compatibility with existing themes, easy customization and sharing.&lt;/p&gt;

&lt;p&gt;Let’s start with how themes work in existing terminals and what we wanted to improve.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Themes only control the text display, yet the rest of the UI remains the same. We want to deliver a more cohesive and immersive experience.&lt;/li&gt;
&lt;li&gt;Themes are hard to control, you have to turn a ton of individual “knobs” in order to get something that looks good. It’s not easy to get what you want and add your personal preference. We want to make customization simple, flexible and quick.&lt;/li&gt;
&lt;li&gt;Themes are hard to share. Importing and exporting themes are different and can require several steps. We want to make it a one click simple experience to import and share.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  The foundation
&lt;/h1&gt;

&lt;p&gt;To ensure warp could be compatible with all other existing themes we started with the standard foundation of 16 ANSI colors. Using these standards as our foundation allows practically every terminal theme to be used in warp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg75wv85y7tfbrzropdat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg75wv85y7tfbrzropdat.png" alt="16 ANSI Colors" width="325" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We wanted to take it one step further and have the theme extend to the whole UI. Since we custom built Warp’s UI this allows us to have full control of the look and feel.&lt;/p&gt;

&lt;p&gt;The first step of that was to consider the UI accent areas like the tab indicator and block selection. We want these areas to pop and gain your attention while also adding some visual flair. We needed to add another color attribute which we naturally called the accent color.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcevl8ug0crphzlmrj2yf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcevl8ug0crphzlmrj2yf.png" alt="Warp Dark Theme" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being able to change the accent color gave themes a wider range of customization just from one color change without changing the look and functionality of the core theme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f7Jx4CCA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1646741107395/bjEjFGQfr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f7Jx4CCA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://cdn.hashnode.com/res/hashnode/image/upload/v1646741107395/bjEjFGQfr.gif" alt="color change.gif" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  UI theming
&lt;/h2&gt;

&lt;p&gt;There is a bit more UI to cover within Warp like contextual menus, auto suggestion overlays, dialogs, command palette. The text coloring could easily be inherited from the theme and the accent color used for the same conditions, though we wanted the UI surface to stand out from the background. The last thing we wanted to do is to add yet another color attribute.&lt;/p&gt;

&lt;p&gt;Starting with dark and light themes we had to handle the UI layering in different ways. To achieve separation from the background for dark themes, we added a white overlay which aligns with the core text color, and for light themes the opposite, black overlay which also aligns with the core text color.&lt;/p&gt;

&lt;p&gt;We created a consistent style called “UI surface” which would be the background of all the overlay UI elements.&lt;/p&gt;

&lt;p&gt;It consists of the theme background color, the opposite overlay color and an outline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk99zakfnqjhwka04q7r5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk99zakfnqjhwka04q7r5.png" alt="image.png" width="371" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0ial6y9y35jdodss6px.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0ial6y9y35jdodss6px.png" alt="image.png" width="371" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This basic system allows for a different UI surface leveraging the same color system. Seen here in dark and light mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feud6ioxg3zqvjbxqle0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feud6ioxg3zqvjbxqle0e.png" alt="Warp Dark Mode" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe977xhorqgy5667nqlhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe977xhorqgy5667nqlhu.png" alt="Warp Light Mode" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The really neat part is how this easily extends to existing themes like &lt;a href="https://github.com/dracula/dracula-theme" rel="noopener noreferrer"&gt;Dracula&lt;/a&gt; and &lt;a href="https://github.com/altercation/solarized" rel="noopener noreferrer"&gt;Solarized&lt;/a&gt;. For existing themes we convert, we will choose an accent color from the core 16 colors that we feel match the best. If you want to change it, no sweat, you can customize the accent color to any of the 16 theme colors or any custom color you like to add your personal preference to existing themes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwp99zk0bmoq1g369854.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwp99zk0bmoq1g369854.png" alt="Dracula" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfbrexo1lkr9l7v2vcrz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfbrexo1lkr9l7v2vcrz.png" alt="Solarized Light" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Image backgrounds
&lt;/h2&gt;

&lt;p&gt;So with the standard theme color system and an added accent color attribute, we can easily customize the whole Warp UI. Yet we didn’t want to stop there, what about adding photos and advanced color treatment?&lt;/p&gt;

&lt;p&gt;We experimented with adding photo backgrounds with color themes that matched and loved the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99qd719ktpar4zue9085.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99qd719ktpar4zue9085.png" alt="Leafy image" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddzye765i9fu7qfymqmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddzye765i9fu7qfymqmq.png" alt="Leafy theme" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uddgxo14gwaiomb8ymv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uddgxo14gwaiomb8ymv.png" alt="Leafy theme with menus" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynp1h5e3oh7qasqncoxi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynp1h5e3oh7qasqncoxi.png" alt="Dark City image" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvah8ym2vqpwks4kfnr31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvah8ym2vqpwks4kfnr31.png" alt="Dark City theme" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm0f8vxj3r65nyf1zhmo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm0f8vxj3r65nyf1zhmo.png" alt="Dark City theme with menus" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also added the ability to support gradients, which can drastically change the look of a theme and add additional depth and visual pizzaz.&lt;/p&gt;

&lt;p&gt;Dracula regular:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxyyiihkqd02o7t4hyjmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxyyiihkqd02o7t4hyjmn.png" alt="Dracula regular" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dracula with gradients:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno1ictsu18yet1hjulpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno1ictsu18yet1hjulpz.png" alt="Dracula with gradients" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kz61xm78j1g9s4vs0f6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kz61xm78j1g9s4vs0f6.gif" alt="Image description" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want our user to have the power to make amazing themes, so we are working on a theme editor that makes customizing themes super simple. For lightweight use, you can start with our default dark or light theme and change the accent color. Choose one of many popular themes we have built into warp or import your favorite. If you want to go for even more customization, add a photo, make your accent color a gradient. With just a few customization you can radically change the way your terminal looks and feels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing
&lt;/h2&gt;

&lt;p&gt;Currently, there are ton of different sites and resources to get terminal themes, each of them with totally different ways to download themes, from a file download, github repo, etc.&lt;/p&gt;

&lt;p&gt;For Warp themes, we want to build a one stop shop for themes. One spot where you can browse, install and share themes all with one click. We also want individual themes to be easily shareable from user to user within the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check out themes now
&lt;/h2&gt;

&lt;p&gt;It’s been really exciting working on themes in Warp. Building a system that can accommodate existing themes, expanding on them within Warp and giving users more control to customize and share. We can’t wait to see what themes get created.&lt;/p&gt;

&lt;p&gt;You can check out the base theme experience in Warp now, where you can choose from a set of themes and &lt;a href="https://github.com/warpdotdev/themes/" rel="noopener noreferrer"&gt;customize via an external file&lt;/a&gt;. Shortly gradients and image support will be rolling out. Customization and sharing are down the road.&lt;/p&gt;

&lt;p&gt;We are all super excited to get all these theming features out, we have a bit of work ahead of us! Please let us know if there are any ideas that brew up, we would love your input!&lt;/p&gt;

&lt;p&gt;Request early access to our beta. We look forward to your input!&lt;/p&gt;

</description>
      <category>warp</category>
      <category>terminal</category>
      <category>product</category>
      <category>design</category>
    </item>
    <item>
      <title>What’s so special about PS1? Fun with customizing Bash command prompts</title>
      <dc:creator>cieplik</dc:creator>
      <pubDate>Thu, 17 Mar 2022 05:13:44 +0000</pubDate>
      <link>https://forem.com/warpdotdev/whats-so-special-about-ps1-fun-with-customizing-bash-command-prompts-345i</link>
      <guid>https://forem.com/warpdotdev/whats-so-special-about-ps1-fun-with-customizing-bash-command-prompts-345i</guid>
      <description>&lt;p&gt;Long ago, when I was still a console newbie, I copied my friend’s bash configuration file. It had all the necessary stuff already included - aliases, colors, and most importantly: a nice prompt setup. I used it on all machines I had access to due to all the extra context it provided. For example, it would turn red when I was on a production machine, show me a current git branch from the repository I was working on, and indicate whether I had any changes to commit by showing a star.&lt;/p&gt;

&lt;p&gt;Today there are a seemingly endless set of tools for configuring the command prompt to your liking, but back then things like &lt;a href="https://github.com/starship/starship" rel="noopener noreferrer"&gt;Starship&lt;/a&gt; were completely non-existent. Most developers I know have customized their prompts in one way or another. This fact, combined with the feedback we’ve received from the Warp community, was good motivation to dig deeper into prompt customization in Warp.&lt;/p&gt;

&lt;p&gt;In this post I’m going to tell you how we implemented support for PS1 in Warp, and why adding it was technically challenging. Along the way, we’ll also tackle the DCS (device control string) and some fun shell tricks!&lt;/p&gt;

&lt;h2&gt;
  
  
  Hey, Cieplik, what actually &lt;em&gt;is&lt;/em&gt; PS1?
&lt;/h2&gt;

&lt;p&gt;Glad you asked! PS1 is one of the few variables used by the shell to generate the prompt. As explained in the &lt;a href="https://www.gnu.org/software/bash/manual/bash.html" rel="noopener noreferrer"&gt;bash manual&lt;/a&gt;, PS1 represents the &lt;em&gt;primary prompt string&lt;/em&gt; (hence the “PS”) - which is what you see most of the time before typing a new command in your terminal. On top of that there are actually few more - from PS0 to PS4, each of them executed and printed in different contexts of command execution. For example, you’ll see PS2 whenever the command has multiple lines as a &lt;em&gt;secondary prompt string&lt;/em&gt;. And then zsh also provides RPS1, which displays the Prompt String on the right-hand side… there's a lot to work with.&lt;/p&gt;

&lt;p&gt;Each of the Prompt String variables can easily be customized with the &lt;a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Controlling-the-Prompt" rel="noopener noreferrer"&gt;backslash-escaped special characters&lt;/a&gt;, but they can also contain shell function calls or even emojis, since most modern terminals support unicode.&lt;/p&gt;

&lt;p&gt;With Warp in the works we made a decision to provide useful defaults for users to help them hit the ground running when using a new app. This included the prompt. We explicitly decided to not support PS1 in Warp, as it could clash with the &lt;a href="https://blog.warp.dev/how-warp-works/" rel="noopener noreferrer"&gt;bootstrapping script and PRECMD/PREEXEC hooks&lt;/a&gt; we use to create blocks of inputs and outputs. By implementing our own default prompt we had much more control over the user experience, and that was critical at the early stages. Eventually, however, it became obvious that our users really need their custom setups and lack of certain information on the prompt may actually slow them down. It came time for us to honor the user’s PS1 settings. Keep in mind that this is not our last word on this topic&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s under the Prompt String’s hood?
&lt;/h1&gt;

&lt;p&gt;In Warp, because we buffer the user’s input, we can’t allow the shell to directly render the prompt. Instead we use the metadata from the shell to do our own rendering. Our app receives it from the shell via the PRECMD hook, to do things like retrieve the current git branch, which is used in Warp’s default prompt. We use a special escape sequence and a JSON string packed with data to pass all the necessary information.&lt;/p&gt;

&lt;p&gt;This is why we decided that the best way to support PS1 in Warp is to pass it as part of our PRECMD hook. The first challenge, however, comes from &lt;em&gt;printing&lt;/em&gt; the variable into this JSON string.&lt;/p&gt;

&lt;p&gt;Your PS1 is usually a set of variables, like information about the colors you want to see, but sometimes it can be a function call too. We have to translate it into a &lt;em&gt;rendered&lt;/em&gt; Prompt String - a set of escape sequences and characters that tells the terminal the exact string to print and how to print it.&lt;/p&gt;

&lt;p&gt;Here’s the example of an oh-my-zsh PS1 setting&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; echo $PS1
%F{magenta}%n%f at %F{yellow}%m%f in %B%F{green}%~%f%b$(git_prompt_info)$(ruby_prompt_info)
$(virtualenv_info) $(prompt_char)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In zsh rendering a prompt is actually quite simple. You just &lt;code&gt;print&lt;/code&gt; it. (and the shell expands all parameters for you via &lt;a href="https://zsh.sourceforge.io/Doc/Release/Prompt-Expansion.html#Prompt-Expansion" rel="noopener noreferrer"&gt;prompt parameter expansion&lt;/a&gt;.) We insert the output into our JSON and display it on our terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646743812699%2FO91DgFaab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646743812699%2FO91DgFaab.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that the middle prompt is the output of the print command, prepended with "rendered prompt" for clarity.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bash does have a similar way of &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html" rel="noopener noreferrer"&gt;expanding prompt parameters&lt;/a&gt; as zsh does, so we could easily run &lt;code&gt;echo ${PS1@P}&lt;/code&gt; in our script to get the rendered prompt and call it a day! Except…&lt;/p&gt;

&lt;p&gt;That functionality has only been introduced in bash 4.4+. But Macs do not ship with that by default (this is related to a licensing change: since version 4.0, bash switched to a GPL license, which is not supported by Apple). The question, then, is how do we render the PS1 in older versions of bash without using any special tools or libraries?&lt;/p&gt;

&lt;p&gt;The way we ended up solving this issue is by invoking a subshell, executing a very simple command in it, capturing the entire subshell output (including the prompt), and then manipulating it to only capture the prompt itself.&lt;/p&gt;

&lt;p&gt;This is the final code we’re currently using in Warp to get the value of the rendered bash prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$(echo -e "\n"  | PS1="$WARP_PS1" bash --norc -i 2&amp;gt;&amp;amp;1 | head -2 | tail -1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break that down, pipe by pipe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;echo -e "\n"&lt;/code&gt;  prints an empty line in our subshell;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PS1="WARP_PS1" bash --norc -i 2&amp;gt;&amp;amp;1&lt;/code&gt; we pass a previously captured &lt;code&gt;$WARP_PS1&lt;/code&gt; value as the PS1 in the subshell, and specify that no configuration files should be loaded (norc argument) to improve the performance of this operation; the &lt;code&gt;-i&lt;/code&gt; flag denotes an interactive shell, while &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; redirects the stderr to stdout, which allows us to capture the rendered prompt;&lt;/li&gt;
&lt;li&gt;Head &amp;amp; tail operations simply help us manipulate the output to extract the prompt only.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. We've got our rendered prompt, we can start showing it in Warp!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646743936627%2FEmHL6K4UL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646743936627%2FEmHL6K4UL.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Escaping the Prompt
&lt;/h2&gt;

&lt;p&gt;Some things and places are really hard to escape, like &lt;a href="https://weirdrussia.com/2015/08/20/dont-try-to-leave-the-city-of-omsk/" rel="noopener noreferrer"&gt;the Russian city of Omsk, where even stray dogs cannot leave&lt;/a&gt;. Escaping in the terminal realm is really close to that experience, and Prompt String seems to make it even harder.&lt;/p&gt;

&lt;p&gt;When juggling shell data and passing it to Warp via our PRECMD hook, we escape &lt;em&gt;escape sequences&lt;/em&gt; that may break our JSON string with our magic sed invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sed -E 's/(["\])/\\1/g; s/''\t''/\t/g; s/''\r''/\r/g; /\n/'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is actually a series of replacements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All double-quotes and backslashes are replaced with their escaped versions (&lt;code&gt;\”&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;\b&lt;/code&gt; (backspace), &lt;code&gt;\t&lt;/code&gt; (tab), &lt;code&gt;\f&lt;/code&gt; (form feed), &lt;code&gt;\r&lt;/code&gt; (carriage return) are all replaced with their escaped versions;&lt;/li&gt;
&lt;li&gt;We add an escaped &lt;code&gt;\n&lt;/code&gt; (new line) on every line explicitly, since sed analyzes the input line-by-line and thus is not aware of the actual new lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This worked great, yet with the Prompt String you actually get many more things to escape: it’s not uncommon for the rendered prompt to include some extra bells and whistles (I mean the actual bell character - &lt;code&gt;\a&lt;/code&gt;) and lots of other non-printable characters and escape sequences (most significant being &lt;code&gt;\x1b&lt;/code&gt; aka &lt;code&gt;\e&lt;/code&gt; or &lt;code&gt;\033&lt;/code&gt;, which is literally &lt;a href="https://www.asciitable.com/" rel="noopener noreferrer"&gt;ESC&lt;/a&gt;). Note that those sequences also had to be &lt;em&gt;double escaped&lt;/em&gt;, to not only create a valid JSON string, but also not break our Rust implementation when &lt;em&gt;unescaped&lt;/em&gt;! At least we were able to use the same tool for both supported shells this time!&lt;/p&gt;

&lt;p&gt;In the first iteration, the final sed call looked a lot like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sed -E 's/'$'\e''/\\e/g; s/'$'\a''/\\a/g;  s/(["\\])/\\\1/g; s/'$'\b''/\\b/g; s/'$'\t''/\\t/g; s/'$'\f''/\\f/g; s/'$'\r''/\\r/g; $!s/$/\\n/'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Back to the whiteboard
&lt;/h2&gt;

&lt;p&gt;At this point, we started testing internally. It was then that we found a mysterious behavior.&lt;/p&gt;

&lt;p&gt;A common tool used to customize the prompt in zsh - &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;oh-my-zsh&lt;/a&gt; - is used by many team members. The prompt rendering worked for every theme...except the &lt;a href="https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme" rel="noopener noreferrer"&gt;default oh-my-zsh theme&lt;/a&gt;. When the theme was used, the entire bootstrapping script would fail, leaving Warp in an unusable state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744181698%2F4j7EXqg-m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744181698%2F4j7EXqg-m.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Robby Russell’s oh-my-zsh prompt theme&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It turned out the little arrow (➜) to the left was the root cause!&lt;/p&gt;

&lt;p&gt;With shells and terminal emulators it always makes sense to analyze the actual bytes that are being processed. Let's check what’s under the hood of this little arrow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744215101%2FvvQfUax_x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744215101%2FvvQfUax_x.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This particular emoji consists of 4 bytes: e2, 9e, 9c and 0a. A careful reader may notice that both 9e and 9c bytes come from the extended ASCII table, but do they carry any special meaning? When in doubt, it’s always good to go back to the source - in this case, check the &lt;a href="https://vt100.net/emu/dec_ansi_parser" rel="noopener noreferrer"&gt;shell parser documentation&lt;/a&gt;. For completeness, let's quickly unpack what VT100 (and other VTs) is, and what this mysterious DCS is that are both mentioned in the linked docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VT100 was a &lt;strong&gt;V&lt;/strong&gt;ideo &lt;strong&gt;T&lt;/strong&gt;erminal introduced in the 70s. It was one of the first machines that allowed for cursor control with ANSI-escape codes, and added a bunch of other control options. Later on its spec became a de facto standard, and modern terminal emulator programs try to follow it (including Warp).&lt;/p&gt;

&lt;p&gt;Control characters and control sequences in the terminal world are special ANSI-escape characters that control the terminal’s behavior. It can be anything from the cursor position, mouse control, even colors. &lt;strong&gt;DCS&lt;/strong&gt; (device control string) is a special control sequence that is followed by a data string. You can find out more &lt;a href="https://vt100.net/docs/vt510-rm/chapter4.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744289174%2FZ-zHLowSr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646744289174%2FZ-zHLowSr.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  unhook
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;When a device control string is terminated by ST, CAN, SUB or ESC, this action calls the previously selected handler function with an “end of data” parameter. This allows the handler to finish neatly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As it turned out - the 9c byte carried a special meaning in a terminal world - it’s an &lt;a href="https://vt100.net/docs/vt510-rm/chapter4.html" rel="noopener noreferrer"&gt;ST (string terminator) escape sequence&lt;/a&gt;, which also happens to denote the &lt;em&gt;unhook&lt;/em&gt; operation. As a result, the emoji in our JSON string would prematurely end the PRECMD hook, making it impossible for Warp to fully start.&lt;/p&gt;

&lt;p&gt;From there the solution was simple - rather than trying to escape emojis and unicode characters that may have similar issues in the future, we simply encoded the entire Prompt String as a hexadecimal string. This completely eliminated the need for using sed and simplified our shell script.&lt;/p&gt;

&lt;p&gt;Below you'll find a snippet with our current precmd function (example in bash):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   warp_escape_ps1 () {
           tr '\n\n' ' ' &amp;lt;&amp;lt;&amp;lt; "$*" | xxd -p | tr -d '\n'
        }

                # Format a string value according to JSON syntax - Adapted from https://github.com/lework/script.
        warp_escape_json () {
            # Explanation of the sed replacements (each command is separated by a `;`):
            # s/(["\\])/\\\1/g - Replace all double-quote (") and backslash (\) characters with the escaped versions (\" and \\)
            # s/\b/\\b/g - Replace all backspace characters with \b
            # s/\t/\\t/g - Replace all tab characters with \t
            # s/\f/\\f/g - Replace all form feed characters with \f
            # s/\r/\\r/g - Replace all carriage return characters with \r
            # $!s/$/\\n/ - On every line except the last, insert the \n escape at the end of the line
            #              Note: sed acts line-by-line, so it doesn't see the literal newline characters to replace
            #
            # tr -d '\n' - Remove the literal newlines from the final output
            #
            # Additional note: In a shell script between single quotes ('), no escape sequences are interpreted.
            # To work around that and insert the literal values into the regular expressions, we stop the single-quote,
            # then add the literal using ANSI-C syntax ($'\t'), then start a new single-quote. That is the meaning
            # behind the various `'$'\b''` blocks in the command. All of these separate strings are then concatenated
            # together to form the full argument to send to sed.
            sed -E 's/(["\\])/\\\1/g; s/'$'\b''/\\b/g; s/'$'\t''/\\t/g; s/'$'\f''/\\f/g; s/'$'\r''/\\r/g; $!s/$/\\n/' &amp;lt;&amp;lt;&amp;lt;"$*" | tr -d '\n'
        }

        warp_precmd () {
            # $? is relative to the process so we MUST check this first
            # or else the exit code will correspond to the commands
            # executed within this block instead of the actual last
            # command that was run.
            local exit_code=$?
            # Clear the prompt again before the command is rendered as it could
            # have been reset by the user's bashrc or by setting the variable
            # on the command line.
            if [[ -n $PS1 ]]; then
              WARP_PS1="$PS1"
            fi
            unset PS1
            unset PROMPT
            # Escaped PS1 variable
            local escaped_ps1
            if [[ $WARP_FEATURE_FLAG_HONOR_PS1 == "1" ]]; then
              # Tricking the shell into rendering the prompt
              # Note that in more modern versions of bash we could use ${PS1@P} to achieve the same,
              # but macOs comes by default with a much older version of bash, and we want to be compatible.
              deref_ps1=$(echo -e "\n"  | PS1="$WARP_PS1" bash --norc -i 2&amp;gt;&amp;amp;1 | head -2 | tail -1)
              escaped_ps1=$(warp_escape_ps1 "$(echo "$deref_ps1")")
            fi
            # Flush history
            history -a
            # Reset the custom kill-whole-line binding as the user's bashrc (which is sourced after bashrc_warp)
            # could have added another bind. This won't have any user-impact because these shortcuts are only run
            # in the context of the bash editor, which isn't displayed in Warp.
            bind -r '"\C-p"'
            bind "\C-p":kill-whole-line
            local escaped_pwd
            escaped_pwd=$(warp_escape_json "$PWD")
            local escaped_virtual_env=""
            if [ ! -z "$VIRTUAL_ENV" ]; then
                escaped_virtual_env=$(warp_escape_json "$VIRTUAL_ENV")
            fi
            local escaped_conda_env=""
            if [ ! -z "$CONDA_DEFAULT_ENV" ]; then
                escaped_conda_env=$(warp_escape_json "$CONDA_DEFAULT_ENV")
            fi
            local git_branch
            git_branch=$(git rev-parse --abbrev-ref HEAD 2&amp;gt;/dev/null || echo "")
            local escaped_git_branch
            escaped_git_branch=$(warp_escape_json "$git_branch")
            # At this point, escaped prompt looks something like
            # \\u{001B}\\u{005B}\\u{0030}\\u{0031}\\u{003B} ...
            # We need to maintain the double quoting of \\u in the message that
            # is sent otherwise the receiving side will interpret the value
            # as JS string literals of the form \uHEX, and will include
            # ctrl characters (like ESC) in the json, which will cause a JSON
            # parse error.
            # Note WARP_SESSION_ID doesn't need to be escaped since it's a number
            local escaped_json="{\"hook\": \"Precmd\", \"value\": {\"pwd\": \"$escaped_pwd\", \"ps1\": \"$escaped_ps1\", \"git_branch\": \"$escaped_git_branch\", \"virtual_env\": \"$escaped_virtual_env\", \"conda_env\": \"$escaped_conda_env\", \"exit_code\": $exit_code, \"session_id\": $WARP_SESSION_ID}}"
            warp_send_message "$escaped_json"
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Future of the prompt
&lt;/h2&gt;

&lt;p&gt;Warp now has the option to honor the user's PS1 setting. It works with most configurations, though you’ll find the full compatibility table in our &lt;a href="https://docs.warp.dev/features/prompt" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is not, however, the last time you'll hear from us about working on the prompt. Our &lt;a href="https://blog.warp.dev/how-we-design-warp-our-product-philosophy/" rel="noopener noreferrer"&gt;key product principles&lt;/a&gt; include fixing the UI and providing a great out-of-the box experience. One of the ideas we're currently exploring is Context Chips - our low-calorie approach to snacking on bite-size information.&lt;/p&gt;

&lt;p&gt;We’re working on plenty of interesting problems just like this one at Warp; and we’re hiring! If you want to be a part of &lt;a href="https://blog.warp.dev/whos-behind-warp-meet-the-team/" rel="noopener noreferrer"&gt;our team&lt;/a&gt; and work together on improving the day-to-day life of developers from around the world - check out our &lt;a href="https://www.warp.dev/hiring" rel="noopener noreferrer"&gt;careers&lt;/a&gt; page.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fantastic terminal programs and how to quit them</title>
      <dc:creator>Zheng Tao</dc:creator>
      <pubDate>Thu, 17 Mar 2022 05:12:31 +0000</pubDate>
      <link>https://forem.com/warpdotdev/fantastic-terminal-programs-and-how-to-quit-them-ad6</link>
      <guid>https://forem.com/warpdotdev/fantastic-terminal-programs-and-how-to-quit-them-ad6</guid>
      <description>&lt;p&gt;There’s a popular meme about exiting vim.&lt;/p&gt;

&lt;p&gt;More earnestly, there’s even a &lt;a href="https://www.linkedin.com/learning/learning-vim" rel="noopener noreferrer"&gt;LinkedIn learning course&lt;/a&gt; on how to use vim. The first module is dedicated to how to exit.&lt;/p&gt;

&lt;p&gt;Why is it so hard? This seems like an odd problem to have. When’s the last time you’ve thought about exiting your web browser, MSWord, or any other application on your computer?&lt;/p&gt;

&lt;p&gt;It’s not just Vim, but the user experience on the terminal as a whole.&lt;/p&gt;

&lt;p&gt;The terminal predates human computer interaction. It existed before Xerox PARC lab &amp;amp; Apple Computer worked on personal computing and GUIs. Apple introduced some human interface guidelines in 1977, which have been copied pretty much everywhere else -- but the terminal has stayed the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739851928%2F1obBGIT4C.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739851928%2F1obBGIT4C.png" alt="Progression of computers over time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s true that some popular terminal programs were created after this all happened, but there’s a big difference in their philosophy of the user.&lt;/p&gt;

&lt;p&gt;Today, it’s not revolutionary to say, as Apple did in their human interface guidelines, that “people are not trying to use computers—they’re trying to get their jobs done.” In the terminal, it’s just the opposite: for example, people are trying to use tmux to experience &lt;a href="https://leanpub.com/the-tao-of-tmux/read#leanpub-auto-foreword" rel="noopener noreferrer"&gt;the tao of tmux.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The terminal is hard to use. Interactive terminal programs are particularly difficult and they behave unlike anything else on your computer.&lt;/p&gt;

&lt;p&gt;As intimidating it is to get started, it can be extremely valuable to know how to use these programs. They are cross-platform, available on even the most limited environments, keyboard-accessible, and if you’re on a remote host it’s often the best option to accomplish some task.&lt;/p&gt;

&lt;p&gt;Here’s the basic ins-and-(literally!)outs for a bunch of programs, why they can be non intuitive, and why other modern apps aren’t difficult in the same way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739905737%2Fmo52URc7G.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739905737%2Fmo52URc7G.png" alt="Terminal meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739918264%2FXxF_CuSyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739918264%2FXxF_CuSyj.png" alt="Tar meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vim
&lt;/h2&gt;

&lt;p&gt;Vim has a number of different modes. By default, it’s in “normal” mode. Hitting ‘i’ will get you into “insertion” mode which edits the text content directly. You’ll need to get out of insertion mode (esc) to communicate a quit command to vim.&lt;/p&gt;

&lt;p&gt;We’re used to modeless text editors today. They’re the default editors everywhere: on computers, phones and tablets – natively and on the web.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739947074%2Ft6BTYxeo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739947074%2Ft6BTYxeo6.png" alt="No modes license plate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;License plate of Larry Tesler, “Primary inventor of modeless editing and cut, copy, paste.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you are in any mode aside from normal mode, it’s communicated in the bottom left. Many tools use a similar status bar UI. This can be easy to miss if you don’t already know to look here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739979736%2FsBWD2NyLo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739979736%2FsBWD2NyLo.png" alt="Status bar in Vim"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Status bar in Vim&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When they do use modes, other apps today clearly indicate the mode. If you go from an editing mode to viewing mode in GDocs, you can’t miss it. Any design app will also change the cursor to indicate what you’re doing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740002615%2F9gc3QwuCe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740002615%2F9gc3QwuCe.png" alt="Changing modes in Figma"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Changing modes in Figma&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From normal mode in Vim, we can type &lt;code&gt;:&lt;/code&gt; to get into Cmd-line mode. This makes the cursor jump from the content to the bottom-left, and vim commands can be typed.&lt;/p&gt;

&lt;p&gt;Finally, we can type &lt;code&gt;q &amp;lt;enter&amp;gt;&lt;/code&gt; to quit.&lt;/p&gt;

&lt;p&gt;If you have some unsaved content, vim won’t let you quit, to ensure you don’t lose anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740052176%2Fi8XIAIUD1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740052176%2Fi8XIAIUD1.png" alt="Error quitting vim"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One option is to go nuclear and force quit &lt;code&gt;:q!&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Otherwise, you can save the file. If there’s already a filename, &lt;code&gt;:wq&lt;/code&gt; or &lt;code&gt;:x&lt;/code&gt; will let you save and quit. If you did come from the empty state, there’s no file here yet so you will have to explicitly save to a file (:w filename) before quitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emacs
&lt;/h2&gt;

&lt;p&gt;The keybinding to quit emacs is &lt;code&gt;Ctrl-X&lt;/code&gt; + &lt;code&gt;Ctrl-C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This key sequence is more complicated - and I’ve definitely messed it up before when doing this the first couple times.&lt;/p&gt;

&lt;p&gt;Emacs does give some visual feedback in the bottom-left corner after typing the first &lt;code&gt;Ctrl-X&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740130173%2FQZe7NUZkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740130173%2FQZe7NUZkp.png" alt="Emacs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’ll also tell you if you messed up in the same place. But you can also inadvertently type a bunch of stuff that edits the file, and you won’t necessarily get this feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740152708%2F0uH2m2FB1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740152708%2F0uH2m2FB1.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Relative to vim, it’s a little easier to deal with editing a file and saving it, because emacs will ask you how to deal with an unsaved file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740167332%2F7kXZ2VKjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740167332%2F7kXZ2VKjc.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Emacs doesn’t have modality, but its shortcuts are more complicated in general. A lot of functionality in emacs uses the Meta key. For example Meta-f and Meta-b move forward and back one word respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740183696%2Fq_l6xCqeC.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740183696%2Fq_l6xCqeC.png" alt="Old keyboard with Meta key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it used to be a thing, modern keyboards don’t include the Meta key. Most terminal emulator apps will let users map some other key to imitate Meta instead.  It’s not typically on by default, and sometimes one has to dig quite a lot to do this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740204238%2FgW2Smvtfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740204238%2FgW2Smvtfp.png" alt="Mapping Option to Meta in iTerm2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mapping Option to Meta in iTerm2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We generally won’t see key sequences this complicated in other places on the computer outside of the terminal. At the very least, other apps are not going to use a key that no longer exists and put the burden on the user to figure out how to make it available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nano
&lt;/h2&gt;

&lt;p&gt;Quitting nano is not hard relative to other things. There’s a status bar that hints Ctrl-X will exit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740232569%2F6sJoy2spL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646740232569%2F6sJoy2spL.png" alt="Nano editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many people have found themselves in nano because git or arc put them there, even when they have not chosen it as an editor.&lt;/p&gt;

&lt;p&gt;This is some pretty unforgiving UX. It’s rare outside of the terminal to end up in an application where you may not know how to exit.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tmux
&lt;/h1&gt;

&lt;p&gt;Tmux is a terminal multiplexer. It provides a lot of functionality, and one basic way to think of it is a way to manage multiple running shells arranged in a number of “windows” + panes.&lt;/p&gt;

&lt;p&gt;For tmux, any attempt to communicate directly to tmux involves typing &lt;code&gt;Ctrl-b&lt;/code&gt; (we call this the prefix key). There are two main options for getting out: &lt;code&gt;Ctrl-b&lt;/code&gt; + &lt;code&gt;d&lt;/code&gt; will detach the screen session. &lt;code&gt;Ctrl-b&lt;/code&gt;+ &lt;code&gt;:kill-session&lt;/code&gt; kills the current session.&lt;/p&gt;

&lt;p&gt;When typing command sequences after the prefix key, no feedback is given and you’ll have to internalize these commands to use tmux.&lt;/p&gt;

&lt;p&gt;Remembering the one prefix key is not as hard as remembering all the commands to work regularly in a terminal multiplexer. In my experience, pros mess up basic tmux commands &lt;a href="https://youtu.be/JeOSpnT29go?t=308" rel="noopener noreferrer"&gt;again&lt;/a&gt; and &lt;a href="https://youtu.be/5r6yzFEXajQ?t=2786" rel="noopener noreferrer"&gt;again&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These programs epitomize &lt;em&gt;remember-and-type&lt;/em&gt; over &lt;em&gt;see-and-point&lt;/em&gt;. As Apple’s human interface guidelines puts it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Command-line interfaces require the user to remember a command and type it onto the computer. This kind of interface makes considerable demands on the user’s memory -- especially when the commands are complex or cryptic. Such an interface is especially galling to the new or infrequent user, but it distracts all users from their task and focuses attention instead on the computer’s needs.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Furthermore, “users rely on recognition, not recall; they shouldn’t have to remember anything the computer already knows.”&lt;/p&gt;

&lt;p&gt;Developers know this principle very well when it comes to their code. Djikstra famously said “the competent programmer is fully aware of the limited size of his own skull. He therefore approaches his task with full humility, and avoids clever tricks like the plague.” The mantra DRY (Don’t Repeat Yourself) is programmer dogma and is present in many programming books and classes. This principle has not made it into the terminal’s user interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Every program here consistently quits in a different way from all the other ones.&lt;/p&gt;

&lt;p&gt;A final point here is, even though we are used to discussing these commands as “keyboard shortcuts”, these are not alternatives but the only way to run the command. Popular guides will also encourage usage of these programs by saying they are “keyboard accessible." The idea that keystrokes can provide a productivity boost but shouldn’t be the only interface is ingrained in the English language.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Program&lt;/th&gt;
&lt;th&gt;How to exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;vim&lt;/td&gt;
&lt;td&gt;&lt;ul&gt;
&lt;li&gt;Get out of insertion mode by hitting esc&lt;/li&gt;
&lt;li&gt;Type :q&lt;/li&gt;
&lt;/ul&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emacs&lt;/td&gt;
&lt;td&gt;Ctrl-x Ctrl-c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nano&lt;/td&gt;
&lt;td&gt;Ctrl-x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vim&lt;/td&gt;
&lt;td&gt;&lt;ul&gt;
&lt;li&gt;Ctrl-b + d (detaches session)&lt;/li&gt;
&lt;li&gt;Ctrl-b + :kill-session to kill session&lt;/li&gt;
&lt;/ul&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;screen&lt;/td&gt;
&lt;td&gt;&lt;ul&gt;
&lt;li&gt;Ctrl-a + d (detaches session)&lt;/li&gt;
&lt;li&gt;Ctrl-a + k (kill session)&lt;/li&gt;
&lt;/ul&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal terminal session&lt;/td&gt;
&lt;td&gt;Ctrl-d (End-of-Transmission / EOT)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;At Warp, we believe the terminal also can be a delightful &lt;em&gt;product&lt;/em&gt;, in addition to a powerful utility. We are building a terminal from the ground-up with the user in mind.&lt;/p&gt;

&lt;p&gt;Request early access to our beta. We look forward to your input!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Who’s behind Warp? - Meet the team</title>
      <dc:creator>cieplik</dc:creator>
      <pubDate>Thu, 17 Mar 2022 05:10:57 +0000</pubDate>
      <link>https://forem.com/warpdotdev/whos-behind-warp-meet-the-team-235k</link>
      <guid>https://forem.com/warpdotdev/whos-behind-warp-meet-the-team-235k</guid>
      <description>&lt;p&gt;My idea for a perfect opening line of this blog post was to use a quote about the importance of building a great team. I scrolled through pages of quotes from personas such as Henry Ford, or Michael Jordan, eventually settling on… nothing. We all know that teamwork is critical and a huge part of engineering work, no need to back this up by famous people’s quotes! It becomes even more important in small companies and startups, such as Warp—where everyone works together to deliver the best terminal experience to other developers. In the last post we uncovered a little bit about &lt;a href="https://blog.warp.dev/how-we-design-warp-our-product-philosophy/" rel="noopener noreferrer"&gt;how we think about building the product&lt;/a&gt;; in today’s, I’ll introduce you to people behind Warp and the story of how we built the new terminal!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did I join Warp?
&lt;/h2&gt;

&lt;p&gt;Let me start by telling you my part of this story.&lt;/p&gt;

&lt;p&gt;As a person who rides a 700lbs motorcycle that doesn’t have a reverse (try parking that thing!) and a digital clock is its most advanced piece of technology; who writes her code in Vim and still mixes all the ingredients for the cake manually—I clearly like to... do things in a more traditional way. No wonder the terminal has been my main tool ever since I started working as an engineer.&lt;/p&gt;

&lt;p&gt;I was initially very skeptical about Warp—after all bash scripts, &lt;code&gt;grep,&lt;/code&gt;sed&lt;code&gt;and&lt;/code&gt;awk` can already get you a pretty long way when working with the terminal, right? I was still curious about what the company has to offer, and that’s why I joined one of the first user research sessions. I even borrowed a MacOS laptop specifically for this!&lt;/p&gt;

&lt;p&gt;This was the first time I met the entire Warp team and got to try the application. Learning about the roadmap (especially the team features that will improve efficiency and safety of devops work!) and seeing how user-focused the team is, was what got me excited about the product and the company. 2 months ago I quit my job as an Infrastructure Software Engineer in Dropbox, and joined Warp—the company that builds a next-gen Rust-based terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did Zach decide to build a terminal?
&lt;/h2&gt;

&lt;p&gt;Zach, whom you might’ve seen on &lt;a href="https://twitter.com/zachlloydtweets" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or a recent &lt;a href="https://www.youtube.com/watch?v=bZT09V5m8ak" rel="noopener noreferrer"&gt;webinar&lt;/a&gt; (talking about Warp, obviously), is the CEO of Warp. He previously worked as a Principal Engineer at Google, CTO at Time and co-founded the venture-backed startup SelfMade, yet his interests circle around improving the developer’s experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646738203321%2FCl6Vhd2SY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646738203321%2FCl6Vhd2SY.png" alt="Zach Lloyd at the Warp Arizona retreat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Zach Lloyd, Warp's CEO&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There’re plenty of tools around that improve the development process in one way or another—focusing on a broad group of engineers, or on a very specific niche. However, only 2 are truly used by almost every programmer there is—they are IDEs and code editors or a terminal. The former already went a long way—we’ve had Eclipse and JetBrains, or IntelliJ; there was notepad and Atom and Sublime; and nowadays VSCode seems to be the &lt;a href="https://insights.stackoverflow.com/survey/2019#development-environments-and-tools" rel="noopener noreferrer"&gt;majority’s favorite IDE and code editing tool.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, the terminal stayed the same for almost 40 years (and still counting)! It’s slowly evolving with extra functionality coming from new shells (like &lt;a href="https://fishshell.com/" rel="noopener noreferrer"&gt;fish&lt;/a&gt;) or prompt-tools (like &lt;a href="https://starship.rs/" rel="noopener noreferrer"&gt;starship.rs&lt;/a&gt;). Yet, the main experience has stayed the same, and modern terminals still emulate the physical ones, implementing the subset of functionality from &lt;a href="https://www.vt100.net/" rel="noopener noreferrer"&gt;VT100&lt;/a&gt; (hence the correct term is actually “terminal emulator”). Lack of innovation in this area is what made Zach take a closer look at building a terminal. And that’s how Warp was born.&lt;/p&gt;

&lt;h2&gt;
  
  
  People behind Warp
&lt;/h2&gt;

&lt;p&gt;Soon, Shikhiu, Michelle, and Aloke joined Zach in the effort of working on Warp. The initial prototype was an Electron app, though the poor performance pushed the team to pivot and rewrite the application in Rust. Besides making Warp an OS-native, and blazingly fast application which utilizes GPU rendering, Rust is a language with a strong community support and tools that make it easy to ramp up new developers (Rustlings, we’re looking at you!). As a result the team (with support from Nathan Sobo—the Atom editor creator) created a Rust UI framework which we plan to open source in the near future.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646738411080%2FGgPmnE2qJ.png"&gt;&lt;/td&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646738427107%2FdHQLzogFH.png"&gt;&lt;/td&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646738438358%2FrHSKr4y1i.png"&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Michelle used to work as an engineering intern in Facebook, Slack, and Robinhood. She also pursued an entrepreneurial path co-founding the first health tech incubator in Yale and creating her own health tech app during her college years. Viral growth and user feedback loops are clearly her thing, however, not the only thing. Michelle has a black belt in taekwondo; loves watching movies and analyzing them for hours afterwards; and recently she started to learn how to drive (on the streets of New York City!).&lt;/p&gt;

&lt;p&gt;The only person who did not pick up Rust (just yet), is Shikhiu. His excuse is being the Founding Designer. As a master of Figma, Shikhiu creates mockups of new Warp features in minutes, helping the team visualize the work and iterate faster. He recently told me he’s into plants, which would explain why the “leafy” terminal theme (soon to ship in Warp) is so beautiful. He used to work at Adobe and later on at Google where he led a team of 30+ designers on design for the Docs suite, yet (luckily for us) he chose to join us to reimagine the terminal product experience!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739317144%2FWJgfFhKqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739317144%2FWJgfFhKqc.png" alt="Custom themes in Warp"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Work in progress of themes in Warp - with "leafy" being one of the concepts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Working as a TL for Google Docs, Aloke had already collaborated with Shikhiu during his time in Google. He also came across multiple TODOs left in the Google Docs codebase by the mysterious &lt;code&gt;zachlloyd&lt;/code&gt;, but hadn't had a chance to work with Zach prior to joining Warp. Before his interview with Warp, Aloke dropped a very sharp knife on his foot, but decided to tough it up and pass the interview with a bleeding foot. This definitely showed how dedicated he is, though we are all glad that he saw a doctor and his foot is now fully healed… Besides cooking as a hobby, Aloke lifts (not just our spirits) and is very much into public transportation. He gets triggered by lack of periods at the end of the comment, since that makes it an &lt;em&gt;incomplete&lt;/em&gt; sentence—I like to think it was the reason he started working on &lt;em&gt;completions&lt;/em&gt; in Warp.&lt;/p&gt;




&lt;p&gt;Currently, Warp consists of 7 engineers with Zheng, Chuck, Kevin and me joining over the course of the last 6 months.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739388413%2FyS8H_2pwl.png"&gt;&lt;/td&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739396415%2FBpOcZllhg.png"&gt;&lt;/td&gt;
  &lt;/tr&gt;
  
  &lt;tr&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739405015%2FgdgomTbrl.png"&gt;&lt;/td&gt;
    &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646739412833%2F44BRRKXyU.png"&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Zheng, Chuck, Kevin &amp;amp; me all wearing our Warp's T-Shirts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Chuck plays a critical role in the team since he’s the only person with prior Rust experience. Besides being a mentor, who leaves helpful and constructive feedback in our PRs, Chuck collects &lt;a href="https://us.pez.com/" rel="noopener noreferrer"&gt;Pez candy dispensers&lt;/a&gt; and can solve a Rubik’s cube in under 1 minute. One of the things that drew him to Warp is being able to improve developers’ experience first hand—he used to work in the devtools team in LinkedIn and maintains &lt;a href="https://volta.sh/" rel="noopener noreferrer"&gt;Volta&lt;/a&gt; tool manager. Working on the terminal was a logical next step.&lt;/p&gt;

&lt;p&gt;After a few years of working at Dropbox, Zheng decided to dive into the startup world. First, she worked for Gem, and later on met Zach and the team. She deeply cares about the work culture, so her joining Warp says a lot about the company. She likes to travel, pet cute animals and drink good wine; and also she likes to dig really deep into why and how our users use the terminal and other tools day to day. That’s why her current main focus is driving the tmux-like experience in Warp.&lt;/p&gt;

&lt;p&gt;Kevin is our most recent hire. He joined as a full-time engineer only a month ago, but has been working part-time at Warp for the past year. He spent his college years traveling to different countries while continuing the education remotely and recently graduated. As part of his international adventures, he interned in a Japanese company in Tokyo for 2 summers, and thanks to that polished not only his engineering skills, but also learned to speak the language! During his internships he tried himself in different fields, including data science and ML, and figured that he’s really interested in developing tools for other engineers, wanted to work on something that will have a direct impact on users and was pretty much a blank canvas. Warp matched his interests perfectly.&lt;/p&gt;




&lt;p&gt;Over the summer, we also worked with Saumya &amp;amp; Matt—summer interns who delivered functionality such as notification on long running commands and custom theming. Big thanks to both of you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Working at Warp
&lt;/h2&gt;

&lt;p&gt;The team works remotely and is spread across different time zones (though we recently opened an optional office space in NYC!). The company was founded during the peak of COVID-19 pandemic, so it couldn’t have been different anyways.&lt;/p&gt;

&lt;p&gt;We meet daily to discuss progress and show demos of new features, and organize more Zoom meetings whenever necessary. As a group of very opinionated people (who in the tech industry isn’t?), we sometimes tend to stay online longer, discussing new ideas, making sure that everybody has a space to express their point of view. Somehow we still manage to make the majority of the decisions unanimously - unless it’s about the fate of the &lt;code&gt;ctrl-r&lt;/code&gt; keybinding… In those moments of doubt, Blue’s (Zach’s dog) appearance in the background cheers everyone up, especially when he tries to eat our CEO’s dinner. Altersnatively, Backup (my dog) sometimes comes to the standup and adds his voice to the pool of our opinions.&lt;/p&gt;

&lt;p&gt;When it comes to figuring out the road map for Warp, we try not to rely on our personal views only. Hence, the closed beta program and user focus groups - both helping us learn what’s the right set of features to build next. Zach wrote about our &lt;a href="https://blog.warp.dev/how-we-design-warp-our-product-philosophy/" rel="noopener noreferrer"&gt;8 product principles&lt;/a&gt; in a separate post, and you can read more about the team’s culture and approach on his blog: &lt;a href="https://thezbook.com/" rel="noopener noreferrer"&gt;thezbook.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;And most importantly—if you feel like our culture suits you and you’re passionate about bringing the developers’ experience to the next level—we are &lt;a href="https://www.warp.dev/hiring" rel="noopener noreferrer"&gt;hiring&lt;/a&gt; and would love to work with you!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Warp’s product principles for reinventing the terminal</title>
      <dc:creator>Warp</dc:creator>
      <pubDate>Thu, 17 Mar 2022 04:56:58 +0000</pubDate>
      <link>https://forem.com/warpdotdev/warps-product-principles-for-reinventing-the-terminal-30bb</link>
      <guid>https://forem.com/warpdotdev/warps-product-principles-for-reinventing-the-terminal-30bb</guid>
      <description>&lt;p&gt;At Warp, we believe we can keep what’s best about the command-line while fixing its pain points and adding super-powers.  &lt;/p&gt;

&lt;p&gt;Our goal is that using Warp every developer should be as productive as a CLI veteran.&lt;/p&gt;

&lt;p&gt;Here are the eight principles that guide our approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  #1: Meet developers where they are.
&lt;/h3&gt;

&lt;p&gt;This means that our terminal should be backwards compatible.  A developer using it for the first time should be at least as productive as they were in their old terminal.  Practically speaking, this means we are creating a terminal that works with developers’ existing shells, scripts, and key bindings.&lt;/p&gt;

&lt;h3&gt;
  
  
  #2: Keep the power, but fix the UI.
&lt;/h3&gt;

&lt;p&gt;We don’t see any reason why the terminal can’t have a more modern, intuitive interface, while maintaining the power of a primarily textual, keyboard driven experience.  If a user wants to use the mouse to position the cursor, why not support that?  Why not have a native autocomplete and search experience?  Our goal is to &lt;strong&gt;create the UX that best supports what developers use&lt;/strong&gt; the tool to accomplish, and we are pragmatic about whether that UX is character based or pixel based and whether the input mechanism is the keyboard or the mouse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mrukc5gstorzv92gjoq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mrukc5gstorzv92gjoq.jpeg" alt="Warp Blocks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We introduced blocks to help you navigate your work in the terminal.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  #3: A great out-of-the-box experience.
&lt;/h3&gt;

&lt;p&gt;There are many different plugins, themes, and open-source projects that improve aspects of the CLI; e.g. &lt;a href="https://github.com/tmux/tmux/wiki" rel="noopener noreferrer"&gt;tmux&lt;/a&gt;, &lt;a href="https://mosh.org/" rel="noopener noreferrer"&gt;mosh&lt;/a&gt;, &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;OhMyZsh&lt;/a&gt;, and &lt;a href="https://github.com/romkatv/powerlevel10k" rel="noopener noreferrer"&gt;powerlevel10k&lt;/a&gt;.  Each has its own install process, configuration scheme, update mechanism and more.  Many developers end up using a very basic and underpowered version of the CLI because they never discover, configure or learn these tools.  We believe there is value in an &lt;strong&gt;integrated, out-of-the-box&lt;/strong&gt; experience that helps all developers be more productive immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxykq2yu5k5nok56f1qa.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxykq2yu5k5nok56f1qa.jpeg" alt="Warp completion support"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  #4: Build for speed.
&lt;/h3&gt;

&lt;p&gt;One of the great attractions of working in the CLI is that if you master it, you can work very quickly.  That’s only true though if the underlying terminal is fast.  We are building Warp on the fastest possible modern architecture.  On desktop: a pure Rust app that goes directly to the GPU for rendering. On the web, a WASM app that uses WebGL.&lt;/p&gt;

&lt;h3&gt;
  
  
  #5: Build a platform.
&lt;/h3&gt;

&lt;p&gt;Most developers will experience Warp as a terminal app, a product they launch and run.  However, the CLI is more than an app, it’s a platform, much like iOS or Windows.  It has (primitive) mechanisms for discovering apps, installing them, running them, but it lacks many of the affordances of modern platforms, like an app-store, a unified way of securely installing and removing apps, a well defined API for developers to configure the runtime environment, and more.  While we are starting by focusing on the terminal app and experience, we eventually want to build the best possible CLI platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  #6: Leverage the cloud.
&lt;/h3&gt;

&lt;p&gt;The basics of computing have changed since the advent of the CLI, and no change has been more profound than the rise of the internet.  Once you start to think about it, there are a lot of leverage points: why is shell history limited by your local hard-drive?  Why doesn’t your shell configuration follow you across machines?  Why can’t you transfer a CLI session to the browser so you can keep working on the go? (Don't worry, though, Warp is local-first, and all cloud features will be opt-in. You can find out more in &lt;a href="https://www.warp.dev/privacy" rel="noopener noreferrer"&gt;our privacy policy&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  #7: Build for teams.
&lt;/h3&gt;

&lt;p&gt;Developers today work in teams on shared projects.  Because of this, most applications developers use are collaborative in one sense or another.  Github is a good example, as is VSCode live session sharing.  And yet the CLI is still primarily a single-user tool, with collaboration facilitated by copy/paste, screenshots, and screen-sharing, rather than a true multi-player experience.  We believe that CLI-based teamwork is a very powerful concept, extending beyond google-docs real-time collaboration, to facilitating asynchronous workflows like command-reviews, shared devops terminals and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646737771497%2Fc5IF71TNH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646737771497%2Fc5IF71TNH.png" alt="Real time collaboration &amp;amp; session sharing"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Real time collaboration &amp;amp; session sharing is one of the features we're working on.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  #8: Build for everyone.
&lt;/h3&gt;

&lt;p&gt;The CLI traditionally has a bit of a mystique around it; developers feel like hackers when they use it, and there’s almost a rite of passage in conquering its idiosyncrasies.  We believe that the CLI should be more accessible, more humane, and more useful to all developers.  Too many are turned off by the intimidating UI.  Many aspiring developers have their first experience using the CLI (it’s how they set up development environments), and that first experience is often very negative because the interface is so hard and creates so much incidental complexity.  We want to fix this and make a modern CLI for all developers.&lt;/p&gt;




&lt;p&gt;We truly believe that following all eight above principles will help us deliver the terminal that will finally bring the developers’ experience to the next level! And while we’re focused on building Warp, you can still meet us in &lt;a href="https://discord.gg/warpdotdev" rel="noopener noreferrer"&gt;our Discord community&lt;/a&gt; where we discuss Warp with developers from around the world. Request early access if you haven’t already. We look forward to your feedback.&lt;/p&gt;

</description>
      <category>warp</category>
      <category>terminal</category>
      <category>design</category>
      <category>product</category>
    </item>
    <item>
      <title>The terminal is on life support. Is it worth saving?</title>
      <dc:creator>Warp</dc:creator>
      <pubDate>Thu, 17 Mar 2022 04:50:00 +0000</pubDate>
      <link>https://forem.com/warpdotdev/the-terminal-is-on-life-support-is-it-worth-saving-20cm</link>
      <guid>https://forem.com/warpdotdev/the-terminal-is-on-life-support-is-it-worth-saving-20cm</guid>
      <description>&lt;p&gt;Why doesn’t the terminal work like the rest of your apps? Developer tools have &lt;strong&gt;evolved towards reusability, composability, and collaboration.&lt;/strong&gt; Meanwhile, terminals are &lt;strong&gt;inherently single-user, linear, and ephemeral.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers work in teams, but terminals don’t support collaboration&lt;/li&gt;
&lt;li&gt;Developers rely on sharing knowledge, but all my terminal work disappears every time I close a session&lt;/li&gt;
&lt;li&gt;Developers work across machines, but my terminal environment is tethered to my computer&lt;/li&gt;
&lt;li&gt;Developers are becoming accustomed to nice interfaces, but the terminal inflicts pain and makes me feel like an idiot when I try to do moderately complicated things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In spite of these obvious shortcomings, terminals persist!&lt;/p&gt;

&lt;p&gt;A lot of developers swear by them.  And I understand why: if you know what you’re doing, the terminal’s text-based interface is fast, efficient, composable, and programmable.  Terminals are awesome power tools.&lt;/p&gt;

&lt;p&gt;So we have an interesting situation: terminals are both obviously useful and highly antiquated.  How did we get here? What’s the best way forward?&lt;/p&gt;

&lt;p&gt;In the rest of this post, I’ll argue that&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Terminals persist because CLIs are the best interface for a lot of developer tasks&lt;/li&gt;
&lt;li&gt;Their antiquated architecture has stifled innovation&lt;/li&gt;
&lt;li&gt;This has in turn caused terminals to lose share to GUIs&lt;/li&gt;
&lt;li&gt;The right way forward is to keep what’s best about the terminal, but modernize it...which is what we are working on at &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And for those who don’t want to read a whole blog post, you can get a sneak peek at Warp up front:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646736783080%2FHpdLqJoAk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646736783080%2FHpdLqJoAk.gif" alt="Warp features"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A preview of Warp showing blocks, text-editing, native completions and visual hisory.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminals persist because CLIs are good interfaces for developers
&lt;/h2&gt;

&lt;p&gt;As a simple example, say you have written a program and suspect it’s leaking file handles but aren’t sure.  If you are comfortable in the terminal you can run a few commands to see what’s going on:&lt;/p&gt;

&lt;p&gt;First you can find your program’s process id using ps and grep:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ps aux | grep {program-name}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then you can see how many files it has open:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lsof -p {process-id} | wc -l&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And you can use &lt;code&gt;watch&lt;/code&gt; to track this over time to see if your debugging theory is right.  &lt;/p&gt;

&lt;p&gt;A GUI is almost all downside for this use case.  Slower, less-flexible, won’t work over ssh, etc, plus it would be a pain to build.  There are lots of developer tasks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ki6hjqs4hrt8afotvyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ki6hjqs4hrt8afotvyi.png" alt="CLI vs GUI Comparison"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though capital-letter CLIs have a lot of use-cases, the current implementation of the terminal is really bad for reasons mentioned earlier - no collaboration, transient sessions, bad UX, etc.&lt;/p&gt;

&lt;p&gt;Consequently, terminals are losing developer market share to user-friendly and modern GUI apps like VSCode, Postman, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hasn’t anyone already fixed the CLI?
&lt;/h2&gt;

&lt;p&gt;People have tried, but the terminal architecture makes it really difficult to innovate.  &lt;/p&gt;

&lt;p&gt;Terminals and shells only support the character-in-character-out protocol of a teletype, a piece of hardware that hasn’t been used in most new developers’ lifetimes. Specifically, the terminal sits on one side of a &lt;a href="https://en.wikipedia.org/wiki/Pseudoterminal#:~:text=In%20some%20operating%20systems%2C%20including,emulator%20process%20controls%20the%20slave." rel="noopener noreferrer"&gt;PTY&lt;/a&gt; and the shell sits on the other. Between them flows a sequence of characters.  The only structure in place is a set of character codes that control how the terminal lays out and paints its buffer.  &lt;/p&gt;

&lt;p&gt;Because shells are responsible for parsing input and running programs, terminals don’t know even basic things about what's happening; e.g. what separates a command from its output from the next command?  Was the command successful? What text was written to stdout vs. stderr?  &lt;strong&gt;Terminals have a knowledge deficit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Conversely, shells have a much richer understanding of all this, but they have extremely limited capabilities on the UX side.  They can't handle mouse interactions or render graphics, like for instance a completion UI.  &lt;strong&gt;Shells have a capability deficit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4yzojkfq13kvzek0ph9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4yzojkfq13kvzek0ph9.png" alt="PTY"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As someone wanting to modernize "the CLI", you are faced with the fact that there actually is no single CLI app – there are terminals and shells.  Trying to fix the experience at the shell level can be somewhat effective – e.g. &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;ohmyzsh&lt;/a&gt;, &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt;, &lt;a href="https://github.com/nvbn/thefuck" rel="noopener noreferrer"&gt;thefuck&lt;/a&gt;  – but it can never fix fundamental accessibility issues and it will never make the terminal feel like a modern app.&lt;/p&gt;

&lt;p&gt;In order to really modernize the CLI, you need to start with the terminal, and keep pushing further and further into the world of shells, ultimately ending up with a better integrated experience.  This is hard, but we think it's worthwhile because of the productivity gains it will unlock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warp: a more accessible, powerful terminal
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt; we are building a new Rust-based terminal that keeps what’s best about the CLI while modernizing and upgrading the experience.  One awesome thing about how we are building it is that it works with your existing shell so it's easy to try.&lt;/p&gt;

&lt;p&gt;If you’re interested in how we are pushing the technical limits, please give &lt;a href="https://blog.warp.dev/how-warp-works/" rel="noopener noreferrer"&gt;How Warp Works&lt;/a&gt; a read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A quick preview&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rather than a single buffer, we divide terminal output into a sequence of commands - making it easy for them to navigate, copy, save, and share units of work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nalvul7rsy51z8ipdev.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nalvul7rsy51z8ipdev.jpeg" alt="Warp Blocks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Warp is a block-based terminal&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We make input more familiar and usable, replacing the character buffer with a full-fledged text editor built in Rust, and equipping it with intellisense-like autocomplete:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b3etmb3mc8b19z887jl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b3etmb3mc8b19z887jl.jpeg" alt="Completions in Warp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built-in completions and documentation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And finally, we want to transform the terminal from an inherently single user utility into a modern collaborative app where sessions, blocks, and environments are all persistent, searchable and shared by link (being very mindful of security and &lt;a href="https://www.warp.dev/privacy" rel="noopener noreferrer"&gt;privacy&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqmf2m5emykvbzz828rl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqmf2m5emykvbzz828rl.png" alt="Collaboration and session sharing in Warp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These types of features are just the start, and part of a larger set of principles we have for improving the terminal’s CLI.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more, please check out our &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;site&lt;/a&gt; and request early access here.  We are still in the early days of implementing our vision but would love feedback and input as we move forward.&lt;/p&gt;

</description>
      <category>warp</category>
      <category>terminal</category>
      <category>startup</category>
      <category>bash</category>
    </item>
    <item>
      <title>How Warp Works</title>
      <dc:creator>Aloke Desai</dc:creator>
      <pubDate>Thu, 17 Mar 2022 04:27:16 +0000</pubDate>
      <link>https://forem.com/warpdotdev/how-warp-works-1ji7</link>
      <guid>https://forem.com/warpdotdev/how-warp-works-1ji7</guid>
      <description>&lt;p&gt;Warp is a new high-performance terminal built entirely in Rust that makes you and your team more productive and the CLI easier to use. The &lt;a href="https://docs.warp.dev/features/the-input-editor" rel="noopener noreferrer"&gt;input editor&lt;/a&gt; for commands is a full text-editor that supports selections, cursor positioning, and shortcuts like you are used to in other apps. Commands and their output are visually grouped into &lt;a href="https://docs.warp.dev/features/blocks" rel="noopener noreferrer"&gt;blocks&lt;/a&gt;, and existing shortcuts like up-arrow and ctrl-r have new visual menus to make them easier to use.&lt;/p&gt;

&lt;p&gt;In this article, I walk through how we built the foundation of Warp: the UI, blocks, and the input editor. Building this foundation ultimately helped us unblock even more exciting features that we’re launching in the coming months, like infinite history, real-time collaboration, and shared environment variables.&lt;/p&gt;

&lt;p&gt;Designing Warp required us to be intentional about our stack at nearly every level. At the start we had a key requirements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed:&lt;/strong&gt; Speed is critical when using a terminal, especially for commands that can have a lot of output. In practice, this means Warp should always be running at 60fps even on 4K or 8K monitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compatibility with Existing Shells:&lt;/strong&gt; Warp needs to work with popular existing shells like Bash, ZSH, and Fish and with existing keybindings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiplatform, including supporting the Web:&lt;/strong&gt; Warp needs to run on multiple platforms, including rendering Warp in a browser to support real-time collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration with shells to support blocks (including over SSH):&lt;/strong&gt; To support features like blocks, Warp needs to be able to integrate deeply with the current running session in the shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Arbitrary UI elements:&lt;/strong&gt; Unlike traditional terminals that mostly just render characters, Warp needs to render arbitrary UI elements (snackbars, overflow menus, etc). This is critical to our vision of making the terminal more accessible and functional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native and Intuitive Editing:&lt;/strong&gt; Warp needs a full text editor to support intuitive editing (e.g. selecting text, cursor positioning, and multiple cursors) that users are familiar with from other modern applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust + Metal
&lt;/h2&gt;

&lt;p&gt;One of the most important priorities when building Warp was speed. Many terminals built on Electron are capable tools but can quickly lag under certain conditions. Since we’re adding a layer of UI on top of the terminal, we wanted to be sure we chose a stack that would allow us to be in the upper echelon of terminals in terms of speed even while rendering more complicated UI elements.&lt;/p&gt;

&lt;p&gt;There are quite a few points during the process of outputting text to the terminal screen that can be potential performance bottlenecks for a terminal. A few include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reading from the pseudoterminal:&lt;/strong&gt; Reading from the pseudoterminal (see the Implementing Blocks section for more context on what a pseudoterminal is) and parsing ANSI escape sequences can be expensive, especially if there is a program running that prints a lot of output to the screen (such as &lt;code&gt;cat&lt;/code&gt;ing a large file)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering:&lt;/strong&gt; Depending on the implementation, actually rendering pixels onto the screen can be expensive. If the terminal has output that is constantly changing, this can very quickly cause a terminal to feel laggy and be under 60fps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scrolling:&lt;/strong&gt; Once the terminal viewport is full, new lines that are printed out to the terminal require the previous lines to be scrolled up before re-rendering. Scrolling speed often scales with the number of visible lines in the viewport.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The diagram below shows the output of &lt;a href="https://github.com/alacritty/vtebench" rel="noopener noreferrer"&gt;vtebench&lt;/a&gt; for scrolling in various terminals. For some reason Hyper generally could not handle running the benchmarks at all and did not terminate after a reasonable amount of time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasa7uc1z7fn6hevxup5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fasa7uc1z7fn6hevxup5z.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Speed for scrolling a single line in various terminal. This was computed by running the scrolling test from vtebench in various terminals at the same window size. For some reason Hyper generally could not handle running the benchmarks at all and did not terminate after a reasonable amount of time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After a very brief experiment with Electron, we quickly pivoted to building in Rust and rendering directly on the GPU using &lt;a href="https://developer.apple.com/metal/" rel="noopener noreferrer"&gt;Metal&lt;/a&gt; (Mac’s GPU API). As a low-level systems language, Rust was appealing because of its speed and also its strong developer community: using &lt;a href="https://crates.io/" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt; for package management was a breeze and our entire team (none of whom had written Rust before) onboarded very quickly with tools like &lt;a href="https://github.com/rust-lang/rustlings" rel="noopener noreferrer"&gt;Rustlings&lt;/a&gt;. More importantly, Rust has a pretty extensive platform support--allowing us to write in a single language and then build for Mac, Linux, Windows, and ultimately the web by compiling to WASM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Render on the GPU?
&lt;/h3&gt;

&lt;p&gt;Most applications that you use are rendered on the CPU using standard OS frameworks to render. For example, MacOS provides APIs to its &lt;a href="https://developer.apple.com/documentation/coregraphics" rel="noopener noreferrer"&gt;Core Graphics framework&lt;/a&gt; to render items like shapes and arbitrary paths directly on the CPU. Rendering on the CPU is relatively simple because of these high level APIs for drawing and can be pretty fast for 2D graphics and text. Rendering on the GPU is typically much lower level (there are no equivalent APIs for rendering shapes or text) but performs well for 3D graphics or any task that requires high throughput.&lt;/p&gt;

&lt;p&gt;Interestingly, GPU-accelerated rendering for terminals has become fairly standard recently (iTerm, Hyper, Kitty, and Alacritty all support it). It may seem that rendering on the CPU would be better suited since it’s mostly just text and 2D shapes that are being rendered. However, terminal rendering is more complex now than it was in the 80s. Terminals today need to be able to render high resolution text on a 4K display and possibly at a very high FPS since some displays run at 240hz or even higher.&lt;/p&gt;

&lt;p&gt;Efficient use of a GPU renderer (minimizing state changes between two frames, rasterizing glyphs only once, and minimizing the number of draw calls) can push rendering on the GPU to 400+ fps, which typically isn’t possible using a CPU renderer.&lt;/p&gt;

&lt;p&gt;Eliminating as many software and architectural bottlenecks on the CPU allows us render at well over 144fps, even on a 4K monitor. We chose Metal over OpenGL as our GPU API since we knew we were going to target MacOS as our first platform. The Metal debugging tools in Xcode are excellent, allowing us to inspect texture resources and easily measure important metrics like frame rate and GPU memory size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdkjoguc1qi8zguc5l3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdkjoguc1qi8zguc5l3q.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  But how do you render arbitrary UI elements on the GPU?
&lt;/h3&gt;

&lt;p&gt;Supporting arbitrary UI elements on the GPU is tricky. Rendering on the GPU with Metal is very low level, the API essentially only allows to render triangles using a &lt;a href="https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives" rel="noopener noreferrer"&gt;vertex and fragment shader&lt;/a&gt; or to &lt;a href="https://developer.apple.com/documentation/metal/creating_and_sampling_textures" rel="noopener noreferrer"&gt;sample from a texture&lt;/a&gt; to render images.&lt;/p&gt;

&lt;p&gt;The key insight here is that for Warp the only thing we needed to render is rectangles, images, and glyphs (which are often rendered via a &lt;a href="https://en.wikipedia.org/wiki/Texture_atlas" rel="noopener noreferrer"&gt;texture atlas&lt;/a&gt;). Making use of this insight drastically reduced the complexity of using Metal: we built shaders for each of these primitives in around 200 lines of code and don’t need to touch the shaders when adding new UI components since they are composed of these primitives.&lt;/p&gt;

&lt;p&gt;However, we still needed a higher level of abstraction to render UI elements on top of the characters rendered as part of the terminal output. The lack of a stable UI framework for Rust has been a known issue for a while (see &lt;a href="https://www.areweguiyet.com/" rel="noopener noreferrer"&gt;areweguiyet.com&lt;/a&gt;) and the available options get even more bare if you want to support Metal as the rendering backend. Options we considered were &lt;a href="https://azul.rs/" rel="noopener noreferrer"&gt;Azul&lt;/a&gt; and &lt;a href="https://github.com/linebender/druid" rel="noopener noreferrer"&gt;Druid&lt;/a&gt;, however both were still very experimental at the time and didn’t support Metal (Druid doesn’t have any GPU rendering backend yet and Azul only supports OpenGL).&lt;/p&gt;

&lt;p&gt;Given the lack of stable options, we decided to build our own UI framework in Rust, which essentially amounted to building the architecture of a browser. Though this would have been a significant engineering hit to build this from scratch, we partnered with  &lt;a href="https://github.com/nathansobo" rel="noopener noreferrer"&gt;Nathan Sobo&lt;/a&gt;, co-founder of the Atom text editor, who had already started building a Rust UI framework that was loosely inspired by Flutter. We use this UI framework to build an element tree for our app that we can then render using various rendering backends (for now just Metal, but we plan to add OpenGL and WebGL as we support more platforms).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gkf9ivj6oh7qxsxc459.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gkf9ivj6oh7qxsxc459.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the rendering level, we started by building primitive abstractions (such as a rect, image, and glyph) that we render in Metal. We compose these abstractions to build higher level elements such as a snackbar, a context menu, or a block.  We expect this separation between elements and the actual GPU pipeline to serve us well as we support more platforms that don’t support Metal: all we need to do is reimplement these primitives (&amp;lt;250 lines of shader code) in the respective GPU API while leaving our elements and anything higher in the stack untouched. We hope to open source this UI framework as we iterate on it, we already think it’s a capable framework for rendering in Rust.&lt;/p&gt;

&lt;p&gt;Rendering on the GPU has been fruitful thus far: even with many UI elements and a lot of terminal output we are still able to render &amp;gt; 144 FPS. For example, over the past week the average time to redraw the screen in Warp was only 1.9 ms!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8db0jmxeqw95ik58j75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8db0jmxeqw95ik58j75.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Blocks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femf7qdw7lib6fzbcjaye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femf7qdw7lib6fzbcjaye.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason you don’t see a feature like blocks (with the exception of &lt;a href="https://github.com/railsware/upterm" rel="noopener noreferrer"&gt;Upterm&lt;/a&gt;) in most other terminals is because the terminal has no concept of what program is running, or really of anything that’s happening within the shell. At a high level, a terminal reads and writes bytes from a &lt;a href="https://en.wikipedia.org/wiki/Pseudoterminal#:~:text=In%20some%20operating%20systems%2C%20including,emulator%20process%20controls%20the%20slave." rel="noopener noreferrer"&gt;pseudoterminal&lt;/a&gt; to interact with the shell. This technology is very antiquated--the shell essentially thinks it is interacting with a physical teletype terminal even though they haven’t been used in practice in over 30 years!&lt;/p&gt;

&lt;p&gt;A terminal implements the &lt;a href="https://vt100.net/emu/dec_ansi_parser" rel="noopener noreferrer"&gt;VT100 spec&lt;/a&gt;, which is used to communicate information from the shell to the terminal. For example if the shell wants to tell the terminal to render text red, bold, and underline it would send the following escape code, which the terminal then parses and renders with the appropriate styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\033[31;1;4mHello\033[0m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;a href="https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences&lt;/a&gt; covers how colors are represented via escape sequences very well. It’s also just generally one of my favorite stackoverflow answers--make sure to read the interlude!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Given all the terminal sees is characters in and characters out, trying to determine when a command is run is next to impossible using just the characters from the pseudoterminal.&lt;/p&gt;

&lt;p&gt;Thankfully, most shells provide hooks for before the prompt is rendered (zsh calls this &lt;a href="https://zsh.sourceforge.io/Doc/Release/Functions.html" rel="noopener noreferrer"&gt;precmd&lt;/a&gt;) and before a command is executed (&lt;a href="https://zsh.sourceforge.io/Doc/Release/Functions.html" rel="noopener noreferrer"&gt;preexec&lt;/a&gt;). Zsh and Fish have built in support for these hooks. Though bash does not have built in support for these hooks, scripts like &lt;a href="https://github.com/rcaloras/bash-preexec" rel="noopener noreferrer"&gt;bash-preexec&lt;/a&gt; exist to mimic the behavior of other shells. Using these hooks, we send a custom DCS (&lt;a href="https://vt100.net/docs/vt510-rm/chapter4.html#:~:text=Device%20control%20strings%20(DCS)%2C,for%20a%20device%20control%20string." rel="noopener noreferrer"&gt;Device Control String&lt;/a&gt;) from the running session to Warp. This DCS contains an encoded JSON string that includes metadata about the session that we want to render. Within Warp we can parse the DCS, deserialize the JSON, and create a new block within our data model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;The VT100 spec represents the viewport in terms of rows and columns which naturally leads to most terminals using a grid as their internal data model. We started by forking &lt;a href="https://github.com/alacritty/alacritty" rel="noopener noreferrer"&gt;Alacritty&lt;/a&gt;’s model code, which was well suited for us because it was already written in Rust and is tailored to be as performant as possible. &lt;a href="https://github.com/nixpulvis" rel="noopener noreferrer"&gt;Nathan Lilienthal&lt;/a&gt; and &lt;a href="https://github.com/jwilm" rel="noopener noreferrer"&gt;Joe Wilm&lt;/a&gt;, two of the maintainers of Alacritty, were early supporters of Warp and were extremely gracious in reviewing early design docs and providing their technical expertise.&lt;/p&gt;

&lt;p&gt;We quickly realized, however, that a single grid would not work for building a feature like blocks: separating commands from each other requires ensuring that terminal output is actually written on separate lines of a grid and can’t be overwritten by output from another command. Neither of these can be guaranteed by the VT100 spec. For example, consider a command like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash-5.1$ printf "hello"
hellobash-5.1$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;printf "hello"&lt;/code&gt; output the text &lt;code&gt;"hello"&lt;/code&gt;, but no newline was inserted, so the prompt for the next command was inserted on the same line. Similarly, escape sequences exist that allow moving the cursor up within the visible screen by n rows, allowing any command to overwrite any command that was already executed. With a traditional grid data model, we wouldn’t be able to support the case where a single line had the command from one block and the output from another. We also didn’t want the output from one command to be able to overwrite the contents from a previous block.&lt;/p&gt;

&lt;p&gt;To fix these issues, we create a separate grid for each command and output based on the precmd/preexec hooks we receive from the shell. This level of grid isolation ensures we can separate commands and their output without having to deal with the output of one command overwriting that of another. We found this approach to be a good balance of being faithful to the VT100 spec and using the existing Alacritty grid code with rendering blocks in a more flexible way that was traditionally prescribed by a teletype terminal. This model gives us more flexibility for implementing features for a single block that are traditionally for an entire terminal grid. For example, searching per block and copying the command/output separately are trivial to implement if our model separates each command and output into their own submodel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input Editor
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuh0cuac92w996ewwfry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuh0cuac92w996ewwfry.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Together with &lt;a href="https://github.com/nathansobo" rel="noopener noreferrer"&gt;Nathan Sobo&lt;/a&gt;, we built a full-fledged text editor as our input editor as an editor view in our framework. Building this as a full text editor unblocked features like multiple cursors and selections within the editor and also will let us use the editor as the default editor within the shell if we ever want to support it.&lt;/p&gt;

&lt;p&gt;Because we built our own command line editor, we had to re-implement all the existing shortcuts users are used to while editing (such as up arrow, ctrl-r, and tab completion) while also balancing implementing new modern shortcuts like Move Cursor By Word or Select All that previously didn’t exist in the shell line editor. We built an action dispatching system into our framework that can be triggered based on keybindings so implementing all these shortcuts was relatively trivial. This also made it easy to enable/disable various shortcuts depending on the state of the app: for example “ctrl-r” for searching previous commands should only be enabled if the input editor is visible.&lt;/p&gt;

&lt;p&gt;Building the editor also heavily relied on a SumTree, a custom data structure that is essentially a &lt;a href="https://en.wikipedia.org/wiki/Rope_(data_structure)" rel="noopener noreferrer"&gt;Rope&lt;/a&gt; that can hold generic types and can index the type on multiple dimensions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnneh71cxsma8rndsp782.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnneh71cxsma8rndsp782.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like many text editors, we use the SumTree to hold the contents of our buffer. We also use the SumTree for transformations of the editor that don’t affect the underlying buffer contents (such as code folding, holding unselectable and uneditable annotations, etc). Representing transformations as a SumTree allows us to efficiently query for both the buffer text and the display text at any given point in the editor. We also use SumTrees to keep track of the operations (or edits) on the buffer. We intentionally designed our editor to be an Operation-based &lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener noreferrer"&gt;CRDT&lt;/a&gt; from the start, so that we can support real-time collaboration within the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;Looking forward, our stack opens up the ability to realize our full vision of what Warp can be. By choosing Rust and building a UI framework that is rendering-platform agnostic, we can easily scale to other platforms and even the web by &lt;a href="https://rustwasm.github.io/docs/book/" rel="noopener noreferrer"&gt;compiling to WASM&lt;/a&gt; (a mistake we made was to not think about which crates will and won’t compile to WASM early on) and rendering with WebGL.&lt;/p&gt;

&lt;p&gt;One feature that the web rendering enables is real-time collaboration. You can share a secure link to your teammate to debug issues in real-time &lt;a href="https://github.com/warpdotdev/warp/issues/2" rel="noopener noreferrer"&gt;https://github.com/warpdotdev/warp/issues/2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can also leverage the “Blocks” model to enable users to search history by more than just the command: &lt;a href="https://github.com/warpdotdev/warp/issues/1" rel="noopener noreferrer"&gt;https://github.com/warpdotdev/warp/issues/1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our precmd and preexec hooks also provide the necessary technical foundation for Environment variable sharing: &lt;a href="https://github.com/warpdotdev/warp/issues/3" rel="noopener noreferrer"&gt;https://github.com/warpdotdev/warp/issues/3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;** * Edit: When we published this post, we had not set up our Github issues yet. We have since transferred the technical implementation details to Github.*&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Performance is one of our most important features--it’s a feature most users won’t notice on a day-to-day basis but it makes a huge difference in building a quality product for the end user.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more, please check out our site and request early access to our beta here.  We are still in the early days of implementing our vision but would love your feedback and input as we move forward!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>warp</category>
      <category>bash</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Rust's Rules Are Made to Be Broken</title>
      <dc:creator>Charles Pierce</dc:creator>
      <pubDate>Wed, 02 Mar 2022 17:18:43 +0000</pubDate>
      <link>https://forem.com/warpdotdev/rusts-rules-are-made-to-be-broken-3gmi</link>
      <guid>https://forem.com/warpdotdev/rusts-rules-are-made-to-be-broken-3gmi</guid>
      <description>&lt;p&gt;We’ve talked &lt;a href="https://blog.warp.dev/how-warp-works/"&gt;in the past&lt;/a&gt; about why we chose to build Warp in Rust. Since making that decision, one thing that stands out is how productive we are as a team while still reaping the performance benefits of a systems level language. &lt;strong&gt;A big reason for that productivity is the borrow checker.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By enforcing its rules at compile time, Rust’s &lt;a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html"&gt;borrow checker&lt;/a&gt; is able to make guarantees about the memory safety of our app, which lets us focus more of our energy on building the future of the terminal and less on chasing down use-after-free bugs.&lt;/p&gt;

&lt;p&gt;At the same time, the borrow checker can be a source of frustration. Sometimes, you need to do something that breaks the rules but don’t want to throw the safety out the window. &lt;strong&gt;Fortunately, the Rust standard library provides helpful types to let you do just that: break the rules!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#shared-ownership"&gt;Shared Ownership&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#reference-counting-to-the-rescue"&gt;Reference counting (Rc and Arc)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#unique-borrows"&gt;Unique Borrows&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#refcell"&gt;RefCell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#locking-types"&gt;Locking Types (Mutex and RwLock)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#atomics"&gt;Atomics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Shared Ownership
&lt;/h2&gt;

&lt;p&gt;The first borrow checker rule we’re going to break is single ownership: By default in Rust, every piece of data has a single owner, and when that owner goes out of scope, the data is cleaned up. This is great for ergonomics as you don’t have to worry about manually allocating and freeing memory! &lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#fn1"&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, sometimes you don’t want data to have only a single owner. Maybe you want the data to pass through different parts of your program that will each run for different lengths and there’s no way to know how long in total the data needs to exist. Maybe you’re prototyping and don’t yet want to tackle ownership and lifetimes in a more concrete way. Whatever the reason, you really would like to have multiple owners for some data, so that it lasts as long as all of them combined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference counting to the rescue!
&lt;/h3&gt;

&lt;p&gt;Built into the Rust standard library are two types that provide shared ownership of the underlying data: &lt;code&gt;Rc&lt;/code&gt; and &lt;code&gt;Arc&lt;/code&gt; (short for ‘Reference counted’ and ‘Atomically reference counted’, respectively).&lt;/p&gt;

&lt;p&gt;Both of these types give shared ownership of the contained data by tracking the number of references and ensuring the data will last as long as there are any active references. They each implement &lt;a href="https://doc.rust-lang.org/std/clone/trait.Clone.html"&gt;Clone&lt;/a&gt; and &lt;a href="https://doc.rust-lang.org/std/ops/trait.Drop.html"&gt;Drop:&lt;/a&gt; cloning increments the reference count while dropping one decrements it. The data is kept alive as long as there are references and only cleaned up once all of the clones have gone out of scope. For example, this snippet uses &lt;code&gt;Rc&lt;/code&gt; to share ownership of two pet objects between two owners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Pet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Pet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create two pets with shared ownership&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Pet&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tigger"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Pet&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chase"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

    &lt;span class="c1"&gt;// Create one person who owns both pets&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;brother&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Create another person who _also_ owns both pets&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sister&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Even if one person gives up ownership, the other person still has shared ownership,&lt;/span&gt;
    &lt;span class="c1"&gt;// so the pets are kept around (yay!)&lt;/span&gt;
    &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sister&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pets: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brother&lt;/span&gt;&lt;span class="py"&gt;.pets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both types incur a small amount of runtime overhead to maintain the reference count. The key difference between them is that &lt;code&gt;Arc&lt;/code&gt; is thread safe, while &lt;code&gt;Rc&lt;/code&gt; isn’t. &lt;code&gt;Arc&lt;/code&gt; uses atomic operations to manage the reference count, which gives it a higher runtime cost but makes it safe to share between threads. If you’re only working in a single thread, then &lt;code&gt;Rc&lt;/code&gt; is the faster alternative.&lt;/p&gt;

&lt;p&gt;The last important fact about &lt;code&gt;Rc&lt;/code&gt; and &lt;code&gt;Arc&lt;/code&gt; is that they only allow you to get immutable references to the underlying data.&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#fn2"&gt;[2]&lt;/a&gt; Since they fundamentally represent shared data, allowing mutable references would violate Rust’s safety guarantees by allowing for data races and use-after-free errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unique Borrows
&lt;/h3&gt;

&lt;p&gt;“But wait!,” you say, “What if I want to share mutable data between parts of my app?” Luckily, the next borrow checker rule we’re going to break is unique borrows: In order to mutate something, you need to have a unique (also called mutable) reference to the data. The borrow checker enforces that you can only have one mutable reference or any number of immutable references, but never a combination of the two, so you can’t ever mutate data that another part of the program is trying to read at the same time.&lt;/p&gt;

&lt;p&gt;This restriction works great most of the time, but we’re here to break the rules! Two cases where you might want to mutate data without having a mutable reference are:&lt;/p&gt;

&lt;p&gt;Caching an otherwise immutable calculation (i.e. memoization)&lt;br&gt;
The above shared ownership case with Rc or Arc&lt;br&gt;
For bending the compile-time borrowing rules, the compiler provides several helpful types, each with their own sets of drawbacks. All of them let you safely mutate data behind an immutable reference, using different approaches to make sure that your program is still safe.&lt;a href="https://blog.warp.dev/rules-are-made-to-be-broken/#fn3"&gt;[3]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RefCell&lt;br&gt;
First up is &lt;code&gt;RefCell&lt;/code&gt;, which moves the enforcement of unique borrows from compile-time to runtime. Similar to &lt;code&gt;Rc&lt;/code&gt;, &lt;code&gt;RefCell&lt;/code&gt; uses reference counting to track how many borrows are active while the program is running. If you ever try to take a mutable reference and another reference at the same time, &lt;code&gt;RefCell&lt;/code&gt; will immediately panic! So when using &lt;code&gt;RefCell&lt;/code&gt;, it’s up to you to make sure that your program doesn’t try to read and write the same data at the same time.&lt;/p&gt;

&lt;p&gt;Here's an example of using &lt;code&gt;RefCell&lt;/code&gt; to cache intermediate results for something that is otherwise immutable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;FibonacciCalculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RefCell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;FibonacciCalculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// Calculate the Nth fibonacci number, caching the result to prevent recalculation&lt;/span&gt;
    &lt;span class="cd"&gt;/// Note that this takes `&amp;amp;self`, not `&amp;amp;mut self`!&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Base case&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check the cache&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cache&lt;/span&gt;&lt;span class="nf"&gt;.borrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Calculate and cache the value&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cache&lt;/span&gt;&lt;span class="nf"&gt;.borrow_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, the reference counting in &lt;code&gt;RefCell&lt;/code&gt; is not thread safe, so it’s not possible to share &lt;code&gt;RefCell&lt;/code&gt; data between threads. For mutating shared data between threads, we need to turn to our next tool.&lt;/p&gt;

&lt;p&gt;Locking Types&lt;br&gt;
The next two types are &lt;code&gt;Mutex&lt;/code&gt; and &lt;code&gt;RwLock&lt;/code&gt;, which both provide ways to access mutable references from an immutable reference in a thread-safe way. They do this by completely blocking the thread until it is safe to access the data. This provides a strong guarantee that the access is safe, but also has a major pitfall: Deadlocks. Deadlocks occur when two threads are blocked waiting for access to data that the other thread is holding. Similar to &lt;code&gt;RefCell&lt;/code&gt;, it's up to you to make sure the logic of your program doesn't hold onto some data while waiting for access to other data leading to a deadlock.&lt;/p&gt;

&lt;p&gt;For example, the following snippet uses a Mutex to increment a counter in parallel in two new threads, then reads the final result from the original thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a shared, mutable counter&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Spawn one thread incrementing the counter&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;counter1&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Spawn another thread incrementing the counter&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;counter2&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Wait for the threads to complete&lt;/span&gt;
&lt;span class="n"&gt;handle1&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;handle2&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Will write "Value: 20", since each thread incremented the counter 10 times.&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Value: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference between the two types is how they handle different kinds of access. A &lt;code&gt;Mutex&lt;/code&gt; doesn’t care about whether you are trying to read or write the data, only a single thread can have access at a time. All other threads have to wait until the current consumer gives up their access.&lt;/p&gt;

&lt;p&gt;By contrast, &lt;code&gt;RwLock&lt;/code&gt; follows Rust’s rules: Any number of threads can have read-only access to the underlying data or a single thread can have write access, but no combination. Attempting to obtain write access will block until there are no active readers and vice versa.&lt;/p&gt;

&lt;p&gt;The thread safe nature of these locking types makes them some of the most powerful ways to share mutable data, however it comes at potentially significant performance cost: Locking the thread so that no other work can be done until the data is available. If our data is simple enough, the last type we’re going to look at can provide shared access across threads without needing to lock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomics
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://doc.rust-lang.org/std/sync/atomic/"&gt;Atomic types&lt;/a&gt; are available for integer and boolean primitives. These types all provide methods to mutate or read the data as a single operation, so that nothing can happen in between and there is no possibility for data races.&lt;/p&gt;

&lt;p&gt;For example, if you want to increment a counter, instead of reading the value, adding one, then writing the value, you would use the &lt;code&gt;fetch_add&lt;/code&gt; method that does all of that in a single block.&lt;/p&gt;

&lt;p&gt;Atomics are often used as the building blocks for more complicated thread-safe sharing or one-off initialization. As mentioned above, Arc uses an atomic counter internally to manage the reference counting in a thread-safe way.&lt;/p&gt;




&lt;p&gt;The beauty of all of these types is that they provide a way to break the borrow checker rules while still maintaining Rust’s safety guarantees. Understanding which rules each type breaks is the key to knowing which tool you need. If you’re looking for shared ownership without copying, you want the reference counted types. If you’re looking for mutability without Rust’s strict mutable references, reach for interior mutability. And if you need shared mutable state, it’s not uncommon to use a combination of them: &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; or &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; are combinations for single- and multithreaded shared mutable ownership, respectively.&lt;/p&gt;

&lt;p&gt;If you’re interested in breaking more of Rust’s rules and pushing the terminal into the future, &lt;a href="https://www.warp.dev/hiring"&gt;we’re hiring at Warp!&lt;/a&gt; Or if you’d just like to check out our Rust-based terminal, request early access below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://form.typeform.com/to/yrwMkgtj?typeform-medium=embed-snippet"&gt;Request early access to Warp&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using tree data structures to implement terminal split panes - more fun than it sounds</title>
      <dc:creator>Yunfan Yang</dc:creator>
      <pubDate>Wed, 19 Jan 2022 19:35:26 +0000</pubDate>
      <link>https://forem.com/warpdotdev/using-tree-data-structures-to-implement-terminal-split-panes-more-fun-than-it-sounds-2kon</link>
      <guid>https://forem.com/warpdotdev/using-tree-data-structures-to-implement-terminal-split-panes-more-fun-than-it-sounds-2kon</guid>
      <description>&lt;p&gt;Most popular command line tools and code editors like VSCode and Tmux support split panes: they provide a handy way for users to visually organize different workflows and keep track of processes that are happening in parallel. One of the first things I learned when doing web development was to have two panes – one for running the server and one for checking and pushing code to Github.&lt;/p&gt;

&lt;p&gt;Using split panes is one thing; building them is another. I never thought I would need to build split panes from scratch until I joined Warp – at &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt;, we are building a high-performance terminal written entirely in Rust.&lt;/p&gt;

&lt;p&gt;As part of building it, we had to build native split panes. Here are our  key product requirements. Developers should be able to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Infinitely split the screen in any direction (approximately).&lt;/li&gt;
&lt;li&gt;Remove panes from the screen.&lt;/li&gt;
&lt;li&gt;Navigate across panes with arrow keys.&lt;/li&gt;
&lt;li&gt;Arbitrarily resize panes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Representing panes in a two dimensional array?
&lt;/h2&gt;

&lt;p&gt;The first piece is figuring out what data structure to use to represent these panes. The first data structure that comes to mind to represent panes is a two dimensional array. Intuitively, it has some great properties we could take advantage of: for example, it captures how panes are laid out visually, so if we want to navigate in a certain direction, we simply need to change the row and column index to get the target pane. Pushing new panes is straightforward as well, we just need to either insert it into an empty cell or create a new row/column if the cell is currently occupied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3btfvrhvi74aku4srpyc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3btfvrhvi74aku4srpyc.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnlyv1sn0ykf1ifbk238.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnlyv1sn0ykf1ifbk238.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Example of a screen split into four panes and its representation with a two dimensional array.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Intuitive as it may be, we encountered some issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Representation ends up becoming sparse:&lt;/strong&gt;
Users should be able to split the panes in any arbitrary way – they could have two panes on row 0 and five panes on row 1. This will fill the two dimensional array with many empty cells. As a result, navigation becomes harder since simply increasing or decreasing the current cell's row/column index might lead to an empty cell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance does not scale:&lt;/strong&gt;
Inserting and removing panes could be memory expensive. For example, if we need to insert a new pane to the right of pane 0, we will have to allocate a new row and shift the content of every pane in the rows after. This will get increasingly expensive as users create more panes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Representing panes with trees
&lt;/h2&gt;

&lt;p&gt;The Zed editor (originally created by Nathan Sobo) provides an interesting approach: using trees to represent panes. Trees are well suited for this problem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trees could be unbalanced:&lt;/strong&gt;
This solves the obstacle we had when using the two dimensional array as we could split panes in any arbitrary way and still represent them efficiently in a tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trees could be recursive:&lt;/strong&gt;
We won’t need to worry about allocating an entire new row or column for each pane. Instead, we just need to insert a new branch when a user adds another pane.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trees could be hierarchical:&lt;/strong&gt;
We could represent the relationship between panes and their sub-panes with parent nodes and child nodes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each tree can have only two different types of nodes – a branch or a pane node. A pane node, as its name suggests, represents a pane on the screen. This should always be a leaf node within a tree. A branch node holds information about the spatial relationships between its children. In our use case, the branch node represents the direction a pane is split, which can be either vertical or horizontal. An example of how a snapshot of pane state could be represented as a tree is shown below.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd2dfgoxoo5efyyjbcm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd2dfgoxoo5efyyjbcm4.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3e3bf44q4k35or0tn3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3e3bf44q4k35or0tn3b.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Example of a screen split into three panes and its representation with a tree.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The way we define pane trees and their components in Warp looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node {
    BranchNode,
    PaneNode,
}

Direction {
    Horizontal,
    Vertical
}

BranchNode {
    split_direction Direction,
    children Node[],
}

PaneNode {
   id,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Add and Remove Panes?
&lt;/h2&gt;

&lt;p&gt;After we settled on trees as the data structure, we had to figure out how to modify these trees to add and remove panes.&lt;/p&gt;

&lt;p&gt;For adding panes, we could consider it as “splitting” the current pane node into a branch with two child nodes. For example, in the simple case when user hits split horizontally on a single pane, the tree before and after the action would look like the following:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupwl8ksmjuf9vh2bjwil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupwl8ksmjuf9vh2bjwil.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Change of tree state when adding a new pane&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What about panes that are nested under other branches? This becomes more complicated as we could have two different scenarios: splitting in the same direction and splitting in a different direction as the parent branch.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47cf4nkn04uptizkpz1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47cf4nkn04uptizkpz1m.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;_Splitting in the same direction_&lt;/center&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbn6hlq8zfkimndd87on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbn6hlq8zfkimndd87on.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;_Splitting in a different direction_&lt;/center&gt;

&lt;p&gt;The algorithm we use here is to recursively traverse down the tree until we locate the deepest branch that hosts the current pane (depth-first search). In case the branch’s split direction is the same as the new pane’s split direction, we simply insert a new pane node into the children vector. When the split direction is different, we instead replace the pane node with a new branch node that has the new split direction and the current and new added panes as its children.&lt;/p&gt;

&lt;p&gt;Removing panes is very similar to the reverse of adding panes. There’s one special case we need to keep in mind – if a branch node has only one pane node, we should collapse them into one pane node as there is no longer a “split”. Thus the algorithm should keep track of the branch’s children size after removing the target pane, once it’s below the size of two, we return the last pane node and the parent branch should replace the branch node with the returned pane node.&lt;/p&gt;

&lt;p&gt;With regard to the performance of adding and removing panes, the run-time complexity should be O(N) where N is the number of nodes in the tree. The main source of algorithm runtime is from the depth-first algorithm used to locate the current pane. The memory complexity should be O(1) since we only need to change the target pane and its parent branch at most.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Tree for all of the split panes&lt;/span&gt;
&lt;span class="cd"&gt;///&lt;/span&gt;
&lt;span class="cd"&gt;/// Holds the root node.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PaneData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/// Single Node in the tree of panes.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;PaneNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// A collection of terminals split in a specific direction&lt;/span&gt;
    &lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaneBranch&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="cd"&gt;/// A single terminal&lt;/span&gt;
    &lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/// The result of attempting to remove a pane from a branch&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;BranchRemoveResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/// The pane was not found in this sub-tree&lt;/span&gt;
    &lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cd"&gt;/// The pane was found and removed, no further action is needed&lt;/span&gt;
    &lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="cd"&gt;/// The pane was found and removed, leaving only a single node in the branch, so it needs to&lt;/span&gt;
    &lt;span class="cd"&gt;/// be collapsed into the parent&lt;/span&gt;
    &lt;span class="nf"&gt;Collapse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PaneBranch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SplitDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaneNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PaneData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;old_session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SplitDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.root&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.root&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PaneNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;old_session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SplitDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;old_session&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PaneBranch&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                    &lt;span class="k"&gt;true&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;false&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Leaves can only be removed from the containing branch&lt;/span&gt;
            &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Collapse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;true&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;PaneBranch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SplitDirection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PaneBranch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_session&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;old_session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SplitDirection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.enumerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;old_session_id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// If the split comes in the same direction as the previous splits&lt;/span&gt;
                        &lt;span class="c1"&gt;// on this sub-tree, we can insert the new pane into the nodes directly&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.axis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                            &lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="c1"&gt;// Otherwise, split the current leaf into a perpendicular branch&lt;/span&gt;
                            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PaneBranch&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id_to_remove&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BranchRemoveResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.iter_mut&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.enumerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id_to_remove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nn"&gt;PaneNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Leaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pane&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;session_id_to_remove&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="c1"&gt;// Safety: We know that there is an element in `self.nodes`&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Collapse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nn"&gt;BranchRemoveResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Navigating Panes with Arrow Keys
&lt;/h2&gt;

&lt;p&gt;One challenge we encountered when creating split panes was supporting navigation with arrow keys in a way that mapped to the visual layout of the panes. Although visually the panes are organized in two dimensional space, the pane tree does not maintain the spatial knowledge of which panes are next to each other. Take the state below as an example: when the user hits the right key on the top left corner, in a two dimensional array representation, we might just increment the column index by 1 to get the top right pane. However, in a tree representation, we will need to first traverse up the tree until we find a branch with the same split direction as our navigation direction and then traverse down that subtree until we hit the target node.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftg95uxskp83963u1rrdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftg95uxskp83963u1rrdw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn17qbqijhpwj29noxp0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn17qbqijhpwj29noxp0e.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: How navigation with arrow keys works in the tree structure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since multiple nodes could exist in the sub-tree, one heuristic we use to make the navigation experience more natural is to focus on the first child node if the navigation direction is right or down and focus on the last child node if the direction is left or up. In the end, the run-time complexity of navigation will also be O(N) as the depth-first search to locate the current pane is O(N) and traversing up to locate the target pane is O(N) as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;find_pane_by_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FindPaneByDirectionResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.enumerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="nf"&gt;.pane_by_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;FindPaneByDirectionResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Found&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;FindPaneByDirectionResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Located&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// If the axis is different, we left for the parent branch to handle.&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="nf"&gt;.axis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.axis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;target_pane_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nn"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="nf"&gt;.last_pane&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nn"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="nf"&gt;.first_pane&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_pane_id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;FindPaneByDirectionResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Found&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nn"&gt;FindPaneByDirectionResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nn"&gt;FindPaneByDirectionResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NotFound&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Representing Pane Sizes
&lt;/h2&gt;

&lt;p&gt;Every pane should be resizable. Due to how all panes together occupy a space with set dimensions, resizing one pane will lead to the resizing of multiple other panes as well. For example, in the case where one screen is split into four equal panes, dragging the border in the center will actually cause all four panes to be resized as shown below.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcaqtw6wngm29vj7gcba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcaqtw6wngm29vj7gcba.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn13ywr0u3kjhtdvm2w2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn13ywr0u3kjhtdvm2w2e.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 5: Resizing one pane could lead to multiple other panes to resize as well.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With this ripple effect of individual pane sizes, saving the absolute pane size in each node is undesirable as we will need to recalculate all sizes on each resize call. Thus, we decided to save the ratio each child node takes of the branch node’s size instead (we call it the flex ratio). When rendering, we pass in the available size from the root node and go down the branches, deriving each individual pane size based on the split direction and the flex ratio in the branch node. If you want to know more about how Warp renders on the GPU with its native UI framework, check out our blog post &lt;a href="https://blog.warp.dev/how-warp-works/" rel="noopener noreferrer"&gt;here&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The new BranchNode will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BranchNode {
    split_direction Direction,
    flex_ratio f32[], # this should add up to 1.0
    children Node[],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd32poxdjkxvxcptplen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd32poxdjkxvxcptplen.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 6: An example of how we derive the absolute size for each pane using the flex ratio, assuming we start with a screen with dimension 1024 by 768 and the pane is split into four panes. We could walk down from the root pane to first derive the dimensions of the branch nodes and then use that information to calculate the absolute size of each individual pane.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Personally, I’m a big fan of how the simple split pane UI makes the terminal so much more powerful. It’s been really exciting to work on designing and programming the split pane functionality within Warp myself. Although there are still many remaining features yet to be implemented that are available in iTerm and Tmux (drag and drop panes!), having a native implementation of the split pane unlocks many more possible things we could do in Warp to make the developer experience even better.&lt;/p&gt;

&lt;p&gt;Join our waitlist and let us know what we could make to further improve split panes in Warp!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>algorithms</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to draw styled rectangles using the GPU and Metal</title>
      <dc:creator>Michelle Lim</dc:creator>
      <pubDate>Thu, 18 Nov 2021 17:35:12 +0000</pubDate>
      <link>https://forem.com/warpdotdev/how-to-draw-styled-rectangles-using-the-gpu-and-metal-246a</link>
      <guid>https://forem.com/warpdotdev/how-to-draw-styled-rectangles-using-the-gpu-and-metal-246a</guid>
      <description>&lt;p&gt;This is a tutorial on drawing styled rectangles using Metal graphics shaders. Learn how to draw a rectangle, add borders, round the rectangle’s corners, and fill the rectangle with linear gradients.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why render on the GPU
&lt;/h2&gt;

&lt;p&gt;For apps that require fast rendering, such as video processing apps or 3D game apps, using the GPU for rendering is often required. The GPU has more cores and so can perform data-parallel calculations like calculating pixel position and color very quickly.&lt;/p&gt;

&lt;p&gt;The tradeoff of using the GPU is that we have to implement shaders. However, for a modern UI app, you'd only have to implement them for glyphs, icons, and rectangles. &lt;a href="https://www.warp.dev/" rel="noopener noreferrer"&gt;Warp&lt;/a&gt;'s UI, for example, is entirely composed of those three primitives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyudr5unr30258eg5bhpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyudr5unr30258eg5bhpg.png" alt="Rectangles, glyphs, and icons are all the primitives we used to compose Warp's UI."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tutorial will focus just on shaders for rectangles. The snack bar, centered at the top of the window, is just a rectangle with a border and rounded corners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will walk through drawing a rectangle, adding borders, rounding the rectangle’s corners, and filling the rectangle with a linear gradient.&lt;/strong&gt; We will cover interesting graphics concepts like distance fields, vector projections, and antialiasing. &lt;/p&gt;

&lt;p&gt;This tutorial is aimed at beginners who are new to GPU rendering. The code examples are in &lt;a href="https://developer.apple.com/documentation/metal/" rel="noopener noreferrer"&gt;Metal&lt;/a&gt;, Apple’s official shader API. &lt;/p&gt;

&lt;p&gt;Here is the table of contents: you can feel free to jump to any section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why render on the GPU&lt;/li&gt;
&lt;li&gt;How to draw a basic rectangle using shaders&lt;/li&gt;
&lt;li&gt;How to draw borders on a rectangle using shaders&lt;/li&gt;
&lt;li&gt;How to draw rounded rectangles using shaders&lt;/li&gt;
&lt;li&gt;How to fill rectangles with gradients using shaders&lt;/li&gt;
&lt;li&gt;Putting it all together&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How to draw a basic rectangle using shaders
&lt;/h2&gt;

&lt;p&gt;We provide instructions to the GPU via a pair of functions: the vertex shader and the fragment shader. The vertex shader’s job is to produce the positions that need to be drawn. The fragment shader takes these positions and determines the color for every pixel within these position boundaries. In the case of a triangle, the vertex shader produces the three vertices, and the fragment shader fills the triangle pixel by pixel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9bwd7hib8inqlc2wfz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9bwd7hib8inqlc2wfz1.png" alt="A vertex shader transforms an input coordinate on a viewport to one that is independent of the input coordinates. [Apple Metal docs](https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives))"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s draw a rectangle with shaders. &lt;/p&gt;

&lt;p&gt;For the vertex shader, we normalize the coordinates of the vertex to be independent of the viewport. Concretely, we do this by dividing the coordinates by half of the viewport (see the diagram above from these &lt;a href="https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives" rel="noopener noreferrer"&gt;Apple Metal docs&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;For the fragment shader, we can return the interpolated color of the pixel.&lt;/p&gt;

&lt;p&gt;Here is the code we’re using. Notice that we also pass in &lt;code&gt;PerRectUniforms&lt;/code&gt;—a struct that holds the origin, size, and color information of the rectangle the vertex comes from. This information helps us infer information about each vertex and calculate the color of each pixel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rect_vertex_shader(
   uint vertex_id [[vertex_id]],
   constant vector_float2 *vertices [[buffer(0)]],
   constant PerRectUniforms *rect_uniforms [[buffer(1)]],
   constant Uniforms *uniforms [[buffer(2)]]
) {
   float2 pixel_space_position = vertices[vertex_id] * rect_uniforms-&amp;gt;size + rect_uniforms-&amp;gt;origin;
   vector_float2 viewport_size = uniforms-&amp;gt;viewport_size;

   RectFragmentData out;
   out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
// Normalize coordinates of the vertex
   out.position.xy = pixel_space_position / (viewport_size / 2.0);
   out.position.z = rect_uniforms-&amp;gt;z_index / uniforms-&amp;gt;max_z_index;
   out.color = rect_uniforms-&amp;gt;background_color;

   return out;
}

fragment float4 rect_fragment_shader(RectFragmentData in [[stage_in]])
{
   return in.color;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For brevity, we’ll focus only on writing the shaders for the rendering pipeline. We recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Apple’s &lt;a href="https://developer.apple.com/documentation/metal/basic_tasks_and_concepts/using_metal_to_draw_a_view_s_contents" rel="noopener noreferrer"&gt;‘Using Metal to Draw a View’s Content’&lt;/a&gt; to learn how to use MetalKit to create a view and send rendering commands to draw on it&lt;/li&gt;
&lt;li&gt;Apple’s &lt;a href="https://developer.apple.com/documentation/metal/using_a_render_pipeline_to_render_primitives" rel="noopener noreferrer"&gt;‘Using a Render Pipeline to Render Primitives’&lt;/a&gt; explains how to use the shaders in the rendering commands to draw a shape on the view.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We used the code above, along with MetalKit and our custom layout engine, to draw the first iteration of tabs in Warp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf8iw6shvsj63xrjr63q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf8iw6shvsj63xrjr63q.png" alt="first iteration of tabs in Warp"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to draw borders on a rectangle using shaders
&lt;/h2&gt;

&lt;p&gt;While drawing borders, the rectangle stays the same size and shape. Hence, we do not need to alter our vertex shader. Instead, we just have to edit the fragment shader.&lt;/p&gt;

&lt;p&gt;The fragment shader processes a pixel at a time. For each pixel, we have to figure out whether it is inside or outside the border. First, we calculate the border boundary by subtracting the border widths corresponding to the pixel’s quadrant. If the pixel is above and to the right of the center of the rectangle, then we should subtract the rectangle corner by the border top and the border bottom. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7j47ypwymmfoowinsew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7j47ypwymmfoowinsew.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And so on and so forth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fragment float4 rect_fragment_shader(
       RectFragmentData in [[stage_in]],
constant Uniforms *uniforms [[buffer(0)]])
{
   vector_float2 border_corner = in.rect_corner;
   if (in.position.y &amp;gt;= in.rect_center.y) {
       border_corner.y -= in.border_bottom;
   } else {   
       border_corner.y -= in.border_top;
   }
   if (in.position.x &amp;gt;= in.rect_center.x) {
       border_corner.x -= in.border_right;
   } else {
       border_corner.x -= in.border_left;
   }
   ...

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the border corner obtained, we can then assign pixels outside the border corner with the border color, the ones inside with the background color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
return in.position.xy &amp;gt; border_corner ? border_color : background_color;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s how we form the bordered rectangle in Warp’s tab bar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1lqs6qhh3hnzc38cw40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1lqs6qhh3hnzc38cw40.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to draw rounded rectangles using shaders
&lt;/h2&gt;

&lt;p&gt;To round the corners of the rectangle, we need a framework that tells the the fragment shader whether a pixel falls inside or outside a &lt;strong&gt;rounded edge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distance fields&lt;/strong&gt; are functions that help us define non-rectangular edges. Given a pixel, a distance field outputs its distance to the nearest edge of a shape. This is a useful API for fragment shaders, which only has access to one pixel at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using distance fields to express rounded corners
&lt;/h3&gt;

&lt;p&gt;The following diagram draws out four distance fields of a rectangle (lines). Each line represents pixels that are the same distance away from the edge of a rectangle, similar to contour maps in geography. Notice that each distance field matches the outline of rounded rectangles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxgqm9w1k14ikvne9egi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxgqm9w1k14ikvne9egi.png" alt="Distance fields of a rectangle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A rounded rectangle is simply a union of a “shrunk rectangle” and the area within the distance field of size &lt;code&gt;corner_radius&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farq1tg1ekl47lxozq9e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farq1tg1ekl47lxozq9e5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We derive the new rectangle corner of the shrunk rectangle by subtracting the corner radius from the original rectangle corner. We want pixels outside the shape to be transparent, i.e. for the alpha channel of its color to be 0. And pixels inside to be opaque, i.e. alpha = 1. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4r0exmrgwbmmbdr57aa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4r0exmrgwbmmbdr57aa.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The distance field of our rounded shape is simply the distance field of the shrunk rectangle minus the corner radius.&lt;/p&gt;

&lt;p&gt;The formula for distance field of a rectangle is:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jfey74nzzyzehmzo2xq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jfey74nzzyzehmzo2xq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;p&lt;/code&gt; is the vector of the pixel, and &lt;code&gt;R&lt;/code&gt; is the vector of the rectangle corner. Check out Inigo Quilez’s derivation of the distance field function &lt;a href="https://www.youtube.com/watch?v=62-pRVZuS5c" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The actual R of the shrunk rectangle is &lt;code&gt;rect_corner-corner_radius&lt;/code&gt;. Substituting that in gives us a distance function for the shrunk rectangle. We then get the distance function for the rounded rectangle by subtracting the corner radius.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;float distance_from_rect(vector_float2 pixel_pos, vector_float2 rect_center, vector_float2 rect_corner, float corner_radius) {
   vector_float2 p = pixel_pos - rect_center;
   vector_float2 q = abs(p) - (rect_corner - corner_radius);
   return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - corner_radius;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Anti-aliasing
&lt;/h3&gt;

&lt;p&gt;The code above will render a rectangle with rounded corners. However, the rounded edges will look jagged: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1gi6jmfu0odggguezfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1gi6jmfu0odggguezfs.png" alt="A jagged edge from LearnOpenGL's antialiasing tutorial "&gt;&lt;/a&gt; From &lt;a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing" rel="noopener noreferrer"&gt;learnopengl.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This happens because there is a sharp cutoff between the pixels inside the shape and outside the shape. We only encounter this issue now because with rounded corners, the edges of our shape do not fit squarely into the pixel grid of a screen. The solution is to gradually transition the pixel color across the boundary. A handy function is &lt;code&gt;smoothstep(edge0, edge1, x)&lt;/code&gt; which performs a smooth &lt;a href="https://en.wikipedia.org/wiki/Hermite_interpolation" rel="noopener noreferrer"&gt;Hermite interpolation&lt;/a&gt; between 0 and 1 when edge 0 &amp;lt; x &amp;lt; edge1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (corner_radius &amp;gt; 0) {
   color.a *= 1.0 - smoothstep(-0.75, -0.1, shape_distance);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For rounded rectangles with borders, we perform the same antialiasing between the background and border. Here in this code, &lt;code&gt;background_distance&lt;/code&gt; refers to the distance field from the non-border contents of  the rectangle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;background_distance = distance_from_rect(in.position.xy, in.rect_center, border_corner, corner_radius);
if (border_color.a != 0) {
   color = mix(background_color, border_color, smoothstep(-0.5, 0.5, background_distance));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can render rounded bordered UIs like in Warp:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhozwj4qcg0jv8ix51oxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhozwj4qcg0jv8ix51oxw.png" alt="Warp's snack bar is a rounded bordered rectangle.&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  How to fill rectangles with gradients using shaders
&lt;/h2&gt;

&lt;p&gt;Linear gradients can be specified by a start and end color, as well as the starting coordinates and the ending coordinates. For example, we can specify a horizontal gradient using (0,0) and (0,1) as coordinates. &lt;/p&gt;

&lt;p&gt;We can perform the mixing of the colors with Metal’s mix function: &lt;code&gt;mix(start_color, end_color, h)&lt;/code&gt;. It returns the linear blend of &lt;code&gt;start_color + (end_color – start_color) * h&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We want the variable &lt;code&gt;h&lt;/code&gt; to represent the progression of the pixels along the direction of the gradient. To do so, the trick is to project the position of the pixel to the direction of the gradient. Pixels closer to the end of the gradient will have a larger magnitude and pixels closer to the start of the gradient will have a smaller magnitude.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4v2oj6gbvaekmuadmwlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4v2oj6gbvaekmuadmwlf.png" alt="vector projection"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;float4 derive_color(float2 pixel_pos, float2 start, float2 end, float4 start_color, float4 end_color) {
   float2 adjusted_end = end - start;
   float h = dot(pixel_pos - start, adjusted_end) / dot(adjusted_end, adjusted_end);
   return mix(start_color, end_color, h);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now produce gradient UI elements, like this header in Warp:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6zwzlwyo0ehg9tbt834.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6zwzlwyo0ehg9tbt834.png" alt="A header in Warp's UI with a linear gradient&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;You can read through the complete code sample &lt;a href="https://github.com/warpdotdev/samples/blob/main/how-to-draw-rectangles-using-gpu.metal" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alongside glyphs and images, the rectangles we produce from these shaders form the UI surface of Warp. Using the GPU for rendering is what enables us to render large amounts of terminal text and UI at over 60fps on a 4K screen. &lt;/p&gt;

&lt;p&gt;Our newer and more complicated UI components are compositions of these building blocks. This has enabled us to create a robust and maintainable UI framework. The code for all our primitives span only 300 lines. &lt;/p&gt;

&lt;p&gt;If you want a fast performant terminal with modern UI, come join our waitlist on our website &lt;a href="https://www.warp.dev" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>graphics</category>
    </item>
  </channel>
</rss>
