Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions lib/core/editor/querya_code_editor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/theme/querya_editor_theme.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

import 'querya_code_language.dart';

/// Shadcn vs Material [TextField] backend for different parent widgets.
enum QueryaCodeEditorVariant {
shadcn,
material,
}

/// Unified code editor (MVP: plain [TextField]; highlighting in #49+).
class QueryaCodeEditor extends StatefulWidget {
const QueryaCodeEditor({
super.key,
this.controller,
this.language = QueryaCodeLanguage.plain,
this.fontSize,
this.readOnly = false,
this.onChanged,
this.placeholder,
this.variant = QueryaCodeEditorVariant.shadcn,
this.expands = true,
this.maxLines,
this.hintText,
this.contentPadding,
this.textAlignVertical,
});

final material.TextEditingController? controller;
final QueryaCodeLanguage language;
final double? fontSize;

/// When null, uses [QueryaEditorTheme.fontSize] from scope.
final bool readOnly;
final ValueChanged<String>? onChanged;
final Widget? placeholder;
final QueryaCodeEditorVariant variant;
final bool expands;
final int? maxLines;
final String? hintText;
final material.EdgeInsetsGeometry? contentPadding;
final material.TextAlignVertical? textAlignVertical;

@override
State<QueryaCodeEditor> createState() => _QueryaCodeEditorState();
}

class _QueryaCodeEditorState extends State<QueryaCodeEditor> {
late material.TextEditingController _controller;
bool _ownsController = false;

@override
void initState() {
super.initState();
_initController(widget.controller);
_controller.addListener(_onTextChanged);
}

void _initController(material.TextEditingController? external) {
if (external == null) {
_controller = material.TextEditingController();
_ownsController = true;
} else {
_controller = external;
_ownsController = false;
}
}

void _onTextChanged() {
widget.onChanged?.call(_controller.text);
}

@override
void didUpdateWidget(QueryaCodeEditor oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller?.removeListener(_onTextChanged);
if (_ownsController) {
_controller.dispose();
}
_initController(widget.controller);
_controller.addListener(_onTextChanged);
}
}

@override
void dispose() {
_controller.removeListener(_onTextChanged);
if (_ownsController) {
_controller.dispose();
}
super.dispose();
}

material.TextStyle _textStyle(QueryaEditorTheme editor) {
final size = widget.fontSize ?? editor.fontSize;
return material.TextStyle(
fontFamily: editor.fontFamily,
fontSize: size,
color: editor.foreground,
height: widget.language == QueryaCodeLanguage.json ? 1.5 : null,
);
}

Widget? _resolvedPlaceholder() {
if (widget.placeholder != null) return widget.placeholder;
return switch (widget.language) {
QueryaCodeLanguage.sql => const Text(
'-- Enter SQL here…\nSELECT 1;',
),
QueryaCodeLanguage.json => const Text('{ }'),
QueryaCodeLanguage.plain => null,
};
}

@override
Widget build(BuildContext context) {
final editor = context.editorTheme;
final style = _textStyle(editor);
final placeholder = _resolvedPlaceholder();

if (widget.variant == QueryaCodeEditorVariant.material) {
return material.TextField(
controller: _controller,
readOnly: widget.readOnly,
maxLines: widget.expands ? null : widget.maxLines,
expands: widget.expands,
style: style,
textAlignVertical: widget.textAlignVertical,
decoration: material.InputDecoration(
border: material.InputBorder.none,
hintText: widget.hintText,
contentPadding: widget.contentPadding ??
const material.EdgeInsets.all(12),
),
onChanged: widget.onChanged,
);
}

return TextField(
controller: _controller,
readOnly: widget.readOnly,
maxLines: widget.expands ? null : widget.maxLines,
expands: widget.expands,
style: style,
placeholder: placeholder,
onChanged: widget.onChanged,
);
}
}
6 changes: 6 additions & 0 deletions lib/core/editor/querya_code_language.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Language mode for [QueryaCodeEditor] (syntax / placeholder hints).
enum QueryaCodeLanguage {
sql,
json,
plain,
}
11 changes: 4 additions & 7 deletions lib/core/editor/sql_syntax_highlighting.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/// Future work: editable SQL with syntax highlighting (see plan: syntax-highlight-epic).
///
/// Candidates: custom [EditableText] + [TextPainter], or a dedicated code-editor package.
/// Plain [TextField] remains the source of truth until an editor is chosen.
abstract class SqlSyntaxHighlighting {
const SqlSyntaxHighlighting._();
}
// Future: syntax highlighting backend for [QueryaCodeEditor] (#49, #50).
//
// MVP uses plain TextField via [QueryaCodeEditor]. Candidates: syntax_highlight,
// re_editor, code_forge (see issue #48).
82 changes: 8 additions & 74 deletions lib/features/main_screen/query_editor_tab.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart' as material
show EdgeInsets, Padding, TextEditingController, TextStyle;
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:flutter/material.dart' as material show EdgeInsets, Padding, TextEditingController;
import 'package:querya_desktop/core/editor/querya_code_editor.dart';
import 'package:querya_desktop/core/editor/querya_code_language.dart';
import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

class QueryEditorTab extends StatelessWidget {
const QueryEditorTab({
Expand All @@ -19,79 +19,13 @@ class QueryEditorTab extends StatelessWidget {

@override
Widget build(BuildContext context) {
return _QueryEditorBody(controller: controller, fontSize: fontSize);
}
}

class _QueryEditorBody extends StatefulWidget {
const _QueryEditorBody({this.controller, required this.fontSize});

final material.TextEditingController? controller;
final double fontSize;

@override
State<_QueryEditorBody> createState() => _QueryEditorBodyState();
}

class _QueryEditorBodyState extends State<_QueryEditorBody> {
late material.TextEditingController _owned;
bool _ownController = false;

@override
void initState() {
super.initState();
if (widget.controller == null) {
_owned = material.TextEditingController();
_ownController = true;
} else {
_owned = widget.controller!;
}
}

@override
void didUpdateWidget(covariant _QueryEditorBody oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.fontSize != widget.fontSize) {
setState(() {});
}
if (oldWidget.controller != widget.controller) {
if (_ownController) {
_owned.dispose();
_ownController = false;
}
if (widget.controller == null) {
_owned = material.TextEditingController();
_ownController = true;
} else {
_owned = widget.controller!;
}
}
}

@override
void dispose() {
if (_ownController) {
_owned.dispose();
}
super.dispose();
}

@override
Widget build(BuildContext context) {
final editor = context.editorTheme;
return material.Padding(
padding: const material.EdgeInsets.all(12),
child: SqlEditorChrome(
child: TextField(
controller: _owned,
maxLines: null,
expands: true,
style: material.TextStyle(
fontFamily: editor.fontFamily,
fontSize: widget.fontSize,
color: editor.foreground,
),
placeholder: const Text('-- Enter SQL here…\nSELECT 1;'),
child: QueryaCodeEditor(
controller: controller,
language: QueryaCodeLanguage.sql,
fontSize: fontSize,
),
),
);
Expand Down
20 changes: 7 additions & 13 deletions lib/features/mongodb/mongo_document_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:convert';

import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/database/mongodb_connection.dart';
import 'package:querya_desktop/core/editor/querya_code_editor.dart';
import 'package:querya_desktop/core/editor/querya_code_language.dart';
import 'package:querya_desktop/core/database/mongodb_service.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' as shadcn;
Expand Down Expand Up @@ -326,20 +328,12 @@ class _MongoDocumentEditorState extends material.State<MongoDocumentEditor> {
material.Expanded(
child: material.Container(
color: cs.card,
child: material.TextField(
child: QueryaCodeEditor(
controller: _controller,
maxLines: null,
expands: true,
style: material.TextStyle(
fontFamily: 'monospace',
fontSize: 13,
color: shadcnCs.foreground,
height: 1.5,
),
decoration: const material.InputDecoration(
border: material.InputBorder.none,
contentPadding: material.EdgeInsets.all(16),
),
language: QueryaCodeLanguage.json,
fontSize: 13,
variant: QueryaCodeEditorVariant.material,
contentPadding: const material.EdgeInsets.all(16),
),
),
),
Expand Down
22 changes: 8 additions & 14 deletions lib/features/mysql/mysql_sql_editor_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/layout/window_layout.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/core/editor/querya_code_editor.dart';
import 'package:querya_desktop/core/editor/querya_code_language.dart';
import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart';
import 'package:querya_desktop/features/mysql/mysql_table_utils.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
Expand Down Expand Up @@ -116,21 +117,14 @@ class _MysqlSqlEditorDialogState extends material.State<_MysqlSqlEditorDialog> {
decoration: SqlEditorChrome.inlineFieldDecorationFromContext(
context,
),
child: material.TextField(
child: QueryaCodeEditor(
controller: _controller,
maxLines: null,
expands: true,
language: QueryaCodeLanguage.sql,
fontSize: 12,
variant: QueryaCodeEditorVariant.material,
textAlignVertical: material.TextAlignVertical.top,
style: material.TextStyle(
fontFamily: context.editorTheme.fontFamily,
fontSize: 12,
color: context.editorTheme.foreground,
),
decoration: const material.InputDecoration(
border: material.InputBorder.none,
contentPadding: material.EdgeInsets.all(12),
hintText: 'SELECT …',
),
hintText: 'SELECT …',
contentPadding: const material.EdgeInsets.all(12),
),
),
),
Expand Down
22 changes: 8 additions & 14 deletions lib/features/postgresql/postgres_sql_editor_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/layout/window_layout.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/core/editor/querya_code_editor.dart';
import 'package:querya_desktop/core/editor/querya_code_language.dart';
import 'package:querya_desktop/features/main_screen/sql_editor_chrome.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';

Expand Down Expand Up @@ -143,21 +144,14 @@ class _PostgresSqlEditorDialogState extends material.State<_PostgresSqlEditorDia
decoration: SqlEditorChrome.inlineFieldDecorationFromContext(
context,
),
child: material.TextField(
child: QueryaCodeEditor(
controller: _controller,
maxLines: null,
expands: true,
language: QueryaCodeLanguage.sql,
fontSize: 12,
variant: QueryaCodeEditorVariant.material,
textAlignVertical: material.TextAlignVertical.top,
style: material.TextStyle(
fontFamily: context.editorTheme.fontFamily,
fontSize: 12,
color: context.editorTheme.foreground,
),
decoration: const material.InputDecoration(
border: material.InputBorder.none,
contentPadding: material.EdgeInsets.all(12),
hintText: 'SELECT …',
),
hintText: 'SELECT …',
contentPadding: const material.EdgeInsets.all(12),
),
),
),
Expand Down
Loading
Loading