-
Notifications
You must be signed in to change notification settings - Fork 326
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
Cursor aware Component Browser #5770
Conversation
Fixes after rebase Add some docs
# Conflicts: # app/gui/src/controller/searcher.rs
# Conflicts: # app/gui/src/controller/searcher.rs # app/gui/src/presenter/searcher.rs
app/gui/src/controller/searcher.rs
Outdated
@@ -628,7 +439,8 @@ impl Searcher { | |||
let def_id = graph.graph().id; | |||
let def_span = double_representation::module::definition_span(&module_ast, &def_id)?; | |||
let module_repr: Rope = module_ast.repr().into(); | |||
let position = module_repr.offset_to_location_snapped(def_span.end); | |||
// TODO: is it correct? | |||
let position = module_repr.offset_to_location_snapped(def_span.end - text::ByteDiff(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subtracting one byte from position helps to find local variables declared inside main
function. Apparently the engine thinks that locals are out of scope otherwise and doesn't send them in completion
response. This issue should be properly investigated separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make a GH issue for that and put its number here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed the issue completely by searching position before creating a new node.
let old_context = old_input.context().map(|ctx| ctx.into_ast().repr()); | ||
|
||
self.invalidate_picked_suggestions(); | ||
let context_changed = old_context != new_context; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if we're doing the correct thing here. Previous code compared expressions, now we compare access chains, and they usually None
. So we are reloading the list far less frequently now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct. Before we were filtering the components by expected argument type (and it depended on the left side of the expression), but now the filtering depends only on accessor chain (i.e. the context).
app/gui/src/controller/searcher.rs
Outdated
assert!(are_same(&arg1.entry, &test_var_1)); | ||
assert!(are_same(&arg2.entry, &test_var_1)); | ||
|
||
// TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what's going on in this part of the test, I will fix it or remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems it is not compatible with the new behavior, removed this piece
let doc_string = documentation.to_string(); | ||
let documentation_html = doc_parser.generate_html_doc_pure(doc_string); | ||
self.documentation_html = Some(documentation_html.unwrap()); | ||
// TODO: we need the Scala parser in searcher tests only for HTML documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look like the correct change, but removing the doc parser usage allows to turn all searcher tests into native ones. This is a huge advantage while developing, though I probably should revert this afterwards and make searcher tests wasm_test
again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the same time, the documentation for snippets is extremely primitive and doc parser doesn't really bring much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can stay with this change. Before the searcher, tests were usually wasm_bindgen anyway, because we used the Scala parser. But I guess it's no longer the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, now the tests use Rust parser in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to remove the scala parser from here, it does not bring us much.
let range_start = raw_range.start; | ||
let range_end = raw_range.end; | ||
Self::ensure_on_char_boundary(&new_input, range_start)?; | ||
Self::ensure_on_char_boundary(&new_input, range_end)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw rare panics when one of the indices was not on char boundary inside the input, which was weird. This code helps to mitigate the issue if it ever happens again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd "inline" the range_start
and range_end
variables. They won't make the lines too long.
@@ -411,6 +411,7 @@ pub struct NodeMetadata { | |||
/// | |||
/// The methods may be defined for different types, so the name alone don't specify them. | |||
#[serde(default, deserialize_with = "enso_prelude::deserialize_or_default")] | |||
// TODO: It is not used anywhere. Deprecate? | |||
pub intended_method: Option<MethodId>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now all usages of the intended_method
are removed. We are no longer using it to communicate with the engine, so this field should be probably removed or marked as deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marked it as deprecated and removed all usages from the code.
let cursor_position_before = | ||
(cursor_position > text::Byte(0)).then(|| cursor_position - text::ByteDiff(1)); | ||
let ast_stack_on_left = | ||
cursor_position_before.map(|cp| AstAtPositionFinder::find(&ast.elem, cp)); | ||
let edited_ast_on_left = ast_stack_on_left.map_or_default(EditedAst::new); | ||
let edited_ast = if edited_ast_on_left.edited_name.is_none() { | ||
let ast_stack_on_right = AstAtPositionFinder::find(&ast.elem, cursor_position); | ||
let edited_ast_on_right = EditedAst::new(ast_stack_on_right); | ||
if edited_ast_on_right.edited_name.is_some() { | ||
edited_ast_on_right | ||
} else { | ||
edited_ast_on_left | ||
} | ||
} else { | ||
edited_ast_on_left | ||
}; | ||
let ast = InputAst::Line(ast); | ||
Self { ast, cursor_position, edited_ast } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code deserves a bit of explanation in the comment.
let range_start = raw_range.start; | ||
let range_end = raw_range.end; | ||
Self::ensure_on_char_boundary(&new_input, range_start)?; | ||
Self::ensure_on_char_boundary(&new_input, range_end)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd "inline" the range_start
and range_end
variables. They won't make the lines too long.
#[derive(Clone, Debug, Fail)] | ||
#[allow(missing_docs)] | ||
#[fail(display = "Not a char boundary: index {} at '{}'.", index, string)] | ||
pub struct NotACharBoundary { | ||
index: usize, | ||
string: String, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put it in Errors
section somewhere at the top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
app/gui/src/controller/graph.rs
Outdated
impl span_tree::generate::Context for Handle { | ||
fn call_info(&self, id: node::Id, name: Option<&str>) -> Option<CalledMethodInfo> { | ||
let db = &self.suggestion_db; | ||
let metadata = self.module.node_metadata(id).ok()?; | ||
let db_entry = db.lookup_method(metadata.intended_method?)?; | ||
// If the name is different than intended method than apparently it is not intended anymore | ||
// and should be ignored. | ||
let matching = if let Some(name) = name { name == db_entry.name } else { true }; | ||
matching.then(|| db_entry.invocation_info()) | ||
fn call_info(&self, _id: node::Id, _name: Option<&str>) -> Option<CalledMethodInfo> { | ||
None | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have Empty
context for such cases: https://github.com/enso-org/enso/blob/d8f6f3fd52d87f63d8b067b609162423ed23ebcc/app/gui/language/span-tree/src/generate/context.rs
So, if we are not in some weird generics' machinery, the controller should not implement Context at all, and use Empty
instead if itself where needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, also removed a couple of tests that relied on arguments information from node metadata.
let old_context = old_input.context().map(|ctx| ctx.into_ast().repr()); | ||
|
||
self.invalidate_picked_suggestions(); | ||
let context_changed = old_context != new_context; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct. Before we were filtering the components by expected argument type (and it depended on the left side of the expression), but now the filtering depends only on accessor chain (i.e. the context).
app/gui/src/test.rs
Outdated
@@ -264,7 +264,7 @@ pub mod mock { | |||
let mut json_client = language_server::MockClient::default(); | |||
let mut binary_client = binary::MockClient::new(); | |||
// Creating a searcher controller always triggers a query for completion. | |||
controller::searcher::test::expect_completion(&mut json_client, &[]); | |||
// controller::searcher::test::expect_completion(&mut json_client, &[]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code. Remove if unneeded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it.
@@ -850,6 +852,8 @@ ensogl::define_endpoints! { | |||
/// Set the node expression. | |||
set_expression (node::Expression), | |||
|
|||
edit_expression (text::Range<Byte>, ImString), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. @farmaazon I think this answers my questions about how the frontend uses the Scala doc parser!
self.graph.graph().set_expression(node_id, expression)?; | ||
if let Mode::NewNode { .. } = self.mode.as_ref() { | ||
let graph = self.graph.graph(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this bound for convenience, or to capture the value returned by graph()
before a subsequent operation changes it? If it's for convenience it should be used consistently so it doesn't look like something tricky is happening; if it is intentionally capturing an older value, a comment would be helpful.
/// Return `true` if the `index` is on a char boundary inside `string` and can be used as a | ||
/// range boundary. Otherwise, return `false` and report an error. | ||
fn ensure_on_char_boundary(string: &str, index: usize) -> FallibleResult<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs don't match return type.
let cursor_position_in_name = self.cursor_position - edited.range.start; | ||
let name = ast::identifier::name(&edited.ast); | ||
name.map_or("", |n| { | ||
&n[text::Range::new(text::Byte(0), text::Byte(0) + cursor_position_in_name)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This slice can panic if edited_ast
and cursor_position
are inconsistent. That shouldn't happen, but the invariant relied on is not trivial--slicing with get
here would reduce the consequences of potential future bugs.
QA reportIssuesWhen starting editing node in the middle of identifier, I see CB with all entries first, and the filtering appears only after a moment. cursor-aware-test-2023-03-01_13.47.34.mp4Bad UXBesides the above issue, the PR meets the acceptance criteria. Still, when testing, I found additional things worsening my UX:
Solution for it is implementing #5356 we may consider keeping this branch unmerged until we implement and merge that task to it. |
@vitvakatu what is the status of the PR? It was accepted 2 weeks ago (!), and the QA was done then. Why is it not merged yet? |
@wdanilo as we agreed with @farmaazon, we will merge #5356 into this PR, and then merge the whole thing. The QA done 2 weeks ago discovered a few issues, and they were addressed during that two weeks. |
@vitvakatu gotcha! Sorry, I forgot about it. Please make short notes about agreements in the future so we will not be missing them. Thanks again! :) |
@vitvakatu as we are making changes here, could you please move it to draft stage and request review again after the code is final, please? |
Closes #5356 Implements recognition of qualified names in the input of the CB. Now accepting of the suggestion produces a valid code, and only necessary imports are added. https://user-images.githubusercontent.com/6566674/225487445-855dfb04-47c7-44c9-bc92-4bb5663d500a.mp4 https://user-images.githubusercontent.com/6566674/225487486-028d1ae0-02e0-4bbc-a623-7d4e620c87d9.mp4 # Important Notes - Filtering by inserted code no longer works, but instead, we filter out entries with qualified names not matching the edited accessor chain. It may result in worsened behavior when typing expressions with local variables as `this` argument.
QA was done in #5960 on an identical state of the code. |
Fix a stupid typo introduced in #5770. It affected the code generated by CB while previewing suggestions.
* develop: Layout fixes (#6066) Use new Enso Hash Codes and Comparable (#6060) Search suggestions by static attribute (#6036) Use .node-version for pinning Node.js version (#6057) Fix code generation for suggestion preview (#6054) Implementation of EnsoGL predefined Rectangle shape. (#6033) Tidy up the public module level statics (#6032) Cursor aware Component Browser (#5770)
Pull Request Description
Closes #5220
This PR implements the cursor-aware behavior of the CB, as described in pivotal issue
2023-02-28.13-12-48.mp4
2023-03-06.17-24-59.mp4
Important Notes
intended_method
of the node's metadata is marked as deprecated, and all usages are removed.This is how the snippets docs look now:
Checklist
Please include the following checklist in your PR:
Scala,
Java,
and
Rust
style guides.
./run ide build
and./run ide watch
.