feat: preferences dialog, SQL formatter, and enhanced table context menu
- PreferencesDialog (Tools → Preferences, Ctrl+,): font family/size, word wrap, default page size, query timeout, max history, auto-commit; font changes apply live to all open editors via apply_settings() cascade - CodeEditor and TableViewer now read initial values from settings singleton - SQL formatter (≡ Format button): uses sqlparse to reindent and uppercase keywords; gracefully prompts to install sqlparse if missing - Table viewer context menu: "Filter by this value" (populates WHERE and reloads), "Copy row as SQL INSERT" (ready-to-paste INSERT statements), renamed "Copy row" → "Copy row as TSV" for clarity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+61
-5
@@ -18,6 +18,7 @@ from app.ui.syntax_highlighter import SQLHighlighter
|
||||
from app.ui.results_panel import ResultsPanel
|
||||
from app.ui.sql_completer import SqlCompleter
|
||||
from app.utils.worker import QueryWorker
|
||||
from app.config.settings import get_settings
|
||||
|
||||
|
||||
# ── Line-number gutter ────────────────────────────────────────────────────────
|
||||
@@ -42,11 +43,12 @@ class CodeEditor(QPlainTextEdit):
|
||||
self._line_area = LineNumberArea(self)
|
||||
self._completer: "SqlCompleter | None" = None
|
||||
|
||||
# Font
|
||||
font = QFont("Consolas", 13)
|
||||
font.setFixedPitch(True)
|
||||
self.setFont(font)
|
||||
self.setTabStopDistance(QFontMetrics(font).horizontalAdvance(" ") * 4)
|
||||
self._apply_font()
|
||||
self.setLineWrapMode(
|
||||
QPlainTextEdit.LineWrapMode.WidgetWidth
|
||||
if get_settings().get("word_wrap", False)
|
||||
else QPlainTextEdit.LineWrapMode.NoWrap
|
||||
)
|
||||
|
||||
# Connect signals
|
||||
self.blockCountChanged.connect(self._update_line_area_width)
|
||||
@@ -112,6 +114,22 @@ class CodeEditor(QPlainTextEdit):
|
||||
bottom = top + round(self.blockBoundingRect(block).height())
|
||||
number += 1
|
||||
|
||||
def _apply_font(self) -> None:
|
||||
s = get_settings()
|
||||
font = QFont(s.get("font_family", "Consolas"), s.get("font_size", 13))
|
||||
font.setFixedPitch(True)
|
||||
self.setFont(font)
|
||||
self.setTabStopDistance(QFontMetrics(font).horizontalAdvance(" ") * 4)
|
||||
|
||||
def apply_settings(self) -> None:
|
||||
"""Re-read settings and apply font + word-wrap live."""
|
||||
self._apply_font()
|
||||
self.setLineWrapMode(
|
||||
QPlainTextEdit.LineWrapMode.WidgetWidth
|
||||
if get_settings().get("word_wrap", False)
|
||||
else QPlainTextEdit.LineWrapMode.NoWrap
|
||||
)
|
||||
|
||||
def set_completer(self, completer: "SqlCompleter") -> None:
|
||||
self._completer = completer
|
||||
|
||||
@@ -430,6 +448,10 @@ class EditorTab(QWidget):
|
||||
self._explain_btn = QPushButton("🔎 Explain")
|
||||
self._explain_btn.clicked.connect(self._explain)
|
||||
|
||||
self._format_btn = QPushButton("≡ Format")
|
||||
self._format_btn.setToolTip("Auto-format SQL (requires sqlparse)")
|
||||
self._format_btn.clicked.connect(self._format_sql)
|
||||
|
||||
self._export_btn = QPushButton("📤 Export")
|
||||
self._export_btn.clicked.connect(self._export)
|
||||
|
||||
@@ -439,6 +461,7 @@ class EditorTab(QWidget):
|
||||
toolbar.addWidget(self._run_btn)
|
||||
toolbar.addWidget(self._stop_btn)
|
||||
toolbar.addWidget(self._explain_btn)
|
||||
toolbar.addWidget(self._format_btn)
|
||||
toolbar.addWidget(self._export_btn)
|
||||
toolbar.addStretch()
|
||||
toolbar.addWidget(self._db_label)
|
||||
@@ -523,6 +546,30 @@ class EditorTab(QWidget):
|
||||
def _export(self):
|
||||
self._results.export_dialog()
|
||||
|
||||
def _format_sql(self):
|
||||
try:
|
||||
import sqlparse
|
||||
except ImportError:
|
||||
QMessageBox.warning(
|
||||
self, "Package Missing",
|
||||
"SQL formatting requires sqlparse.\n\nInstall it with:\n pip install sqlparse",
|
||||
)
|
||||
return
|
||||
sql = self._editor.toPlainText().strip()
|
||||
if not sql:
|
||||
return
|
||||
formatted = sqlparse.format(
|
||||
sql,
|
||||
reindent=True,
|
||||
keyword_case="upper",
|
||||
identifier_case="lower",
|
||||
strip_comments=False,
|
||||
use_space_around_operators=True,
|
||||
)
|
||||
cursor = self._editor.textCursor()
|
||||
cursor.select(QTextCursor.SelectionType.Document)
|
||||
cursor.insertText(formatted)
|
||||
|
||||
def set_context(self, driver, database: str) -> None:
|
||||
"""Update driver/database and refresh schema completions."""
|
||||
self._driver = driver
|
||||
@@ -558,6 +605,9 @@ class EditorTab(QWidget):
|
||||
def get_sql(self) -> str:
|
||||
return self._editor.toPlainText()
|
||||
|
||||
def apply_settings(self) -> None:
|
||||
self._editor.apply_settings()
|
||||
|
||||
|
||||
# ── Tabbed SQL editor container ───────────────────────────────────────────────
|
||||
|
||||
@@ -651,6 +701,12 @@ class SQLEditorWidget(QWidget):
|
||||
w = self._tabs.currentWidget()
|
||||
return w if isinstance(w, EditorTab) else None
|
||||
|
||||
def apply_settings(self) -> None:
|
||||
for i in range(self._tabs.count()):
|
||||
tab = self._tabs.widget(i)
|
||||
if isinstance(tab, EditorTab):
|
||||
tab.apply_settings()
|
||||
|
||||
def open_sql_for(self, driver, database: str, sql: str = ""):
|
||||
tab = self.new_tab(driver, database, sql,
|
||||
title=f"SQL — {database}")
|
||||
|
||||
Reference in New Issue
Block a user