diff --git a/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py b/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py index d73bf80..de44ce3 100644 --- a/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py +++ b/src/myfasthtml/core/formatting/dsl/completion/FormattingCompletionEngine.py @@ -7,7 +7,7 @@ Implements the BaseCompletionEngine for DataGrid formatting rules. from myfasthtml.core.dsl.base_completion import BaseCompletionEngine from myfasthtml.core.dsl.types import Position, Suggestion, CompletionResult from myfasthtml.core.utils import make_safe_id -from . import suggestions as suggestions_module +from . import presets from .contexts import Context, DetectedScope, detect_scope, detect_context from .provider import DatagridMetadataProvider @@ -80,13 +80,283 @@ class FormattingCompletionEngine(BaseCompletionEngine): Returns: List of suggestions """ - return suggestions_module.get_suggestions(context, scope, self.provider) + match context: + # ================================================================= + # Scope-level contexts + # ================================================================= + + case Context.NONE: + return [] + + case Context.SCOPE_KEYWORD: + return presets.SCOPE_KEYWORDS + + case Context.COLUMN_NAME: + return self._get_column_suggestions() + + case Context.ROW_INDEX: + return self._get_row_index_suggestions() + + case Context.CELL_START: + return [Suggestion("(", "Start cell coordinates", "syntax")] + + case Context.CELL_COLUMN: + return self._get_column_suggestions() + + case Context.CELL_ROW: + return self._get_row_index_suggestions() + + # ================================================================= + # Rule-level contexts + # ================================================================= + + case Context.RULE_START: + return presets.RULE_START + + # ================================================================= + # Style contexts + # ================================================================= + + case Context.STYLE_ARGS: + # Presets (with quotes) + params + style_presets = self._get_style_preset_suggestions_quoted() + return style_presets + presets.STYLE_PARAMS + + case Context.STYLE_PRESET: + return self._get_style_preset_suggestions() + + case Context.STYLE_PARAM: + return presets.STYLE_PARAMS + + # ================================================================= + # Format contexts + # ================================================================= + + case Context.FORMAT_PRESET: + return self._get_format_preset_suggestions() + + case Context.FORMAT_TYPE: + return presets.FORMAT_TYPES + + case Context.FORMAT_PARAM_DATE: + return presets.FORMAT_PARAMS_DATE + + case Context.FORMAT_PARAM_TEXT: + return presets.FORMAT_PARAMS_TEXT + + # ================================================================= + # After style/format + # ================================================================= + + case Context.AFTER_STYLE_OR_FORMAT: + return presets.AFTER_STYLE_OR_FORMAT + + # ================================================================= + # Condition contexts + # ================================================================= + + case Context.CONDITION_START: + return presets.CONDITION_START + + case Context.CONDITION_AFTER_NOT: + return presets.CONDITION_AFTER_NOT + + case Context.COLUMN_REF: + return self._get_column_suggestions() + + case Context.COLUMN_REF_QUOTED: + return self._get_column_suggestions_with_closing_quote() + + # ================================================================= + # Operator contexts + # ================================================================= + + case Context.OPERATOR: + return presets.COMPARISON_OPERATORS + + case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE: + # col., True, False + column values + base = presets.OPERATOR_VALUE_BASE.copy() + base.extend(self._get_column_value_suggestions(scope)) + return base + + case Context.BETWEEN_AND: + return presets.BETWEEN_AND + + case Context.IN_LIST_START: + return presets.IN_LIST_START + + case Context.IN_LIST_VALUE: + return self._get_column_value_suggestions(scope) + + # ================================================================= + # Value contexts + # ================================================================= + + case Context.BOOLEAN_VALUE: + return presets.BOOLEAN_VALUES + + case Context.COLOR_VALUE: + return presets.ALL_COLORS + + case Context.DATE_FORMAT_VALUE: + return presets.DATE_PATTERNS + + case Context.TRANSFORM_VALUE: + return presets.TEXT_TRANSFORMS + + case _: + return [] + + def _get_column_suggestions(self) -> list[Suggestion]: + """Get column name suggestions from provider.""" + try: + # Try to get columns from the first available table + tables = self.provider.list_tables() + if tables: + columns = self.provider.list_columns(self.table_name) + return [Suggestion(col, "Column", "column") for col in columns] + except Exception: + pass + return [] + + def _get_column_suggestions_with_closing_quote(self) -> list[Suggestion]: + """Get column name suggestions with closing quote.""" + try: + tables = self.provider.list_tables() + if tables: + columns = self.provider.list_columns(self.table_name) + return [Suggestion(f'{col}"', "Column", "column") for col in columns] + except Exception: + pass + return [] + + def _get_style_preset_suggestions(self) -> list[Suggestion]: + """Get style preset suggestions (without quotes).""" + suggestions = [] + + # Add provider presets if available + try: + custom_presets = self.provider.list_style_presets() + for preset in custom_presets: + # Check if it's already in default presets + if not any(s.label == preset for s in presets.STYLE_PRESETS): + suggestions.append(Suggestion(preset, "Custom preset", "preset")) + except Exception: + pass + + # Add default presets (just the name, no quotes - we're inside quotes) + for preset in presets.STYLE_PRESETS: + suggestions.append(Suggestion(preset.label, preset.detail, preset.kind)) + + return suggestions + + def _get_style_preset_suggestions_quoted(self) -> list[Suggestion]: + """Get style preset suggestions with quotes.""" + suggestions = [] + + # Add provider presets if available + try: + custom_presets = self.provider.list_style_presets() + for preset in custom_presets: + if not any(s.label == preset for s in presets.STYLE_PRESETS): + suggestions.append(Suggestion(f'"{preset}"', "Custom preset", "preset")) + except Exception: + pass + + # Add default presets with quotes + for preset in presets.STYLE_PRESETS: + suggestions.append(Suggestion(f'"{preset.label}"', preset.detail, preset.kind)) + + return suggestions + + def _get_format_preset_suggestions(self) -> list[Suggestion]: + """Get format preset suggestions (without quotes).""" + suggestions = [] + + # Add provider presets if available + try: + custom_presets = self.provider.list_format_presets() + for preset in custom_presets: + if not any(s.label == preset for s in presets.FORMAT_PRESETS): + suggestions.append(Suggestion(preset, "Custom preset", "preset")) + except Exception: + pass + + # Add default presets + for preset in presets.FORMAT_PRESETS: + suggestions.append(Suggestion(preset.label, preset.detail, preset.kind)) + + return suggestions + + def _get_row_index_suggestions(self) -> list[Suggestion]: + """Get row index suggestions (first 10 + last).""" + suggestions = [] + + try: + tables = self.provider.list_tables() + if tables: + row_count = self.provider.get_row_count(self.table_name) + if row_count > 0: + # First 10 rows + for i in range(min(10, row_count)): + suggestions.append(Suggestion(str(i), f"Row {i}", "index")) + + # Last row if not already included + last_index = row_count - 1 + if last_index >= 10: + suggestions.append( + Suggestion(str(last_index), f"Last row ({row_count} total)", "index") + ) + except Exception: + pass + + # Fallback if no provider data + if not suggestions: + for i in range(10): + suggestions.append(Suggestion(str(i), f"Row {i}", "index")) + + return suggestions + + def _get_column_value_suggestions(self, scope: DetectedScope) -> list[Suggestion]: + """Get column value suggestions based on the current scope.""" + if not scope.column_name: + return [] + + try: + # Use table_name from scope, or empty string as fallback + table_name = scope.table_name or "" + values = self.provider.list_column_values(table_name, scope.column_name) + suggestions = [] + for value in values: + if value is None: + continue + # Format value appropriately + if isinstance(value, str): + label = f'"{value}"' + detail = "Text value" + elif isinstance(value, bool): + label = str(value) + detail = "Boolean value" + elif isinstance(value, (int, float)): + label = str(value) + detail = "Numeric value" + else: + label = f'"{value}"' + detail = "Value" + + suggestions.append(Suggestion(label, detail, "value")) + + return suggestions + except Exception: + return [] def get_completions( text: str, cursor: Position, provider: DatagridMetadataProvider, + table_name: str, ) -> CompletionResult: """ Get autocompletion suggestions for the formatting DSL. @@ -97,6 +367,7 @@ def get_completions( text: The full DSL document text cursor: Cursor position (line and ch are 0-based) provider: DataGrid metadata provider + table_name: Table name for completions Returns: CompletionResult with suggestions and replacement range @@ -105,9 +376,10 @@ def get_completions( result = get_completions( text='column amount:\\n style("err', cursor=Position(line=1, ch=15), - provider=my_provider + provider=my_provider, + table_name="app.orders" ) # result.suggestions contains ["error"] filtered by prefix "err" """ - engine = FormattingCompletionEngine(provider) + engine = FormattingCompletionEngine(provider, table_name) return engine.get_completions(text, cursor) diff --git a/src/myfasthtml/core/formatting/dsl/completion/suggestions.py b/src/myfasthtml/core/formatting/dsl/completion/suggestions.py deleted file mode 100644 index eae847d..0000000 --- a/src/myfasthtml/core/formatting/dsl/completion/suggestions.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -Suggestions generation for the formatting DSL. - -Provides functions to generate appropriate suggestions -based on the detected context and scope. -""" - -from myfasthtml.core.dsl.types import Suggestion -from . import presets -from .contexts import Context, DetectedScope -from .provider import DatagridMetadataProvider - - -def get_suggestions( - context: Context, - scope: DetectedScope, - provider: DatagridMetadataProvider, -) -> list[Suggestion]: - """ - Generate suggestions for the given context. - - Args: - context: The detected completion context - scope: The detected scope - provider: Metadata provider for dynamic data - - Returns: - List of suggestions - """ - match context: - # ================================================================= - # Scope-level contexts - # ================================================================= - - case Context.NONE: - return [] - - case Context.SCOPE_KEYWORD: - return presets.SCOPE_KEYWORDS - - case Context.COLUMN_NAME: - return _get_column_suggestions(provider) - - case Context.ROW_INDEX: - return _get_row_index_suggestions(provider) - - case Context.CELL_START: - return [Suggestion("(", "Start cell coordinates", "syntax")] - - case Context.CELL_COLUMN: - return _get_column_suggestions(provider) - - case Context.CELL_ROW: - return _get_row_index_suggestions(provider) - - # ================================================================= - # Rule-level contexts - # ================================================================= - - case Context.RULE_START: - return presets.RULE_START - - # ================================================================= - # Style contexts - # ================================================================= - - case Context.STYLE_ARGS: - # Presets (with quotes) + params - style_presets = _get_style_preset_suggestions_quoted(provider) - return style_presets + presets.STYLE_PARAMS - - case Context.STYLE_PRESET: - return _get_style_preset_suggestions(provider) - - case Context.STYLE_PARAM: - return presets.STYLE_PARAMS - - # ================================================================= - # Format contexts - # ================================================================= - - case Context.FORMAT_PRESET: - return _get_format_preset_suggestions(provider) - - case Context.FORMAT_TYPE: - return presets.FORMAT_TYPES - - case Context.FORMAT_PARAM_DATE: - return presets.FORMAT_PARAMS_DATE - - case Context.FORMAT_PARAM_TEXT: - return presets.FORMAT_PARAMS_TEXT - - # ================================================================= - # After style/format - # ================================================================= - - case Context.AFTER_STYLE_OR_FORMAT: - return presets.AFTER_STYLE_OR_FORMAT - - # ================================================================= - # Condition contexts - # ================================================================= - - case Context.CONDITION_START: - return presets.CONDITION_START - - case Context.CONDITION_AFTER_NOT: - return presets.CONDITION_AFTER_NOT - - case Context.COLUMN_REF: - return _get_column_suggestions(provider) - - case Context.COLUMN_REF_QUOTED: - return _get_column_suggestions_with_closing_quote(provider) - - # ================================================================= - # Operator contexts - # ================================================================= - - case Context.OPERATOR: - return presets.COMPARISON_OPERATORS - - case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE: - # col., True, False + column values - base = presets.OPERATOR_VALUE_BASE.copy() - base.extend(_get_column_value_suggestions(scope, provider)) - return base - - case Context.BETWEEN_AND: - return presets.BETWEEN_AND - - case Context.IN_LIST_START: - return presets.IN_LIST_START - - case Context.IN_LIST_VALUE: - return _get_column_value_suggestions(scope, provider) - - # ================================================================= - # Value contexts - # ================================================================= - - case Context.BOOLEAN_VALUE: - return presets.BOOLEAN_VALUES - - case Context.COLOR_VALUE: - return presets.ALL_COLORS - - case Context.DATE_FORMAT_VALUE: - return presets.DATE_PATTERNS - - case Context.TRANSFORM_VALUE: - return presets.TEXT_TRANSFORMS - - case _: - return [] - - -def _get_column_suggestions(provider: DatagridMetadataProvider) -> list[Suggestion]: - """Get column name suggestions from provider.""" - try: - # Try to get columns from the first available table - tables = provider.list_tables() - if tables: - columns = provider.list_columns(tables[0]) - return [Suggestion(col, "Column", "column") for col in columns] - except Exception: - pass - return [] - - -def _get_column_suggestions_with_closing_quote( - provider: DatagridMetadataProvider, -) -> list[Suggestion]: - """Get column name suggestions with closing quote.""" - try: - tables = provider.list_tables() - if tables: - columns = provider.list_columns(tables[0]) - return [Suggestion(f'{col}"', "Column", "column") for col in columns] - except Exception: - pass - return [] - - -def _get_style_preset_suggestions(provider: DatagridMetadataProvider) -> list[Suggestion]: - """Get style preset suggestions (without quotes).""" - suggestions = [] - - # Add provider presets if available - try: - custom_presets = provider.list_style_presets() - for preset in custom_presets: - # Check if it's already in default presets - if not any(s.label == preset for s in presets.STYLE_PRESETS): - suggestions.append(Suggestion(preset, "Custom preset", "preset")) - except Exception: - pass - - # Add default presets (just the name, no quotes - we're inside quotes) - for preset in presets.STYLE_PRESETS: - suggestions.append(Suggestion(preset.label, preset.detail, preset.kind)) - - return suggestions - - -def _get_style_preset_suggestions_quoted( - provider: DatagridMetadataProvider, -) -> list[Suggestion]: - """Get style preset suggestions with quotes.""" - suggestions = [] - - # Add provider presets if available - try: - custom_presets = provider.list_style_presets() - for preset in custom_presets: - if not any(s.label == preset for s in presets.STYLE_PRESETS): - suggestions.append(Suggestion(f'"{preset}"', "Custom preset", "preset")) - except Exception: - pass - - # Add default presets with quotes - for preset in presets.STYLE_PRESETS: - suggestions.append(Suggestion(f'"{preset.label}"', preset.detail, preset.kind)) - - return suggestions - - -def _get_format_preset_suggestions(provider: DatagridMetadataProvider) -> list[Suggestion]: - """Get format preset suggestions (without quotes).""" - suggestions = [] - - # Add provider presets if available - try: - custom_presets = provider.list_format_presets() - for preset in custom_presets: - if not any(s.label == preset for s in presets.FORMAT_PRESETS): - suggestions.append(Suggestion(preset, "Custom preset", "preset")) - except Exception: - pass - - # Add default presets - for preset in presets.FORMAT_PRESETS: - suggestions.append(Suggestion(preset.label, preset.detail, preset.kind)) - - return suggestions - - -def _get_row_index_suggestions(provider: DatagridMetadataProvider) -> list[Suggestion]: - """Get row index suggestions (first 10 + last).""" - suggestions = [] - - try: - tables = provider.list_tables() - if tables: - row_count = provider.get_row_count(tables[0]) - if row_count > 0: - # First 10 rows - for i in range(min(10, row_count)): - suggestions.append(Suggestion(str(i), f"Row {i}", "index")) - - # Last row if not already included - last_index = row_count - 1 - if last_index >= 10: - suggestions.append( - Suggestion(str(last_index), f"Last row ({row_count} total)", "index") - ) - except Exception: - pass - - # Fallback if no provider data - if not suggestions: - for i in range(10): - suggestions.append(Suggestion(str(i), f"Row {i}", "index")) - - return suggestions - - -def _get_column_value_suggestions( - scope: DetectedScope, - provider: DatagridMetadataProvider, -) -> list[Suggestion]: - """Get column value suggestions based on the current scope.""" - if not scope.column_name: - return [] - - try: - # Use table_name from scope, or empty string as fallback - table_name = scope.table_name or "" - values = provider.list_column_values(table_name, scope.column_name) - suggestions = [] - for value in values: - if value is None: - continue - # Format value appropriately - if isinstance(value, str): - label = f'"{value}"' - detail = "Text value" - elif isinstance(value, bool): - label = str(value) - detail = "Boolean value" - elif isinstance(value, (int, float)): - label = str(value) - detail = "Numeric value" - else: - label = f'"{value}"' - detail = "Value" - - suggestions.append(Suggestion(label, detail, "value")) - - return suggestions - except Exception: - return [] diff --git a/tests/core/formatting/dsl/test_completion.py b/tests/core/formatting/dsl/test_completion.py index 974d40a..928507c 100644 --- a/tests/core/formatting/dsl/test_completion.py +++ b/tests/core/formatting/dsl/test_completion.py @@ -14,7 +14,6 @@ from myfasthtml.core.formatting.dsl.completion.contexts import ( detect_scope, detect_context, ) -from myfasthtml.core.formatting.dsl.completion.suggestions import get_suggestions from myfasthtml.core.formatting.dsl.completion.FormattingCompletionEngine import ( FormattingCompletionEngine, get_completions, @@ -545,9 +544,10 @@ def test_context_none_in_comment(): def test_suggestions_scope_keyword(provider): """Test suggestions for SCOPE_KEYWORD context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope() - suggestions = get_suggestions(Context.SCOPE_KEYWORD, scope, provider) + suggestions = engine.get_suggestions(Context.SCOPE_KEYWORD, scope, "") labels = [s.label for s in suggestions] assert "column" in labels @@ -557,9 +557,10 @@ def test_suggestions_scope_keyword(provider): def test_suggestions_style_preset(provider): """Test suggestions for STYLE_PRESET context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.STYLE_PRESET, scope, provider) + suggestions = engine.get_suggestions(Context.STYLE_PRESET, scope, "") labels = [s.label for s in suggestions] assert "primary" in labels @@ -570,9 +571,10 @@ def test_suggestions_style_preset(provider): def test_suggestions_format_type(provider): """Test suggestions for FORMAT_TYPE context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.FORMAT_TYPE, scope, provider) + suggestions = engine.get_suggestions(Context.FORMAT_TYPE, scope, "") labels = [s.label for s in suggestions] assert "number" in labels @@ -584,9 +586,10 @@ def test_suggestions_format_type(provider): def test_suggestions_operators(provider): """Test suggestions for OPERATOR context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.OPERATOR, scope, provider) + suggestions = engine.get_suggestions(Context.OPERATOR, scope, "") labels = [s.label for s in suggestions] assert "==" in labels @@ -598,9 +601,10 @@ def test_suggestions_operators(provider): def test_suggestions_boolean_value(provider): """Test suggestions for BOOLEAN_VALUE context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.BOOLEAN_VALUE, scope, provider) + suggestions = engine.get_suggestions(Context.BOOLEAN_VALUE, scope, "") labels = [s.label for s in suggestions] assert "True" in labels @@ -609,9 +613,10 @@ def test_suggestions_boolean_value(provider): def test_suggestions_color_value(provider): """Test suggestions for COLOR_VALUE context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.COLOR_VALUE, scope, provider) + suggestions = engine.get_suggestions(Context.COLOR_VALUE, scope, "") labels = [s.label for s in suggestions] assert "red" in labels @@ -621,9 +626,10 @@ def test_suggestions_color_value(provider): def test_suggestions_column_values(provider): """Test suggestions for OPERATOR_VALUE context with column scope.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="status") - suggestions = get_suggestions(Context.OPERATOR_VALUE, scope, provider) + suggestions = engine.get_suggestions(Context.OPERATOR_VALUE, scope, "") labels = [s.label for s in suggestions] # Base suggestions @@ -639,9 +645,10 @@ def test_suggestions_column_values(provider): def test_suggestions_rule_start(provider): """Test suggestions for RULE_START context.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope(scope_type="column", column_name="amount") - suggestions = get_suggestions(Context.RULE_START, scope, provider) + suggestions = engine.get_suggestions(Context.RULE_START, scope, "") labels = [s.label for s in suggestions] assert "style(" in labels @@ -651,9 +658,10 @@ def test_suggestions_rule_start(provider): def test_suggestions_none_context(provider): """Test that NONE context returns empty suggestions.""" + engine = FormattingCompletionEngine(provider, "app.orders") scope = DetectedScope() - suggestions = get_suggestions(Context.NONE, scope, provider) + suggestions = engine.get_suggestions(Context.NONE, scope, "") assert suggestions == [] @@ -668,7 +676,7 @@ def test_i_can_get_completions_for_style_preset(provider): text = 'column amount:\n style("' cursor = Position(line=1, ch=11) - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") assert not result.is_empty labels = [s.label for s in result.suggestions] @@ -681,7 +689,7 @@ def test_i_can_get_completions_filters_by_prefix(provider): text = 'column amount:\n style("err' cursor = Position(line=1, ch=14) - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") labels = [s.label for s in result.suggestions] assert "error" in labels @@ -693,7 +701,7 @@ def test_i_can_get_completions_returns_correct_positions(provider): text = 'column amount:\n style("err' cursor = Position(line=1, ch=14) # After "err" - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") # from_pos should be at start of "err" assert result.from_pos.line == 1 @@ -709,7 +717,7 @@ def test_i_can_get_completions_at_scope_start(provider): text = "" cursor = Position(line=0, ch=0) - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") labels = [s.label for s in result.suggestions] assert "column" in labels @@ -722,7 +730,7 @@ def test_i_can_get_completions_for_column_names(provider): text = "column " cursor = Position(line=0, ch=7) - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") labels = [s.label for s in result.suggestions] assert "id" in labels @@ -735,21 +743,22 @@ def test_i_can_get_completions_in_comment_returns_empty(provider): text = "column amount:\n # comment" cursor = Position(line=1, ch=15) - result = get_completions(text, cursor, provider) + result = get_completions(text, cursor, provider, "app.orders") assert result.is_empty def test_i_can_create_formatting_completion_engine(provider): """Test that FormattingCompletionEngine can be instantiated.""" - engine = FormattingCompletionEngine(provider) + engine = FormattingCompletionEngine(provider, "app.orders") assert engine.provider == provider + assert engine.table_name == "app.orders" def test_i_can_use_engine_detect_scope(provider): """Test engine's detect_scope method.""" - engine = FormattingCompletionEngine(provider) + engine = FormattingCompletionEngine(provider, "app.orders") text = "column amount:\n style()" scope = engine.detect_scope(text, current_line=1) @@ -760,7 +769,7 @@ def test_i_can_use_engine_detect_scope(provider): def test_i_can_use_engine_detect_context(provider): """Test engine's detect_context method.""" - engine = FormattingCompletionEngine(provider) + engine = FormattingCompletionEngine(provider, "app.orders") text = "column amount:\n style(" cursor = Position(line=1, ch=10) scope = DetectedScope(scope_type="column", column_name="amount")