(core) Formula autocomplete improvements for references and lookups

Summary:
Makes the following improvements to formula autocomplete:

- When a user types `$RefCol` (or part of it), also show `$RefCol.VisibleCol` (replace actual column names) in the autocomplete even before the `.` is typed, to help users understand the difference between a raw reference/record and its visible column.
- When a user types a table name, show `.lookupOne` and `.lookupRecords` in the autocomplete, again even before the `.` is typed.
- For `.lookupRecords(` and `.lookupOne(`, once the `(` is entered, suggest each column name as a keyword argument.
- Also suggest lookup arguments involving compatible reference columns, especially 'reverse reference' lookups like `refcol=$id` which are very common and difficult for users.
- To support these features, the Ace editor autocomplete needs some patching to fetch fresh autocomplete options after typing `.` or `(`. This also improves unrelated behaviour that wasn't great before when one column name is contained in another. See the first added browser test.

Discussions:

- https://grist.slack.com/archives/CDHABLZJT/p1659707068383179
- https://grist.quip.com/HoSmAlvFax0j#MbTADAH5kgG
- https://grist.quip.com/HoSmAlvFax0j/Formula-Improvements#temp:C:MbT3649fe964a184e8dada9bbebb

Test Plan: Added Python and nbrowser tests.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3580
This commit is contained in:
Alex Hall
2022-08-16 21:18:19 +02:00
parent 44b4ec7edf
commit 42060df29a
5 changed files with 325 additions and 43 deletions

View File

@@ -19,7 +19,7 @@ from sortedcontainers import SortedSet
import acl
import actions
import action_obj
from autocomplete_context import AutocompleteContext
from autocomplete_context import AutocompleteContext, lookup_autocomplete_options
from codebuilder import DOLLAR_REGEX
import depend
import docactions
@@ -1418,12 +1418,33 @@ class Engine(object):
"""
Return a list of suggested completions of the python fragment supplied.
"""
table = self.tables[table_id]
# Table.lookup methods are special to suggest arguments after '('
match = re.match(r"(\w+)\.(lookupRecords|lookupOne)\($", txt)
if match:
# Get the 'Table1' in 'Table1.lookupRecords('
lookup_table_id = match.group(1)
if lookup_table_id in self.tables:
lookup_table = self.tables[lookup_table_id]
# Add a keyword argument with no value for each column name in the lookup table.
result = [
txt + col_id + "="
for col_id in lookup_table.all_columns
if column.is_user_column(col_id) or col_id == 'id'
]
# Add specific complete lookups involving reference columns.
result += [
txt + option
for option in lookup_autocomplete_options(lookup_table, table, reverse_only=False)
]
return sorted(result)
# replace $ with rec. and add a dummy rec object
tweaked_txt = DOLLAR_REGEX.sub(r'rec.', txt)
# convert a bare $ with nothing after it also
if txt == '$':
tweaked_txt = 'rec.'
table = self.tables[table_id]
autocomplete_context = self.autocomplete_context
context = autocomplete_context.get_context()
@@ -1433,10 +1454,10 @@ class Engine(object):
context.pop('value', None)
context.pop('user', None)
column = table.get_column(column_id) if table.has_column(column_id) else None
if column and not column.is_formula():
col = table.get_column(column_id) if table.has_column(column_id) else None
if col and not col.is_formula():
# Add trigger formula completions.
context['value'] = column.sample_value()
context['value'] = col.sample_value()
context['user'] = User(user, self.tables, is_sample=True)
completer = rlcompleter.Completer(context)
@@ -1450,7 +1471,19 @@ class Engine(object):
break
if skipped_completions.search(result):
continue
results.append(autocomplete_context.process_result(result))
result = autocomplete_context.process_result(result)
results.append(result)
funcname = result[0]
# Suggest reverse reference lookups, specifically only for .lookupRecords(),
# not for .lookupOne().
if isinstance(result, tuple) and funcname.endswith(".lookupRecords"):
lookup_table_id = funcname.split(".")[0]
if lookup_table_id in self.tables:
lookup_table = self.tables[lookup_table_id]
results += [
funcname + "(" + option
for option in lookup_autocomplete_options(lookup_table, table, reverse_only=True)
]
# If we changed the prefix (expanding the $ symbol) we now need to change it back.
if tweaked_txt != txt: