Improved completion to support the correct table name for autocompletion and formatting
This commit is contained in:
@@ -7,7 +7,7 @@ Implements the BaseCompletionEngine for DataGrid formatting rules.
|
|||||||
from myfasthtml.core.dsl.base_completion import BaseCompletionEngine
|
from myfasthtml.core.dsl.base_completion import BaseCompletionEngine
|
||||||
from myfasthtml.core.dsl.types import Position, Suggestion, CompletionResult
|
from myfasthtml.core.dsl.types import Position, Suggestion, CompletionResult
|
||||||
from myfasthtml.core.utils import make_safe_id
|
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 .contexts import Context, DetectedScope, detect_scope, detect_context
|
||||||
from .provider import DatagridMetadataProvider
|
from .provider import DatagridMetadataProvider
|
||||||
|
|
||||||
@@ -80,13 +80,283 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
|||||||
Returns:
|
Returns:
|
||||||
List of suggestions
|
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(
|
def get_completions(
|
||||||
text: str,
|
text: str,
|
||||||
cursor: Position,
|
cursor: Position,
|
||||||
provider: DatagridMetadataProvider,
|
provider: DatagridMetadataProvider,
|
||||||
|
table_name: str,
|
||||||
) -> CompletionResult:
|
) -> CompletionResult:
|
||||||
"""
|
"""
|
||||||
Get autocompletion suggestions for the formatting DSL.
|
Get autocompletion suggestions for the formatting DSL.
|
||||||
@@ -97,6 +367,7 @@ def get_completions(
|
|||||||
text: The full DSL document text
|
text: The full DSL document text
|
||||||
cursor: Cursor position (line and ch are 0-based)
|
cursor: Cursor position (line and ch are 0-based)
|
||||||
provider: DataGrid metadata provider
|
provider: DataGrid metadata provider
|
||||||
|
table_name: Table name for completions
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CompletionResult with suggestions and replacement range
|
CompletionResult with suggestions and replacement range
|
||||||
@@ -105,9 +376,10 @@ def get_completions(
|
|||||||
result = get_completions(
|
result = get_completions(
|
||||||
text='column amount:\\n style("err',
|
text='column amount:\\n style("err',
|
||||||
cursor=Position(line=1, ch=15),
|
cursor=Position(line=1, ch=15),
|
||||||
provider=my_provider
|
provider=my_provider,
|
||||||
|
table_name="app.orders"
|
||||||
)
|
)
|
||||||
# result.suggestions contains ["error"] filtered by prefix "err"
|
# result.suggestions contains ["error"] filtered by prefix "err"
|
||||||
"""
|
"""
|
||||||
engine = FormattingCompletionEngine(provider)
|
engine = FormattingCompletionEngine(provider, table_name)
|
||||||
return engine.get_completions(text, cursor)
|
return engine.get_completions(text, cursor)
|
||||||
|
|||||||
@@ -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 []
|
|
||||||
@@ -14,7 +14,6 @@ from myfasthtml.core.formatting.dsl.completion.contexts import (
|
|||||||
detect_scope,
|
detect_scope,
|
||||||
detect_context,
|
detect_context,
|
||||||
)
|
)
|
||||||
from myfasthtml.core.formatting.dsl.completion.suggestions import get_suggestions
|
|
||||||
from myfasthtml.core.formatting.dsl.completion.FormattingCompletionEngine import (
|
from myfasthtml.core.formatting.dsl.completion.FormattingCompletionEngine import (
|
||||||
FormattingCompletionEngine,
|
FormattingCompletionEngine,
|
||||||
get_completions,
|
get_completions,
|
||||||
@@ -545,9 +544,10 @@ def test_context_none_in_comment():
|
|||||||
|
|
||||||
def test_suggestions_scope_keyword(provider):
|
def test_suggestions_scope_keyword(provider):
|
||||||
"""Test suggestions for SCOPE_KEYWORD context."""
|
"""Test suggestions for SCOPE_KEYWORD context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope()
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "column" in labels
|
assert "column" in labels
|
||||||
@@ -557,9 +557,10 @@ def test_suggestions_scope_keyword(provider):
|
|||||||
|
|
||||||
def test_suggestions_style_preset(provider):
|
def test_suggestions_style_preset(provider):
|
||||||
"""Test suggestions for STYLE_PRESET context."""
|
"""Test suggestions for STYLE_PRESET context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "primary" in labels
|
assert "primary" in labels
|
||||||
@@ -570,9 +571,10 @@ def test_suggestions_style_preset(provider):
|
|||||||
|
|
||||||
def test_suggestions_format_type(provider):
|
def test_suggestions_format_type(provider):
|
||||||
"""Test suggestions for FORMAT_TYPE context."""
|
"""Test suggestions for FORMAT_TYPE context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "number" in labels
|
assert "number" in labels
|
||||||
@@ -584,9 +586,10 @@ def test_suggestions_format_type(provider):
|
|||||||
|
|
||||||
def test_suggestions_operators(provider):
|
def test_suggestions_operators(provider):
|
||||||
"""Test suggestions for OPERATOR context."""
|
"""Test suggestions for OPERATOR context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "==" in labels
|
assert "==" in labels
|
||||||
@@ -598,9 +601,10 @@ def test_suggestions_operators(provider):
|
|||||||
|
|
||||||
def test_suggestions_boolean_value(provider):
|
def test_suggestions_boolean_value(provider):
|
||||||
"""Test suggestions for BOOLEAN_VALUE context."""
|
"""Test suggestions for BOOLEAN_VALUE context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "True" in labels
|
assert "True" in labels
|
||||||
@@ -609,9 +613,10 @@ def test_suggestions_boolean_value(provider):
|
|||||||
|
|
||||||
def test_suggestions_color_value(provider):
|
def test_suggestions_color_value(provider):
|
||||||
"""Test suggestions for COLOR_VALUE context."""
|
"""Test suggestions for COLOR_VALUE context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "red" in labels
|
assert "red" in labels
|
||||||
@@ -621,9 +626,10 @@ def test_suggestions_color_value(provider):
|
|||||||
|
|
||||||
def test_suggestions_column_values(provider):
|
def test_suggestions_column_values(provider):
|
||||||
"""Test suggestions for OPERATOR_VALUE context with column scope."""
|
"""Test suggestions for OPERATOR_VALUE context with column scope."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="status")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
# Base suggestions
|
# Base suggestions
|
||||||
@@ -639,9 +645,10 @@ def test_suggestions_column_values(provider):
|
|||||||
|
|
||||||
def test_suggestions_rule_start(provider):
|
def test_suggestions_rule_start(provider):
|
||||||
"""Test suggestions for RULE_START context."""
|
"""Test suggestions for RULE_START context."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
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]
|
labels = [s.label for s in suggestions]
|
||||||
|
|
||||||
assert "style(" in labels
|
assert "style(" in labels
|
||||||
@@ -651,9 +658,10 @@ def test_suggestions_rule_start(provider):
|
|||||||
|
|
||||||
def test_suggestions_none_context(provider):
|
def test_suggestions_none_context(provider):
|
||||||
"""Test that NONE context returns empty suggestions."""
|
"""Test that NONE context returns empty suggestions."""
|
||||||
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
scope = DetectedScope()
|
scope = DetectedScope()
|
||||||
|
|
||||||
suggestions = get_suggestions(Context.NONE, scope, provider)
|
suggestions = engine.get_suggestions(Context.NONE, scope, "")
|
||||||
|
|
||||||
assert suggestions == []
|
assert suggestions == []
|
||||||
|
|
||||||
@@ -668,7 +676,7 @@ def test_i_can_get_completions_for_style_preset(provider):
|
|||||||
text = 'column amount:\n style("'
|
text = 'column amount:\n style("'
|
||||||
cursor = Position(line=1, ch=11)
|
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
|
assert not result.is_empty
|
||||||
labels = [s.label for s in result.suggestions]
|
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'
|
text = 'column amount:\n style("err'
|
||||||
cursor = Position(line=1, ch=14)
|
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]
|
labels = [s.label for s in result.suggestions]
|
||||||
assert "error" in labels
|
assert "error" in labels
|
||||||
@@ -693,7 +701,7 @@ def test_i_can_get_completions_returns_correct_positions(provider):
|
|||||||
text = 'column amount:\n style("err'
|
text = 'column amount:\n style("err'
|
||||||
cursor = Position(line=1, ch=14) # After "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"
|
# from_pos should be at start of "err"
|
||||||
assert result.from_pos.line == 1
|
assert result.from_pos.line == 1
|
||||||
@@ -709,7 +717,7 @@ def test_i_can_get_completions_at_scope_start(provider):
|
|||||||
text = ""
|
text = ""
|
||||||
cursor = Position(line=0, ch=0)
|
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]
|
labels = [s.label for s in result.suggestions]
|
||||||
assert "column" in labels
|
assert "column" in labels
|
||||||
@@ -722,7 +730,7 @@ def test_i_can_get_completions_for_column_names(provider):
|
|||||||
text = "column "
|
text = "column "
|
||||||
cursor = Position(line=0, ch=7)
|
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]
|
labels = [s.label for s in result.suggestions]
|
||||||
assert "id" in labels
|
assert "id" in labels
|
||||||
@@ -735,21 +743,22 @@ def test_i_can_get_completions_in_comment_returns_empty(provider):
|
|||||||
text = "column amount:\n # comment"
|
text = "column amount:\n # comment"
|
||||||
cursor = Position(line=1, ch=15)
|
cursor = Position(line=1, ch=15)
|
||||||
|
|
||||||
result = get_completions(text, cursor, provider)
|
result = get_completions(text, cursor, provider, "app.orders")
|
||||||
|
|
||||||
assert result.is_empty
|
assert result.is_empty
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_create_formatting_completion_engine(provider):
|
def test_i_can_create_formatting_completion_engine(provider):
|
||||||
"""Test that FormattingCompletionEngine can be instantiated."""
|
"""Test that FormattingCompletionEngine can be instantiated."""
|
||||||
engine = FormattingCompletionEngine(provider)
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
|
|
||||||
assert engine.provider == provider
|
assert engine.provider == provider
|
||||||
|
assert engine.table_name == "app.orders"
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_use_engine_detect_scope(provider):
|
def test_i_can_use_engine_detect_scope(provider):
|
||||||
"""Test engine's detect_scope method."""
|
"""Test engine's detect_scope method."""
|
||||||
engine = FormattingCompletionEngine(provider)
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
text = "column amount:\n style()"
|
text = "column amount:\n style()"
|
||||||
|
|
||||||
scope = engine.detect_scope(text, current_line=1)
|
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):
|
def test_i_can_use_engine_detect_context(provider):
|
||||||
"""Test engine's detect_context method."""
|
"""Test engine's detect_context method."""
|
||||||
engine = FormattingCompletionEngine(provider)
|
engine = FormattingCompletionEngine(provider, "app.orders")
|
||||||
text = "column amount:\n style("
|
text = "column amount:\n style("
|
||||||
cursor = Position(line=1, ch=10)
|
cursor = Position(line=1, ch=10)
|
||||||
scope = DetectedScope(scope_type="column", column_name="amount")
|
scope = DetectedScope(scope_type="column", column_name="amount")
|
||||||
|
|||||||
Reference in New Issue
Block a user