Files
clide/lib/builtin/pql/src/pql_controller.dart
T
jpmschweitzerandClaude Opus 4.8 a02fc754a8 merge the pql search panel into the unified Search tab
The pql sidebar panel and the find-in-files tab were duplicate search
surfaces. Consolidate into one Search tab with a mode switch: Find
(content grep), Vault (pql ranked search), Query (PQL DSL), Markdown
(the synced markdown-file listing, keeping focus-highlight + live
refresh). SearchPanelView holds both FindInFilesController and
PqlController; the pql body + result rows move into a reusable
PqlSearchBody. The standalone builtin.pql sidebar tab is removed (one
fewer tab — eases the rail); the pql extension keeps the Backlinks
context panel. No D-79 conflict — grep vs ranked search remain distinct
backends, this is UI consolidation.

Adds the pql builtin's first widget/controller tests (it was untested,
so folding it into the tested Search panel required covering the
Vault/Query/Markdown modes + PqlController).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:45:54 +02:00

151 lines
3.3 KiB
Dart

/// State model for the pql sidebar panel.
///
/// Manages search, DSL query execution, and markdown file listing.
/// All data comes through pql.* IPC verbs.
library;
import 'dart:async';
import 'package:clide/kernel/kernel.dart';
import 'package:flutter/foundation.dart';
enum PqlView { query, markdown }
enum SearchMode { search, dsl }
class PqlController extends ChangeNotifier {
PqlController({required this.ipc});
final DaemonClient ipc;
PqlView _view = PqlView.query;
PqlView get view => _view;
SearchMode _searchMode = SearchMode.search;
SearchMode get searchMode => _searchMode;
String? _error;
String? get error => _error;
bool _loading = false;
bool get loading => _loading;
List<Map<String, Object?>> _results = const [];
List<Map<String, Object?>> get results => _results;
Map<String, Object?> _planStatus = const {};
Map<String, Object?> get planStatus => _planStatus;
void switchView(PqlView v) {
if (_view == v) return;
_view = v;
_results = const [];
_error = null;
notifyListeners();
switch (v) {
case PqlView.markdown:
unawaited(loadMarkdownFiles());
case PqlView.query:
break;
}
}
void toggleSearchMode() => setSearchMode(_searchMode == SearchMode.search ? SearchMode.dsl : SearchMode.search);
void setSearchMode(SearchMode mode) {
if (_searchMode == mode) return;
_searchMode = mode;
_results = const [];
_error = null;
notifyListeners();
}
Future<void> search(String terms) async {
if (terms.trim().isEmpty) {
_results = const [];
_error = null;
notifyListeners();
return;
}
_loading = true;
_error = null;
notifyListeners();
final r = await ipc.request('pql.search', args: {
'terms': terms,
'limit': 50,
});
_loading = false;
if (!r.ok) {
_error = r.error?.message;
_results = const [];
notifyListeners();
return;
}
_results = _castList(r.data['results']);
notifyListeners();
}
Future<void> loadMarkdownFiles({String? glob}) async {
_loading = true;
notifyListeners();
final r = await ipc.request('pql.files', args: {
'glob': glob ?? '**/*.md',
'limit': 200,
});
_loading = false;
if (!r.ok) {
_error = r.error?.message;
notifyListeners();
return;
}
_error = null;
_results = _castList(r.data['files']);
notifyListeners();
}
Future<void> runQuery(String dsl) async {
if (dsl.trim().isEmpty) return;
_loading = true;
_error = null;
notifyListeners();
final r = await ipc.request('pql.query', args: {
'query': dsl,
'limit': 200,
});
_loading = false;
if (!r.ok) {
_error = r.error?.message;
_results = const [];
notifyListeners();
return;
}
_results = _castList(r.data['results']);
notifyListeners();
}
Future<void> loadPlanStatus() async {
final r = await ipc.request('pql.plan.status');
if (r.ok) {
_planStatus = r.data;
notifyListeners();
}
}
void clearError() {
if (_error == null) return;
_error = null;
notifyListeners();
}
static List<Map<String, Object?>> _castList(Object? raw) {
if (raw is! List) return const [];
return [for (final e in raw) (e as Map).cast<String, Object?>()];
}
}