diff --git a/.gitignore b/.gitignore index 05f48cd7..912c14d5 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,6 @@ legacy/**/.coverage legacy/**/.coverage.* .clide/settings.yaml .claude/skills/pql/ +.pql/* +!.pql/pql-plan.json +!.pql/hooks/ diff --git a/lib/builtin/diff/src/diff_controller.dart b/lib/builtin/diff/src/diff_controller.dart index 816a6745..5b5a632b 100644 --- a/lib/builtin/diff/src/diff_controller.dart +++ b/lib/builtin/diff/src/diff_controller.dart @@ -15,7 +15,7 @@ class DiffController extends ChangeNotifier { } final DaemonClient ipc; - final EventBus events; + final DaemonBus events; StreamSubscription? _eventSub; diff --git a/lib/builtin/editor/src/editor_controller.dart b/lib/builtin/editor/src/editor_controller.dart index 8dcfe96b..fc1dbed1 100644 --- a/lib/builtin/editor/src/editor_controller.dart +++ b/lib/builtin/editor/src/editor_controller.dart @@ -17,14 +17,14 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter/foundation.dart'; class EditorController extends ChangeNotifier { - EditorController({required this.ipc, required EventBus events}) + EditorController({required this.ipc, required DaemonBus events}) : _events = events { _eventSub = events.on().listen(_onEvent); } final DaemonClient ipc; // ignore: unused_field — kept for future subscription changes - final EventBus _events; + final DaemonBus _events; StreamSubscription? _eventSub; diff --git a/lib/builtin/files/src/file_tree_controller.dart b/lib/builtin/files/src/file_tree_controller.dart index 7ca4342f..7f7ec304 100644 --- a/lib/builtin/files/src/file_tree_controller.dart +++ b/lib/builtin/files/src/file_tree_controller.dart @@ -20,7 +20,7 @@ class FileTreeController extends ChangeNotifier { } final DaemonClient ipc; - final EventBus events; + final DaemonBus events; StreamSubscription? _eventSub; diff --git a/lib/builtin/git/src/git_controller.dart b/lib/builtin/git/src/git_controller.dart index d14e190b..ac9fba10 100644 --- a/lib/builtin/git/src/git_controller.dart +++ b/lib/builtin/git/src/git_controller.dart @@ -17,7 +17,7 @@ class GitController extends ChangeNotifier { } final DaemonClient ipc; - final EventBus events; + final DaemonBus events; StreamSubscription? _eventSub; diff --git a/lib/builtin/pql/src/backlinks_controller.dart b/lib/builtin/pql/src/backlinks_controller.dart index 3e70b5d1..552ed471 100644 --- a/lib/builtin/pql/src/backlinks_controller.dart +++ b/lib/builtin/pql/src/backlinks_controller.dart @@ -13,7 +13,7 @@ class BacklinksController extends ChangeNotifier { } final DaemonClient ipc; - final EventBus events; + final DaemonBus events; StreamSubscription? _eventSub; diff --git a/lib/extension/src/extension.dart b/lib/extension/src/extension.dart index eaa1eaf1..547ef903 100644 --- a/lib/extension/src/extension.dart +++ b/lib/extension/src/extension.dart @@ -4,6 +4,7 @@ import 'package:clide/kernel/src/commands/palette.dart'; import 'package:clide/kernel/src/commands/registry.dart'; import 'package:clide/kernel/src/dialog.dart'; import 'package:clide/kernel/src/events/bus.dart'; +import 'package:clide/kernel/src/events/message_bus.dart'; import 'package:clide/kernel/src/files.dart'; import 'package:clide/kernel/src/focus.dart'; import 'package:clide/kernel/src/i18n/i18n.dart'; @@ -53,7 +54,8 @@ abstract class ClideExtensionContext { String get id; Logger get log; - EventBus get events; + DaemonBus get events; + MessageBus get messages; SettingsStore get settings; ThemeController get theme; I18n get i18n; @@ -74,6 +76,17 @@ abstract class ClideExtensionContext { DaemonClient get ipc; } +/// Sugar for message bus scoped to this extension's publisher ID. +extension ClideExtensionContextMessages on ClideExtensionContext { + void publish(String channel, Map data) { + messages.publish(id, channel, data); + } + + Stream subscribe({String? publisher, String? channel}) { + return messages.subscribe(publisher: publisher, channel: channel); + } +} + /// Sugar for i18n lookups scoped to this extension's namespace. extension ClideExtensionContextI18n on ClideExtensionContext { /// `ctx.t('welcome.title', placeholder: 'clide')` → diff --git a/lib/kernel/kernel.dart b/lib/kernel/kernel.dart index bdd0b571..e19f4ed8 100644 --- a/lib/kernel/kernel.dart +++ b/lib/kernel/kernel.dart @@ -12,6 +12,7 @@ library; export 'src/events/bus.dart'; +export 'src/events/message_bus.dart'; export 'src/events/types.dart'; export 'src/ipc/client.dart'; export 'src/log.dart'; diff --git a/lib/kernel/src/events/bus.dart b/lib/kernel/src/events/bus.dart index 404f7ce5..b4aece6d 100644 --- a/lib/kernel/src/events/bus.dart +++ b/lib/kernel/src/events/bus.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:clide/kernel/src/events/types.dart'; -class EventBus { - EventBus(); +class DaemonBus { + DaemonBus(); final StreamController _controller = StreamController.broadcast(); diff --git a/lib/kernel/src/events/message_bus.dart b/lib/kernel/src/events/message_bus.dart new file mode 100644 index 00000000..7c7b2c13 --- /dev/null +++ b/lib/kernel/src/events/message_bus.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +class Message { + Message({ + required this.publisher, + required this.channel, + required this.data, + }) : timestamp = DateTime.now(); + + final String publisher; + final String channel; + final DateTime timestamp; + final Map data; + + String get address => '$publisher/$channel'; +} + +class MessageBus { + final _controller = StreamController.broadcast(); + + void publish(String publisher, String channel, Map data) { + _controller.add(Message(publisher: publisher, channel: channel, data: data)); + } + + Stream subscribe({String? publisher, String? channel}) { + return _controller.stream.where((m) { + if (publisher != null && m.publisher != publisher) return false; + if (channel != null && m.channel != channel) return false; + return true; + }); + } + + void dispose() { + _controller.close(); + } +} diff --git a/lib/kernel/src/extensions_manager.dart b/lib/kernel/src/extensions_manager.dart index 0da5479a..9b3f03d0 100644 --- a/lib/kernel/src/extensions_manager.dart +++ b/lib/kernel/src/extensions_manager.dart @@ -8,6 +8,7 @@ import 'package:clide/kernel/src/commands/palette.dart'; import 'package:clide/kernel/src/commands/registry.dart'; import 'package:clide/kernel/src/dialog.dart'; import 'package:clide/kernel/src/events/bus.dart'; +import 'package:clide/kernel/src/events/message_bus.dart'; import 'package:clide/kernel/src/events/types.dart'; import 'package:clide/kernel/src/files.dart'; import 'package:clide/kernel/src/focus.dart'; @@ -30,6 +31,7 @@ class ExtensionManager extends ChangeNotifier { ExtensionManager({ required this.log, required this.events, + required this.messages, required this.settings, required this.theme, required this.i18n, @@ -52,7 +54,8 @@ class ExtensionManager extends ChangeNotifier { }); final Logger log; - final EventBus events; + final DaemonBus events; + final MessageBus messages; final SettingsStore settings; final ThemeController theme; final I18n i18n; @@ -239,7 +242,9 @@ class _ExtensionContext implements ClideExtensionContext { @override Logger get log => manager.log; @override - EventBus get events => manager.events; + DaemonBus get events => manager.events; + @override + MessageBus get messages => manager.messages; @override SettingsStore get settings => manager.settings; @override diff --git a/lib/kernel/src/facade.dart b/lib/kernel/src/facade.dart index e4c6a940..8582a14a 100644 --- a/lib/kernel/src/facade.dart +++ b/lib/kernel/src/facade.dart @@ -8,6 +8,7 @@ import 'package:clide/kernel/src/commands/palette.dart'; import 'package:clide/kernel/src/commands/registry.dart'; import 'package:clide/kernel/src/dialog.dart'; import 'package:clide/kernel/src/events/bus.dart'; +import 'package:clide/kernel/src/events/message_bus.dart'; import 'package:clide/kernel/src/extensions_manager.dart'; import 'package:clide/kernel/src/files.dart'; import 'package:clide/kernel/src/focus.dart'; @@ -38,6 +39,7 @@ class KernelServices { required this.log, required this.settings, required this.events, + required this.messages, required this.ipc, required this.theme, required this.i18n, @@ -63,7 +65,8 @@ class KernelServices { final Logger log; final SettingsStore settings; - final EventBus events; + final DaemonBus events; + final MessageBus messages; final DaemonClient ipc; final ThemeController theme; final I18n i18n; @@ -95,11 +98,12 @@ class KernelServices { Locale? initialLocale, List availableLocales = const [Locale('en', 'US')], String? socketPath, - DaemonClient Function(Logger, EventBus)? daemonClientFactory, + DaemonClient Function(Logger, DaemonBus)? daemonClientFactory, bool autoStartDaemonClient = true, }) async { final log = Logger(); - final events = EventBus(); + final events = DaemonBus(); + final messages = MessageBus(); final settings = SettingsStore(appDir: appDir); await settings.load(); @@ -147,6 +151,7 @@ class KernelServices { final extensions = ExtensionManager( log: log, events: events, + messages: messages, settings: settings, theme: theme, i18n: i18n, @@ -176,6 +181,7 @@ class KernelServices { log: log, settings: settings, events: events, + messages: messages, ipc: ipc, theme: theme, i18n: i18n, @@ -218,6 +224,7 @@ class KernelServices { project.dispose(); extensions.dispose(); await log.dispose(); + messages.dispose(); await events.dispose(); } } diff --git a/lib/kernel/src/files.dart b/lib/kernel/src/files.dart index bc532c19..e1b26629 100644 --- a/lib/kernel/src/files.dart +++ b/lib/kernel/src/files.dart @@ -31,7 +31,7 @@ class FilesDropped extends ClideEvent { /// the service surface real. class FileServices { FileServices(this._events); - final EventBus _events; + final DaemonBus _events; Future> pickOpen({ List extensions = const [], diff --git a/lib/kernel/src/ipc/client.dart b/lib/kernel/src/ipc/client.dart index acf68dc9..431fc5d7 100644 --- a/lib/kernel/src/ipc/client.dart +++ b/lib/kernel/src/ipc/client.dart @@ -13,13 +13,13 @@ class DaemonClient extends ChangeNotifier { DaemonClient({ required this.socketPath, required Logger log, - required EventBus events, + required DaemonBus events, }) : _log = log, _events = events; final String socketPath; final Logger _log; - final EventBus _events; + final DaemonBus _events; Socket? _socket; bool _connected = false; diff --git a/lib/kernel/src/ipc/in_process.dart b/lib/kernel/src/ipc/in_process.dart index 3c1d0c30..8459e80e 100644 --- a/lib/kernel/src/ipc/in_process.dart +++ b/lib/kernel/src/ipc/in_process.dart @@ -7,7 +7,7 @@ import 'package:clide/src/daemon/dispatcher.dart'; class InProcessClient extends DaemonClient { InProcessClient({ required Logger log, - required EventBus events, + required DaemonBus events, required this.dispatcher, }) : super(socketPath: '', log: log, events: events); diff --git a/lib/kernel/src/os.dart b/lib/kernel/src/os.dart index 619b98aa..adf9b5a4 100644 --- a/lib/kernel/src/os.dart +++ b/lib/kernel/src/os.dart @@ -16,12 +16,12 @@ class OsLifecycleEvent extends ClideEvent { } class OsBridge { - OsBridge({required Logger log, required EventBus events}) + OsBridge({required Logger log, required DaemonBus events}) : _log = log, _events = events; final Logger _log; - final EventBus _events; + final DaemonBus _events; Future openURL(String url) async { final cmd = _openCommand(); diff --git a/lib/kernel/src/project.dart b/lib/kernel/src/project.dart index 3c60092b..b6bb2323 100644 --- a/lib/kernel/src/project.dart +++ b/lib/kernel/src/project.dart @@ -45,14 +45,14 @@ class RecentProject { class ProjectManager extends ChangeNotifier { ProjectManager({ required Logger log, - required EventBus events, + required DaemonBus events, required SettingsStore settings, }) : _log = log, _events = events, _settings = settings; final Logger _log; - final EventBus _events; + final DaemonBus _events; final SettingsStore _settings; Directory? _current; diff --git a/lib/main.dart b/lib/main.dart index 00bedcf9..62fc48b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -170,7 +170,7 @@ String _resolvePtyc(String repoRoot) { class _BusEventSink implements DaemonEventSink { _BusEventSink(this._bus); - final EventBus _bus; + final DaemonBus _bus; @override void emit(IpcEvent event) { diff --git a/test/kernel/src/events/bus_test.dart b/test/kernel/src/events/bus_test.dart index 1655b60f..d40561be 100644 --- a/test/kernel/src/events/bus_test.dart +++ b/test/kernel/src/events/bus_test.dart @@ -2,10 +2,10 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - group('EventBus', () { - late EventBus bus; + group('DaemonBus', () { + late DaemonBus bus; - setUp(() => bus = EventBus()); + setUp(() => bus = DaemonBus()); tearDown(() => bus.dispose()); test('emit delivers to stream subscribers', () async {