Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Tasks in Zed #7108

Closed
1 task done
SomeoneToIgnore opened this issue Jan 30, 2024 · 21 comments
Closed
1 task done

Proposal: Tasks in Zed #7108

SomeoneToIgnore opened this issue Jan 30, 2024 · 21 comments
Assignees
Labels
enhancement [core label]

Comments

@SomeoneToIgnore
Copy link
Contributor

SomeoneToIgnore commented Jan 30, 2024

Check for existing issues

  • Completed

Describe the feature

Idea

Provide multiple new user-facing features:

  • allow defining custom tasks via json configs in the project
  • add more UI for running those:
    • add a file finder-like modal with all actions collected from Zed parts
    • add a new pane with running task tabs (and their output)
    • add new action bindings for (re)running the tasks
    • allow accessing any task using keyboard only
  • consider extending the approach to rust-analyzer LSP and, if ready by that point, BSP and the plugin system

Proposal

#5242 and more questions/mentions under various names.
The idea is to have a way to (re)run various tasks from the editor, either scripted/configured by users or provided by language features (LSP servers, plugins, etc.)

Current situation with tasks outside of Zed

LSP

There's only one "generic" LSP thing for tasks currently: codeLens, that may carry an extra command data for the LSP server to run when queried.
All other ways to run things are editor-specific and are provided by the editor API.

VSCode draws code lens as phantom text on top of certain elements (defined with the range), occupying extra vertical space.
Code lens could be resolved later, and may not contain any command to run and be just a text "label".

Spec:

https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens

Some example rust-analyzer sends back to Zed's prototype
CodeLens {
  range: Anchor {
      timestamp: Lamport {0: 1},
      offset: 10472,
      bias: Left,
      buffer_id: Some(
          1,
      ),
  }..Anchor {
      timestamp: Lamport {0: 1},
      offset: 10477,
      bias: Right,
      buffer_id: Some(
          1,
      ),
  },
  command: Some(
      Command {
          title: "▶\u{fe0e} Run Tests",
          command: "rust-analyzer.runSingle",
          arguments: // ... snip of a json array
      },
  ),
  data: None,
},
CodeLens {
  range: Anchor {
      timestamp: Lamport {0: 1},
      offset: 10472,
      bias: Left,
      buffer_id: Some(
          1,
      ),
  }..Anchor {
      timestamp: Lamport {0: 1},
      offset: 10477,
      bias: Right,
      buffer_id: Some(
          1,
      ),
  },
  command: Some(
      Command {
          title: "Debug",
          command: "rust-analyzer.debugSingle",
          arguments: // ... snip of a json array
      },
  ),
  data: None,
},

The problem with this part of LSP is the facts that running of commands is editor-specific.
In particular, for tasks, there have to be two extra phases done:

  • every command ID and handler are registered by the editor extension using vscode.commands.registerCommand API
    The command handler on the extension side is responsible for deserializing command payload and using vscode.tasks.executeTask API to run the constructed task.

  • later, commands are either triggered by the extension via vscode.commands.executeCommand API or the editor itself handles code lens clicks and runs the command

If we want to support those in Zed, we have to manually implement the command deserialization into the task at least, and keep it up to date with the extension code: all command arguments are typeless json::Value, and only the extension knows about its structure.
Moveover, additional filtering will be needed for certain lens commands like rust-analyzer.debugSingle as those rely on more VSCode-specific extensions.

Last but not least, code lens support is differently (un)supported in different LSP servers, e.g. typescript-language-server does not provide test task lens as rust-analyzer does.

VSCode

Build Server Protocol (BSP) https://build-server-protocol.github.io

It's very raw and current (few) BSP language server do not have tasks for tests.
List of build tools supported currently: https://build-server-protocol.github.io/docs/overview/implementations
note no Python, TS, JS, etc. there.

Spec part for the test tasks: https://build-server-protocol.github.io/docs/specification#buildtargettest-request shows that BSPs are, in theory, capable to build, run various kinds of tests and spawn debug sessions.

Approaching the implementation

Zed supports neither of the above, to avoid future conflicts with the plugin design, it looks more reasonable to develop a minimalistic, standalone feature for now.

Key ideas:

  • avoid excessive UI changes, for custom, static user task configurations on-disk json is fine
  • tasks could be (re)run without using mouse/trackpad
  • committing to large changes as tasks via BSP is unrealistic for now, we should be more incremental

Start with the static tasks

VSCode has this schema for its tasks:

which we can use as an inspiration, simplified, for a task definition for Zed.

In this iteration, the tasks should be able to run user shell commands on the same machine via Zed, and use its serialized form in directory-level and global user configs.
During the implementation, we will have to consider main task-related design questions:

  • integration with the remote part of Zed
    We have to disallow sharing remotely all tasks by default for security reasons, which means extra code on the directory-level config sharing code, is there something else worth considering?

We do not have any established way to grant access to host capabilities, so for now the remote tasks launch seems to be out of scope.

  • how would tasks be run?
    We can create a file_finder-like modal with the task names and an action to spawn it, then we'll be use the keyboard only to spawn the tasks.
    Tasks definition may get a config entry for keybindings, but that might need more work around the scopes, but at least we should provide a way to rerun the last task, to be able to run cargo check in a loop.
    With cargo check on re-run we can disable the check on save rust-analyzer's feature and make LSP more responsive in collab mode, re-checking only when nedeed on the host.

  • how would tasks be presented onscreen?
    Local shell tasks would benefit from a terminal output-like display, with corresponding coloring, cmd+click and other TerminalElement features, but is its code in Zed ready for that?
    We could use a readonly Editor as we do in the LSP logs panel, but this may hang due to large (tens of megabytes) amounts of text pushed to it.
    Based on that decision, the pane for the task output might differ (terminal elements are now allowed only in the terminal pane), but seems that creating another pane with task tabs is a possible way to go.

  • place to store in Zed data
    This has to be at least a project level, to know about the terminal, worktrees and LSP, but potentially a workspace is a better option?

  • define a Task interface (name up to change, but not Task as occupied by Zed's runtime already)
    @osiewicz starts on the design and has proposals for doing that as a short-lived definition object (Rust enum), that can be constructed from any other data.
    This way, we can keep e.g. lens data in LSP format in some cache, that would be able to emit a list of Tasks on demand, which will go to the corresponding UI elements.

Making tasks more generic

  • Templatize the static configuration
    If Zed provides a good set of ${workspace_path}, ${buffer_path}, etc. variables.
    The idea needs evaluation, cargo test runner may be a good attempt to start with.

  • Support rust-analyzer better

Either of options below brings a need for TaskProvider-like logic, which is close to what extensions API needs.

* We can hardcode rust-analyzer's lens' parameter structure in Zed and construct tasks from it.
After supporting the lens, Zed gets a set of tasks for running Rust tests, finding implmentations, etc.
While tests fall into the "run shell command" category, the other ones may involve interaction with the editor.
Gaining generic test tasks for every Rust project is a great step already, hence the rest of the lens' commands can be supported later.

* Another rust-analyzer-specific task provider is an `experimental/tasks` LSP extension request: https://github.com/rust-lang/rust-analyzer/blob/11b401d0b4a743ed1a30791d351df447d691823f/crates/rust-analyzer/src/lsp/ext.rs#L297
It returns a list of cargo-specific tasks that are applicable at the current caret position.
The LSP response sends enough data to reconstruct a VSCode task from it, hence Zed will be able to derive corresponding shell tasks for those.
Allows running current test and test module's test, cargo check and build for the current crate.

Consider Zed plugin API for tasks and tests and if/how BSP can be used there

BSP:

  • Would need to write a BSP server integration in Zed (yet it's very close to LSP in concepts, so some parts could be reused)
  • And would need a BSP server written for the language

Plugins:

  • Would need to have a plugin system to start with!
  • Needs tasks API designed and integrated (if TaskProvider described above already exists, it seems to be simpler)

At this point, it seems enough to take a break and wait for either of the approaches becomes more mature, fixing bugs in the existing tasks support.
Both plugin and BSP require some code to write to support tasks for a language.
Rust BSP can be already attempted, @osiewicz had done some prototype work on it, and I would like to see what it takes to make it on par with rust-analyzer extension's tasks in VSCode.

If applicable, add mockups / screenshots to help present your vision of the feature

No response

@SomeoneToIgnore SomeoneToIgnore added enhancement [core label] triage Maintainer needs to classify the issue admin read Pending admin review labels Jan 30, 2024
@JosephTLyons JosephTLyons removed triage Maintainer needs to classify the issue admin read Pending admin review labels Feb 2, 2024
@osiewicz
Copy link
Contributor

osiewicz commented Feb 5, 2024

Hey, here's a quick update on the matter;
Together with @SomeoneToIgnore we've started on the static runnables front - the code is available on runnables branch.
At the moment, Runnables don't exist in the UI yet; there's an action (with a parameter) called RunActionWithName (we're gonna change that name most likely) that can be used to start runnables defined in runnables.json config file. At the moment the output of a task is being displayed in a debug console.
The next steps for us are as follows:

  • Kirill will work on integration with terminal; we plan to display the progress/output of a runnable there.
  • I will work on creating a runnables panel that will be used to schedule new runnables/track progress of ongoing runnables.

I expect us to revisit the core implementation at some point as we get closer to the MVP; particularly, we still need to figure out/facilitate runnable duplication. Implementing a different Source than a StaticSource should make for a good stress test of it.
We did not start any work on dynamic runnables (the "run test under cursor" kind) yet, as we want to figure out the UX for runnables in general first.

@osiewicz
Copy link
Contributor

osiewicz commented Feb 6, 2024

Another extension worth looking into is allowing one-off runnables spawned from command palette (or some other modal) as described in #7460 ; it's just another source of runnables.

@NeddM
Copy link

NeddM commented Feb 7, 2024

On Neovim it works quite good
image

@osiewicz
Copy link
Contributor

osiewicz commented Feb 8, 2024

Yesterday in discussion with @maxbrunsfeld we came to the following conclusions regarding runnables:

  • For static tasks, since our JSON schema is a subset of VSCode task schema, we should try to stay compatible with them in terms of semantics. If an user copies over a task from VSCode, it is reasonable for them to expect it to work from the getgo.
  • For remote experience, guests should have the possibility of inspecting task output (without screensharing :) ); that sounds like a read-only terminal.
  • Having a panel for runnables history/runnables status is fine, but let's go with a modal for spawning new tasks.
  • I suggested that when the panel is collapsed, the icon in status bar should have it's color change depending on the status of the currently running tasks:
    • Yellow - some tasks are still running, no failures so far.
    • Green - all scheduled tasks have succeeded, yay.
    • Red - some task failed, the rest might still be running.

@matklad
Copy link
Contributor

matklad commented Feb 19, 2024

I've logged some design considirations around tasks a while back in microsoft/language-server-protocol#944, this might be interesting, even if not particularly useful.

@SomeoneToIgnore
Copy link
Contributor Author

Initial step for static runnables had been implemented here: #8009
Will test it for some while, and move onto next steps, involving more dynamic runnables.
Current plan to follow to separate tracks and implement

  • !command Vim-like running of ad-hoc scripts via the runnables modal, to allow rerunning those, if needed.
  • being able to run an arbitrary *.sh file

Future dynamic runnables plan is to work on tree-sitter queries and determine tests with their help — should work for Rust, where r-a detects those as "something in derive macro declaration, that contains the word test".
This will help to avoid using code lens and hardcode around them entirely, and gradually expand it to other languages.

SomeoneToIgnore added a commit that referenced this issue Feb 19, 2024
Part of #7108

This PR includes just the static runnables part. We went with **not**
having a dedicated panel for runnables.
This is just a 1st PR out of N, as we want to start exploring the
dynamic runnables front. Still, all that work is going to happen once
this gets merged.

Release Notes:

- Added initial, static Runnables support to Zed. Such runnables are defined in
`runnables.json` file (accessible via `zed: open runnables` action) and
they can be spawned with `runnables: spawn` action.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Pitor <pitor@zed.dev>
Co-authored-by: Beniamin <beniamin@zagan.be>
osiewicz added a commit that referenced this issue Feb 20, 2024
/cc @SomeoneToIgnore 
Fixes #7460 and partially addresses #7108 
Release Notes:

- N/A
@osiewicz osiewicz changed the title Proposal: Runnables in Zed Proposal: Tasks in Zed Feb 22, 2024
@osiewicz
Copy link
Contributor

osiewicz commented Feb 22, 2024

Initial tasks implementation has landed in Preview yesterday.
There are three new actions at your disposal:

  • zed: open tasks - opens a ZED_DIR/tasks.json file that contains definitions of static tasks (that persist across workspaces and Zed instances).
  • task: spawn which you can use to spawn new runnables. Initially that modal is filled with static tasks from tasks.json.
  • task: rerun reruns the most recently spawned task.

task: spawn modal also supports spawning oneshot tasks, akin to !command Vim-like running of ad-hoc scripts. Simply type the command into the input and use cmd-enter to confirm. Oneshot tasks show up in subsequent uses of task modal, though they do not persist across sessions anyhow.

We're now looking towards more dynamic use cases, like running the tests.

@WeetHet
Copy link
Contributor

WeetHet commented Feb 23, 2024

Initial tasks implementation has landed in Preview yesterday. There are three new actions at your disposal:

  • zed: open tasks - opens a ZED_DIR/tasks.json file that contains definitions of static tasks (that persist across workspaces and Zed instances).
  • task: spawn which you can use to spawn new runnables. Initially that modal is filled with static tasks from tasks.json.
  • task: rerun reruns the most recently spawned task.

task: spawn modal also supports spawning oneshot tasks, akin to !command Vim-like running of ad-hoc scripts. Simply type the command into the input and use cmd-enter to confirm. Oneshot tasks show up in subsequent uses of task modal, though they do not persist across sessions anyhow.

We're now looking towards more dynamic use cases, like running the tests.

Cool, can you please implement local task definitions (like in the project dir/.zed/tasks)?

This was referenced Feb 27, 2024
SomeoneToIgnore added a commit that referenced this issue Feb 28, 2024
![image](https://github.com/zed-industries/zed/assets/2690773/e1511777-b4ca-469e-8636-1e513b615368)

Follow-up of
#7108 (comment)

Makes more clear where each task came from, auto (re)load
.zed/config.json changes, properly filtering out other worktree tasks.

Release Notes:

- Added local task configurations
@osiewicz
Copy link
Contributor

osiewicz commented Mar 7, 2024

We've just shipped task contexts (#8675) in yesterday's Preview, which expose a small subset of the editor's state to tasks in read-only fashion.
Yesterday we've brainstormed how tasks could be exposed to extensions. Max suggested that an extension could expose a static tasks.json file and a function that augments baseline task context with variables that are potentially different than the ones available in "mainland" Zed.
That's what I'm gonna work on next

@powersagitar
Copy link

I think tasks would be even better and more useful if we can chain some together, i.e., one depends on another, similar to VS Code's dependsOn property. Here's how it works in VS Code.

@dangh
Copy link

dangh commented Mar 15, 2024

I would like to add two suggestions:

  • A way to run "headless" task. For example opening the current repo in the Git Fork app. There's no output so it shouldn't show the output panel.
  • Show the tasks directly in the command palette, not nested under "task: spawn". This is one of the things I missed from Sublime Text. You can find most of the features there.

@failable
Copy link

A way to run "headless" task.

Agree! I am doing this to open the repo in Emacs. #8485 (reply in thread)

@osiewicz
Copy link
Contributor

A way to run "headless" task.

This has just been implemented by @SomeoneToIgnore in #9399 =) It should be out with next Preview on Wednesday (assuming we don't release anything sooner).

@dangh
Copy link

dangh commented Mar 20, 2024

A way to run "headless" task.

This has just been implemented by @SomeoneToIgnore in #9399 =) It should be out with next Preview on Wednesday (assuming we don't release anything sooner).

So I just tried it in the new Zed Preview. reveal: never is not exactly headless. It still create a bottom pane, just not reveal it. What I'm saying is something like quiet: true to not create the pane at all.

@failable
Copy link

So I just tried it in the new Zed Preview. reveal: never is not exactly headless. It still create a bottom pane, just not reveal it. What I'm saying is something like quiet: true to not create the pane at all.

After multi runs, a lots of tabs are left there.

image

@baldwindavid
Copy link
Contributor

baldwindavid commented Mar 23, 2024

@failable You'll want to either change "use_new_terminal" to false or remove the setting entirely as false should be the default. This will still result in a tab for the task in the bottom dock, but successive runs of that same task should use the same tab rather than a new one.

@failable
Copy link

@baldwindavid I've thought setting "allow_concurrent_runs" to false will prevent opening multiple tabs (running multiple instances of the task). Setting both to false now will only leave single tab there. Thanks for the tip! But as @dangh said, it would be nice to have the pane not created at all.

@baldwindavid
Copy link
Contributor

baldwindavid commented Mar 23, 2024

Yes, I have use cases where I'd rather the terminal is not added at all, but the current functionality mixed with keybindings gets us pretty far. Eventually I think it would be nice to be able to configure tasks to handle a number of different scenarios with the concept of a "target". There could be different target types:

None (headless)

"target": "none"

No terminal is displayed at all (i.e. headless).

Terminal Panel

{
  "target": [
      "terminal_panel", {
        "use_new_terminal": true | false,
        "display": "blur" | "reveal" | "focus", | "zoom"
      }
    ],
  • "use_new_terminal" does the same as currently
  • "display" types build on top of each other:
    • "blur" - Tab is added, but terminal panel is not revealed if currently hidden. If the terminal panel is open, the tab is created, but the current terminal tab stays displayed.
    • "reveal" - Tab is added and the terminal panel is revealed if currently hidden, tab is is displayed, but new terminal tab does not get focus.
    • "focus" - Tab gets focus.
    • "zoom" - Tab is focused and zoomed.

Center Panel

{
  "target": [
    "center_panel", {
        "use_new_terminal": true | false,
        "pane": "current" | "adjacent",
        "display": "blur" | "reveal" | "focus" | "zoom"
      }
    ],

Same settings as the terminal panel, but the addition of...

  • "pane"
    • "current" - Opens a new tab in the same current pane.
    • "adjacent" - Opens a tab in a new pane to the right of the current pane if there is not already one. If there is one, just adds a new tab in the pane. If on the right pane already, opens a new tab on the left. This is how "excerpts" work already.

I touched on this a bit in #9445

@nickpoorman
Copy link

This feature is really awesome. But I would like an option to kill the tab after the program terminates (maybe only when terminates with no error code?).

@mocenigo
Copy link

mocenigo commented Apr 14, 2024

I strongly endorse this feature. This would allow to have something like VScode's LaTeX Workshop.

However, this would not replace more active extensions, where exposing also the buffer, search/replace, and grep functionality would be awesome. For instance, there are VSCode extensions that perform all types of manipulations on the text without having to rely on LSP, and porting these would be quite useful.

@SomeoneToIgnore
Copy link
Contributor Author

SomeoneToIgnore commented Apr 24, 2024

This issue seems good to be closed after #10873 is merged, so closing optimistically, as this PR's feature is planned to be done one way or another.

Now, Zed has:

  • task: spawn and task: rerun actions that allow opening the task modal
    Screenshot 2024-04-25 at 02 10 31
    and (re) running taks from configs, plugins or modal input
    Terminal gets/reuses a tab for the task, with its output and some extra metadata printed.

  • Task template format, that supports a few dynamic variables:

    pub enum VariableName {
    filled from Zed and tree-sitter data

  • Global and local tasks.json, the way to import VSCode tasks into Zed format + the way to provide task templates from extensions

  • A way to match tests and have discoverable test (and, maybe, later, other kind of) run icons

This looks like a good place to stop adding new related features and concentrate on polishing the existing functionality, fixing the bugs and straightening out the usability.

So far, there are uncertainties about

Notes on future possible plans:

  • Zed might need to list more task collections, not only the modal with the tasks applicable for the current selection.
    The panel with tests, or all runnables available for the file/project/etc. could be useful, but maybe it's fine to live without them for now?
  • certain language servers, e.g. rust-analyzer, have more advanced infrastructure for task management: WIP: Add support for detecting tests in source files, and implement it for Rust #10873 (comment)
    Would need a way to merge LSP and tree-sitter based tasks, but could provide much better ones for certain servers.
  • BSP seems pretty much the same since last time it was checked, so not really ready to be used in Zed yet
  • if WIP: Add support for detecting tests in source files, and implement it for Rust #10873 does not bring a way to persist tasks in the configs/elsewhere, it would be good to implement
  • for remote Zed projects (ssh-like, not the collaborative ones), we should allow running tasks remotely

Overall, the remaining scope so far seems relatively small compared to zero tasks we've started with in the beginning, so let's consider the rest of the feedback, features and improvements in the new issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement [core label]
Projects
None yet
Development

No branches or pull requests