Skip to content

feat(ui): Calculate Field suggests fields, functions, variables and keywords as the expression is typed (#1240) - #2040

Draft
jgregoriorincon wants to merge 1 commit into
opengeos:mainfrom
jgregoriorincon:feat/1240-calculate-field-completion
Draft

jgregoriorincon wants to merge 1 commit into
opengeos:mainfrom
jgregoriorincon:feat/1240-calculate-field-completion

Conversation

@jgregoriorincon

@jgregoriorincon jgregoriorincon commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part 3b1 of #1240: the first expression box with suggestions, in Calculate Field.

  • A new exprcomplete::expr_edit box shows the shared list from refactor(ui): the Script's completion list and its help move to a shared module (#1240) #2016 under the cursor, with what the language takes there as the parser reads it (query::complete_at, feat(data): complete_at reads what may come next at a point of an expression, from the parser itself (#1240) #1999): the dataset's fields, the functions, the variables and the keywords that start with what's typed, fields first. Nothing is listed by hand, so whatever the language learns is offered too.
  • The list opens once a character of the word is typed, or with Ctrl+Space with nothing typed. It never opens inside 'text'; field values come in a later part.
  • Accepting writes a field quoted when its name needs it ("Pop Est"), or in the quote it was begun with; a function with its bracket open (round(), or closed when it takes no arguments (pi()); a variable or keyword as it is. It is one undo step.
  • The help beside the list shows a function's signature, coloured as the Script colours code (scriptcolor::expr_job), with its translated description and an example; a field's type and alias; what a variable reads or a keyword does.
  • With the list open, Enter takes the highlighted item and doesn't run Calculate, and Escape closes the list but not the dialog. With the list closed, both keys work as before.

Also, as promised in earlier reviews:

field_ref moves from sqlbox.rs to exprcomplete.rs and now quotes every keyword of the language (Kw::ALL), not only nine. A field named case or between, added from Labeling's field buttons, is now quoted instead of breaking the expression.

Testing

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo nextest run --workspace (6434 passed; the DuckDB tests skipped, they fail on my machine for an unrelated reason) and cargo xtask layers, on the branch rebased on main.
  • New tests: what is offered for a word begun, with Ctrl+Space, and inside a quoted name; field_ref's quoting; the list taking no keys without the focus; a quoted name getting no keywords or symbols in completepo+ Enter writing"Pop Est"without calculating, then * roofferinground()` and Escape closing only the list.
  • By hand in Calculate Field: the list and its help for fields, functions, variables and
    keywords; Ctrl+Space on an empty; Enter calculating once the list is closed; the Script pane's list unchanged.

UI evidence

BEFORE
image
image

AFTER

image image

Related issue

Part of #1240

@coderabbitai

coderabbitai Bot commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

let chars: Vec<char> = text.chars().collect();
let (Some(before), Some(mut after)) = (chars.get(..offer.start), chars.get(offer.end..)) else { return };
let put = match of {
Item::Field(name) => match offer.quote {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (confidence: high on the output, medium on how often it happens): a !-opened field (!Ar) is accepted as !name!, but the lexer ends a ! name at the next ! (query.rs, the '!' arm). A field whose name contains ! (e.g. Area!) is therefore written as !Area!!, which reads as Area followed by an unterminated !. The expression then fails with an error instead of the field being used.

Fall back to the quoted form (field_ref) for such names:

Suggested change
Item::Field(name) => match offer.quote {
Item::Field(name) => match offer.quote.filter(|q| *q != '!' || !name.contains('!')) {

A regression test with a !-named field would pin this down.

}

/// What `item` (as shown) of `offer` stands for.
fn find<'a>(offer: &'a Offer, item: &str) -> Option<&'a Item> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (confidence: medium): find maps a shown string back to its Item by string equality, and Items are not unique by string. A dataset field named exactly like a keyword or variable (e.g. a field NOT or IN, or $area) is listed before the keyword with the same text. Highlighting the keyword and pressing Enter then resolves to the field and writes "NOT", so the keyword can't be picked. The same lookup feeds the icon and the help.

The fix is to carry the index of the Item through keys/show (or to make the shown strings unique) instead of looking it up again by text.

}
of.extend(c.keywords.iter().filter(|k| fits(k.word())).map(|k| Item::Keyword(*k)));
}
of.truncate(MAX_ITEMS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quality (confidence: high): the list is cut silently at 50 items. The doc on MAX_ITEMS says it "scrolls", which is true only up to 50. Fields are added first, so on a table with 50 or more fields, Ctrl+Space shows fields only and no functions, variables or keywords are offered. Typing a prefix avoids this, but nothing says the list was cut. Either lift the cap (the shared list already scrolls) or fix the doc comment, and add a test with 50+ fields.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • !-quoted field names containing ! are written wrong. accept writes !name! for any name opened with !, but the lexer ends a ! name at the next !, so Area! becomes !Area!!. The expression then errors rather than using the field. Inline suggestion posted at crates/ui-egui/src/exprcomplete.rs:250. Confidence: high on the output, medium on how often it happens.
  • Keyword and variable items can't be picked when a field has the same text. find matches the shown string back to its item. A field named NOT, IN or $area shadows the keyword or variable, so Enter writes the field. Posted at exprcomplete.rs:196. Confidence: medium.
  • Suggestions end silently at 50. The cap hides functions, variables and keywords on tables with 50 or more fields, and the doc comment says the list scrolls. Posted at exprcomplete.rs:92. Confidence: high.

Security

  • None found. Expression text is bounded by MAX_LEN and the parser's depth limits, and the list only inserts names from the dataset or the language catalogs.

Performance

  • Nothing significant. complete_at runs once per text or cursor change and is cached through Worked. The per-frame field-name hash is small.

Quality

  • Low confidence, left in the summary only: when the cursor sits before an existing (, accepting round( gives round((. Accepting a field already handles a following closing quote, so the function path could do the same.
  • The complete.rs symbols and keywords change for quoted names has no other callers, so it is safe. The scriptcomplete::keys focus change is also safe: with no focus, List::keys returns None and show closes the list.

AGENTS.md

  • Missing screenshots (confidence: high). This is a visible UI change, and AGENTS.md asks for before and after screenshots in the PR. The PR body has only an empty HTML-comment placeholder for them. Add the Calculate Field screenshots (typing po, and ro with round()'s help) to the PR.
  • Strings use tl!, no new vendor assets, the layering holds, and there are no new unwraps in non-test code. The tests for !-named fields and 50+ fields don't exist yet and would cover the two bugs above.

No prior review threads existed, so no earlier concerns were skipped. Three inline comments were posted.

Change size

Compared with main (merge base 3da536e to PR head 30e0c4b).

Files changed Added Modified Removed Renamed Size before Size after Total change
9 1 8 0 0 810.5 KB 830.3 KB +19.8 KB
Path Before After Change
crates/ui-egui/src/exprcomplete.rs 0 B 14.2 KB +14.2 KB
crates/ui-egui/src/dialogs.rs 146.5 KB 149.0 KB +2.5 KB
crates/ui-egui/src/scriptcolor.rs 36.2 KB 38.1 KB +1.9 KB
crates/ui-egui/src/completion.rs 18.0 KB 19.4 KB +1.4 KB
crates/ui-egui/src/sqlbox.rs 8.9 KB 7.8 KB -1.0 KB
docs/user-guide/expressions.md 13.4 KB 13.9 KB +0.5 KB
crates/data/src/query/complete.rs 14.1 KB 14.5 KB +0.4 KB
crates/ui-egui/src/scriptcomplete.rs 151.5 KB 151.4 KB -31 B
crates/ui-egui/src/lib.rs 422.0 KB 422.0 KB +18 B

@github-actions

Copy link
Copy Markdown
Contributor

📖 Docs preview

Item Value
Preview https://preview.rustgis.app/pr-2040/
Web app https://preview.rustgis.app/pr-2040/app/
Commit 30e0c4b

GitHub Pages takes a minute or two to publish a new commit.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant