Skip to content

Commit

Permalink
tasks: Provide task variables from matching runnable ranges in task m…
Browse files Browse the repository at this point in the history
…odal (#12237)

In #12003 we found ourselves in need for precise region tracking in
which a given runnable has an effect in order to grab variables from it.
This PR makes it so that in task modal all task variables from queries
overlapping current cursor position.
However, in the process of working on that I've found that we cannot
always use a top-level capture to represent the full match range of
runnable (which has been my assumption up to this point). Tree-sitter
captures cannot capture sibling groups; we did just that in Rust
queries.

Thankfully, none of the extensions are affected as in them, a capture is
always attached to single node. This PR adds annotations to them
nonetheless; we'll be able to get rid of top-level captures in extension
runnables.scm once this PR is in stable version of Zed.


Release Notes:

- N/A
  • Loading branch information
osiewicz committed May 24, 2024
1 parent 08a3d3a commit 27229bb
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 184 deletions.
40 changes: 23 additions & 17 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,18 +406,24 @@ impl Default for ScrollbarMarkerState {
#[derive(Clone, Debug)]
struct RunnableTasks {
templates: Vec<(TaskSourceKind, TaskTemplate)>,
// We need the column at which the task context evaluation should take place.
offset: MultiBufferOffset,
// We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
column: u32,
// Values of all named captures, including those starting with '_'
extra_variables: HashMap<String, String>,
// Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
context_range: Range<BufferOffset>,
}

#[derive(Clone)]
struct ResolvedTasks {
templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
position: Anchor,
}

#[derive(Copy, Clone, Debug)]
struct MultiBufferOffset(usize);
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
struct BufferOffset(usize);
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
///
/// See the [module level documentation](self) for more information.
Expand Down Expand Up @@ -516,7 +522,7 @@ pub struct Editor {
>,
last_bounds: Option<Bounds<Pixels>>,
expect_bounds_change: Option<Bounds<Pixels>>,
tasks: HashMap<(BufferId, BufferRow), (usize, RunnableTasks)>,
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
}

Expand Down Expand Up @@ -4053,15 +4059,15 @@ impl Editor {
this.discard_inline_completion(false, cx);
let tasks = tasks.as_ref().zip(this.workspace.clone()).and_then(
|(tasks, (workspace, _))| {
let position = Point::new(buffer_row, tasks.1.column);
let position = Point::new(buffer_row, tasks.column);
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
let location = Location {
buffer: buffer.clone(),
range: range_start..range_start,
};
// Fill in the environmental variables from the tree-sitter captures
let mut captured_task_variables = TaskVariables::default();
for (capture_name, value) in tasks.1.extra_variables.clone() {
for (capture_name, value) in tasks.extra_variables.clone() {
captured_task_variables.insert(
task::VariableName::Custom(capture_name.into()),
value.clone(),
Expand All @@ -4082,7 +4088,6 @@ impl Editor {
.map(|task_context| {
Arc::new(ResolvedTasks {
templates: tasks
.1
.templates
.iter()
.filter_map(|(kind, template)| {
Expand All @@ -4092,7 +4097,7 @@ impl Editor {
})
.collect(),
position: snapshot.buffer_snapshot.anchor_before(
Point::new(multibuffer_point.row, tasks.1.column),
Point::new(multibuffer_point.row, tasks.column),
),
})
})
Expand Down Expand Up @@ -4693,7 +4698,7 @@ impl Editor {
self.tasks.clear()
}

fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: (usize, RunnableTasks)) {
fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
if let Some(_) = self.tasks.insert(key, value) {
// This case should hopefully be rare, but just in case...
log::error!("multiple different run targets found on a single line, only the last target will be rendered")
Expand Down Expand Up @@ -7931,7 +7936,7 @@ impl Editor {
snapshot: DisplaySnapshot,
runnable_ranges: Vec<RunnableRange>,
mut cx: AsyncWindowContext,
) -> Vec<((BufferId, u32), (usize, RunnableTasks))> {
) -> Vec<((BufferId, u32), RunnableTasks)> {
runnable_ranges
.into_iter()
.filter_map(|mut runnable| {
Expand All @@ -7953,16 +7958,17 @@ impl Editor {
.start
.row;

let context_range =
BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
Some((
(runnable.buffer_id, row),
(
runnable.run_range.start,
RunnableTasks {
templates: tasks,
column: point.column,
extra_variables: runnable.extra_captures,
},
),
RunnableTasks {
templates: tasks,
offset: MultiBufferOffset(runnable.run_range.start),
context_range,
column: point.column,
extra_variables: runnable.extra_captures,
},
))
})
.collect()
Expand Down
4 changes: 2 additions & 2 deletions crates/editor/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1535,8 +1535,8 @@ impl EditorElement {
editor
.tasks
.iter()
.filter_map(|(_, (multibuffer_offset, _))| {
let multibuffer_point = multibuffer_offset.to_point(&snapshot.buffer_snapshot);
.filter_map(|(_, tasks)| {
let multibuffer_point = tasks.offset.0.to_point(&snapshot.buffer_snapshot);
let multibuffer_row = MultiBufferRow(multibuffer_point.row);
if snapshot.is_line_folded(multibuffer_row) {
return None;
Expand Down
28 changes: 20 additions & 8 deletions crates/editor/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use gpui::{Model, WindowContext};
use language::ContextProvider;
use project::{BasicContextProvider, Location, Project};
use task::{TaskContext, TaskVariables, VariableName};
use text::Point;
use text::{Point, ToOffset, ToPoint};
use util::ResultExt;
use workspace::Workspace;

Expand Down Expand Up @@ -70,14 +70,26 @@ fn task_context_with_editor(
};
let captured_variables = {
let mut variables = TaskVariables::default();
for range in location
.buffer
.read(cx)
.snapshot()
.runnable_ranges(location.range.clone())
let buffer = location.buffer.read(cx);
let buffer_id = buffer.remote_id();
let snapshot = buffer.snapshot();
let starting_point = location.range.start.to_point(&snapshot);
let starting_offset = starting_point.to_offset(&snapshot);
for (_, tasks) in editor
.tasks
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
{
for (capture_name, value) in range.extra_captures {
variables.insert(VariableName::Custom(capture_name.into()), value);
if !tasks
.context_range
.contains(&crate::BufferOffset(starting_offset))
{
continue;
}
for (capture_name, value) in tasks.extra_variables.iter() {
variables.insert(
VariableName::Custom(capture_name.to_owned().into()),
value.clone(),
);
}
}
variables
Expand Down
75 changes: 55 additions & 20 deletions crates/language/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
SyntaxSnapshot, ToTreeSitterPoint,
},
task_context::RunnableRange,
LanguageScope, Outline, RunnableTag,
LanguageScope, Outline, RunnableCapture, RunnableTag,
};
use anyhow::{anyhow, Context, Result};
pub use clock::ReplicaId;
Expand Down Expand Up @@ -3061,41 +3061,76 @@ impl BufferSnapshot {

iter::from_fn(move || loop {
let mat = syntax_matches.peek()?;

let test_range = test_configs[mat.grammar_index].and_then(|test_configs| {
let mut tags: SmallVec<[(Range<usize>, RunnableTag); 1]> =
let mut run_range = None;
let full_range = mat.captures.iter().fold(
Range {
start: usize::MAX,
end: 0,
},
|mut acc, next| {
let byte_range = next.node.byte_range();
if acc.start > byte_range.start {
acc.start = byte_range.start;
}
if acc.end < byte_range.end {
acc.end = byte_range.end;
}
acc
},
);
if full_range.start > full_range.end {
// We did not find a full spanning range of this match.
return None;
}
let extra_captures: SmallVec<[_; 1]> =
SmallVec::from_iter(mat.captures.iter().filter_map(|capture| {
test_configs
.runnable_tags
.get(&capture.index)
.extra_captures
.get(capture.index as usize)
.cloned()
.map(|tag_name| (capture.node.byte_range(), tag_name))
.and_then(|tag_name| match tag_name {
RunnableCapture::Named(name) => {
Some((capture.node.byte_range(), name))
}
RunnableCapture::Run => {
let _ = run_range.insert(capture.node.byte_range());
None
}
})
}));
let maximum_range = tags
let run_range = run_range?;
let tags = test_configs
.query
.property_settings(mat.pattern_index)
.iter()
.max_by_key(|(byte_range, _)| byte_range.len())
.map(|(range, _)| range)?
.clone();
tags.sort_by_key(|(range, _)| range == &maximum_range);
let split_point = tags.partition_point(|(range, _)| range != &maximum_range);
let (extra_captures, tags) = tags.split_at(split_point);

.filter_map(|property| {
if *property.key == *"tag" {
property
.value
.as_ref()
.map(|value| RunnableTag(value.to_string().into()))
} else {
None
}
})
.collect();
let extra_captures = extra_captures
.into_iter()
.map(|(range, name)| {
(
name.0.to_string(),
name.to_string(),
self.text_for_range(range.clone()).collect::<String>(),
)
})
.collect();
// All tags should have the same range.
Some(RunnableRange {
run_range: mat
.captures
.iter()
.find(|capture| capture.index == test_configs.run_capture_ix)
.map(|mat| mat.node.byte_range())?,
run_range,
full_range,
runnable: Runnable {
tags: tags.into_iter().cloned().map(|(_, tag)| tag).collect(),
tags,
language: mat.language,
buffer: self.remote_id(),
},
Expand Down
40 changes: 21 additions & 19 deletions crates/language/src/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet};
use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, Task};
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap;
use http::HttpClient;
use lazy_static::lazy_static;
Expand Down Expand Up @@ -882,12 +882,16 @@ struct RedactionConfig {
pub redaction_capture_ix: u32,
}

#[derive(Clone, Debug, PartialEq)]
enum RunnableCapture {
Named(SharedString),
Run,
}

struct RunnableConfig {
pub query: Query,
/// A mapping from captures indices to known test tags
pub runnable_tags: HashMap<u32, RunnableTag>,
/// index of the capture that corresponds to @run
pub run_capture_ix: u32,
/// A mapping from capture indice to capture kind
pub extra_captures: Vec<RunnableCapture>,
}

struct OverrideConfig {
Expand Down Expand Up @@ -1009,23 +1013,21 @@ impl Language {
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;

let query = Query::new(&grammar.ts_language, source)?;
let mut run_capture_index = None;
let mut runnable_tags = HashMap::default();
for (ix, name) in query.capture_names().iter().enumerate() {
if *name == "run" {
run_capture_index = Some(ix as u32);
let mut extra_captures = Vec::with_capacity(query.capture_names().len());

for name in query.capture_names().iter() {
let kind = if *name == "run" {
RunnableCapture::Run
} else {
runnable_tags.insert(ix as u32, RunnableTag(name.to_string().into()));
}
RunnableCapture::Named(name.to_string().into())
};
extra_captures.push(kind);
}

if let Some(run_capture_ix) = run_capture_index {
grammar.runnable_config = Some(RunnableConfig {
query,
run_capture_ix,
runnable_tags,
});
}
grammar.runnable_config = Some(RunnableConfig {
extra_captures,
query,
});

Ok(self)
}
Expand Down
1 change: 1 addition & 0 deletions crates/language/src/task_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use text::BufferId;
pub struct RunnableRange {
pub buffer_id: BufferId,
pub run_range: Range<usize>,
pub full_range: Range<usize>,
pub runnable: Runnable,
pub extra_captures: HashMap<String, String>,
}
Expand Down
18 changes: 12 additions & 6 deletions crates/languages/src/go/runnables.scm
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
(
(function_declaration name: (_) @run
(#match? @run "^Test.*"))
) @go-test
(
(function_declaration name: (_) @run
(#match? @run "^Test.*"))
) @_
(#set! tag go-test)
)

(
(function_declaration name: (_) @run
(#eq? @run "main"))
) @go-main
(
(function_declaration name: (_) @run
(#eq? @run "main"))
) @_
(#set! tag go-main)
)

0 comments on commit 27229bb

Please sign in to comment.