split EventBus into DaemonBus + MessageBus
DaemonBus (was EventBus): typed events for system/IPC layer. MessageBus: channel-based pub/sub for UI/extension coordination. Messages carry publisher (auto-stamped from extension ID), channel (required), timestamp, and payload. Subscribe by publisher, channel, or both — zero collision across extensions. Extension context gains publish() and subscribe() convenience methods that auto-stamp the extension's ID as publisher. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,3 +88,6 @@ legacy/**/.coverage
|
|||||||
legacy/**/.coverage.*
|
legacy/**/.coverage.*
|
||||||
.clide/settings.yaml
|
.clide/settings.yaml
|
||||||
.claude/skills/pql/
|
.claude/skills/pql/
|
||||||
|
.pql/*
|
||||||
|
!.pql/pql-plan.json
|
||||||
|
!.pql/hooks/
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class DiffController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
|
||||||
StreamSubscription<DaemonEvent>? _eventSub;
|
StreamSubscription<DaemonEvent>? _eventSub;
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ import 'package:clide/kernel/kernel.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class EditorController extends ChangeNotifier {
|
class EditorController extends ChangeNotifier {
|
||||||
EditorController({required this.ipc, required EventBus events})
|
EditorController({required this.ipc, required DaemonBus events})
|
||||||
: _events = events {
|
: _events = events {
|
||||||
_eventSub = events.on<DaemonEvent>().listen(_onEvent);
|
_eventSub = events.on<DaemonEvent>().listen(_onEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
// ignore: unused_field — kept for future subscription changes
|
// ignore: unused_field — kept for future subscription changes
|
||||||
final EventBus _events;
|
final DaemonBus _events;
|
||||||
|
|
||||||
StreamSubscription<DaemonEvent>? _eventSub;
|
StreamSubscription<DaemonEvent>? _eventSub;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class FileTreeController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
|
||||||
StreamSubscription<DaemonEvent>? _eventSub;
|
StreamSubscription<DaemonEvent>? _eventSub;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class GitController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
|
||||||
StreamSubscription<DaemonEvent>? _eventSub;
|
StreamSubscription<DaemonEvent>? _eventSub;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class BacklinksController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
|
||||||
StreamSubscription<DaemonEvent>? _eventSub;
|
StreamSubscription<DaemonEvent>? _eventSub;
|
||||||
|
|
||||||
|
|||||||
@@ -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/commands/registry.dart';
|
||||||
import 'package:clide/kernel/src/dialog.dart';
|
import 'package:clide/kernel/src/dialog.dart';
|
||||||
import 'package:clide/kernel/src/events/bus.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/files.dart';
|
||||||
import 'package:clide/kernel/src/focus.dart';
|
import 'package:clide/kernel/src/focus.dart';
|
||||||
import 'package:clide/kernel/src/i18n/i18n.dart';
|
import 'package:clide/kernel/src/i18n/i18n.dart';
|
||||||
@@ -53,7 +54,8 @@ abstract class ClideExtensionContext {
|
|||||||
String get id;
|
String get id;
|
||||||
|
|
||||||
Logger get log;
|
Logger get log;
|
||||||
EventBus get events;
|
DaemonBus get events;
|
||||||
|
MessageBus get messages;
|
||||||
SettingsStore get settings;
|
SettingsStore get settings;
|
||||||
ThemeController get theme;
|
ThemeController get theme;
|
||||||
I18n get i18n;
|
I18n get i18n;
|
||||||
@@ -74,6 +76,17 @@ abstract class ClideExtensionContext {
|
|||||||
DaemonClient get ipc;
|
DaemonClient get ipc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sugar for message bus scoped to this extension's publisher ID.
|
||||||
|
extension ClideExtensionContextMessages on ClideExtensionContext {
|
||||||
|
void publish(String channel, Map<String, Object?> data) {
|
||||||
|
messages.publish(id, channel, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Message> subscribe({String? publisher, String? channel}) {
|
||||||
|
return messages.subscribe(publisher: publisher, channel: channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sugar for i18n lookups scoped to this extension's namespace.
|
/// Sugar for i18n lookups scoped to this extension's namespace.
|
||||||
extension ClideExtensionContextI18n on ClideExtensionContext {
|
extension ClideExtensionContextI18n on ClideExtensionContext {
|
||||||
/// `ctx.t('welcome.title', placeholder: 'clide')` →
|
/// `ctx.t('welcome.title', placeholder: 'clide')` →
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
export 'src/events/bus.dart';
|
export 'src/events/bus.dart';
|
||||||
|
export 'src/events/message_bus.dart';
|
||||||
export 'src/events/types.dart';
|
export 'src/events/types.dart';
|
||||||
export 'src/ipc/client.dart';
|
export 'src/ipc/client.dart';
|
||||||
export 'src/log.dart';
|
export 'src/log.dart';
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:clide/kernel/src/events/types.dart';
|
import 'package:clide/kernel/src/events/types.dart';
|
||||||
|
|
||||||
class EventBus {
|
class DaemonBus {
|
||||||
EventBus();
|
DaemonBus();
|
||||||
|
|
||||||
final StreamController<ClideEventEnvelope> _controller =
|
final StreamController<ClideEventEnvelope> _controller =
|
||||||
StreamController<ClideEventEnvelope>.broadcast();
|
StreamController<ClideEventEnvelope>.broadcast();
|
||||||
|
|||||||
@@ -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<String, Object?> data;
|
||||||
|
|
||||||
|
String get address => '$publisher/$channel';
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageBus {
|
||||||
|
final _controller = StreamController<Message>.broadcast();
|
||||||
|
|
||||||
|
void publish(String publisher, String channel, Map<String, Object?> data) {
|
||||||
|
_controller.add(Message(publisher: publisher, channel: channel, data: data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Message> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/commands/registry.dart';
|
||||||
import 'package:clide/kernel/src/dialog.dart';
|
import 'package:clide/kernel/src/dialog.dart';
|
||||||
import 'package:clide/kernel/src/events/bus.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/events/types.dart';
|
||||||
import 'package:clide/kernel/src/files.dart';
|
import 'package:clide/kernel/src/files.dart';
|
||||||
import 'package:clide/kernel/src/focus.dart';
|
import 'package:clide/kernel/src/focus.dart';
|
||||||
@@ -30,6 +31,7 @@ class ExtensionManager extends ChangeNotifier {
|
|||||||
ExtensionManager({
|
ExtensionManager({
|
||||||
required this.log,
|
required this.log,
|
||||||
required this.events,
|
required this.events,
|
||||||
|
required this.messages,
|
||||||
required this.settings,
|
required this.settings,
|
||||||
required this.theme,
|
required this.theme,
|
||||||
required this.i18n,
|
required this.i18n,
|
||||||
@@ -52,7 +54,8 @@ class ExtensionManager extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final Logger log;
|
final Logger log;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
final MessageBus messages;
|
||||||
final SettingsStore settings;
|
final SettingsStore settings;
|
||||||
final ThemeController theme;
|
final ThemeController theme;
|
||||||
final I18n i18n;
|
final I18n i18n;
|
||||||
@@ -239,7 +242,9 @@ class _ExtensionContext implements ClideExtensionContext {
|
|||||||
@override
|
@override
|
||||||
Logger get log => manager.log;
|
Logger get log => manager.log;
|
||||||
@override
|
@override
|
||||||
EventBus get events => manager.events;
|
DaemonBus get events => manager.events;
|
||||||
|
@override
|
||||||
|
MessageBus get messages => manager.messages;
|
||||||
@override
|
@override
|
||||||
SettingsStore get settings => manager.settings;
|
SettingsStore get settings => manager.settings;
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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/commands/registry.dart';
|
||||||
import 'package:clide/kernel/src/dialog.dart';
|
import 'package:clide/kernel/src/dialog.dart';
|
||||||
import 'package:clide/kernel/src/events/bus.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/extensions_manager.dart';
|
||||||
import 'package:clide/kernel/src/files.dart';
|
import 'package:clide/kernel/src/files.dart';
|
||||||
import 'package:clide/kernel/src/focus.dart';
|
import 'package:clide/kernel/src/focus.dart';
|
||||||
@@ -38,6 +39,7 @@ class KernelServices {
|
|||||||
required this.log,
|
required this.log,
|
||||||
required this.settings,
|
required this.settings,
|
||||||
required this.events,
|
required this.events,
|
||||||
|
required this.messages,
|
||||||
required this.ipc,
|
required this.ipc,
|
||||||
required this.theme,
|
required this.theme,
|
||||||
required this.i18n,
|
required this.i18n,
|
||||||
@@ -63,7 +65,8 @@ class KernelServices {
|
|||||||
|
|
||||||
final Logger log;
|
final Logger log;
|
||||||
final SettingsStore settings;
|
final SettingsStore settings;
|
||||||
final EventBus events;
|
final DaemonBus events;
|
||||||
|
final MessageBus messages;
|
||||||
final DaemonClient ipc;
|
final DaemonClient ipc;
|
||||||
final ThemeController theme;
|
final ThemeController theme;
|
||||||
final I18n i18n;
|
final I18n i18n;
|
||||||
@@ -95,11 +98,12 @@ class KernelServices {
|
|||||||
Locale? initialLocale,
|
Locale? initialLocale,
|
||||||
List<Locale> availableLocales = const [Locale('en', 'US')],
|
List<Locale> availableLocales = const [Locale('en', 'US')],
|
||||||
String? socketPath,
|
String? socketPath,
|
||||||
DaemonClient Function(Logger, EventBus)? daemonClientFactory,
|
DaemonClient Function(Logger, DaemonBus)? daemonClientFactory,
|
||||||
bool autoStartDaemonClient = true,
|
bool autoStartDaemonClient = true,
|
||||||
}) async {
|
}) async {
|
||||||
final log = Logger();
|
final log = Logger();
|
||||||
final events = EventBus();
|
final events = DaemonBus();
|
||||||
|
final messages = MessageBus();
|
||||||
|
|
||||||
final settings = SettingsStore(appDir: appDir);
|
final settings = SettingsStore(appDir: appDir);
|
||||||
await settings.load();
|
await settings.load();
|
||||||
@@ -147,6 +151,7 @@ class KernelServices {
|
|||||||
final extensions = ExtensionManager(
|
final extensions = ExtensionManager(
|
||||||
log: log,
|
log: log,
|
||||||
events: events,
|
events: events,
|
||||||
|
messages: messages,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
i18n: i18n,
|
i18n: i18n,
|
||||||
@@ -176,6 +181,7 @@ class KernelServices {
|
|||||||
log: log,
|
log: log,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
events: events,
|
events: events,
|
||||||
|
messages: messages,
|
||||||
ipc: ipc,
|
ipc: ipc,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
i18n: i18n,
|
i18n: i18n,
|
||||||
@@ -218,6 +224,7 @@ class KernelServices {
|
|||||||
project.dispose();
|
project.dispose();
|
||||||
extensions.dispose();
|
extensions.dispose();
|
||||||
await log.dispose();
|
await log.dispose();
|
||||||
|
messages.dispose();
|
||||||
await events.dispose();
|
await events.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class FilesDropped extends ClideEvent {
|
|||||||
/// the service surface real.
|
/// the service surface real.
|
||||||
class FileServices {
|
class FileServices {
|
||||||
FileServices(this._events);
|
FileServices(this._events);
|
||||||
final EventBus _events;
|
final DaemonBus _events;
|
||||||
|
|
||||||
Future<List<String>> pickOpen({
|
Future<List<String>> pickOpen({
|
||||||
List<String> extensions = const [],
|
List<String> extensions = const [],
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ class DaemonClient extends ChangeNotifier {
|
|||||||
DaemonClient({
|
DaemonClient({
|
||||||
required this.socketPath,
|
required this.socketPath,
|
||||||
required Logger log,
|
required Logger log,
|
||||||
required EventBus events,
|
required DaemonBus events,
|
||||||
}) : _log = log,
|
}) : _log = log,
|
||||||
_events = events;
|
_events = events;
|
||||||
|
|
||||||
final String socketPath;
|
final String socketPath;
|
||||||
final Logger _log;
|
final Logger _log;
|
||||||
final EventBus _events;
|
final DaemonBus _events;
|
||||||
|
|
||||||
Socket? _socket;
|
Socket? _socket;
|
||||||
bool _connected = false;
|
bool _connected = false;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:clide/src/daemon/dispatcher.dart';
|
|||||||
class InProcessClient extends DaemonClient {
|
class InProcessClient extends DaemonClient {
|
||||||
InProcessClient({
|
InProcessClient({
|
||||||
required Logger log,
|
required Logger log,
|
||||||
required EventBus events,
|
required DaemonBus events,
|
||||||
required this.dispatcher,
|
required this.dispatcher,
|
||||||
}) : super(socketPath: '', log: log, events: events);
|
}) : super(socketPath: '', log: log, events: events);
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ class OsLifecycleEvent extends ClideEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OsBridge {
|
class OsBridge {
|
||||||
OsBridge({required Logger log, required EventBus events})
|
OsBridge({required Logger log, required DaemonBus events})
|
||||||
: _log = log,
|
: _log = log,
|
||||||
_events = events;
|
_events = events;
|
||||||
|
|
||||||
final Logger _log;
|
final Logger _log;
|
||||||
final EventBus _events;
|
final DaemonBus _events;
|
||||||
|
|
||||||
Future<bool> openURL(String url) async {
|
Future<bool> openURL(String url) async {
|
||||||
final cmd = _openCommand();
|
final cmd = _openCommand();
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ class RecentProject {
|
|||||||
class ProjectManager extends ChangeNotifier {
|
class ProjectManager extends ChangeNotifier {
|
||||||
ProjectManager({
|
ProjectManager({
|
||||||
required Logger log,
|
required Logger log,
|
||||||
required EventBus events,
|
required DaemonBus events,
|
||||||
required SettingsStore settings,
|
required SettingsStore settings,
|
||||||
}) : _log = log,
|
}) : _log = log,
|
||||||
_events = events,
|
_events = events,
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
|
||||||
final Logger _log;
|
final Logger _log;
|
||||||
final EventBus _events;
|
final DaemonBus _events;
|
||||||
final SettingsStore _settings;
|
final SettingsStore _settings;
|
||||||
|
|
||||||
Directory? _current;
|
Directory? _current;
|
||||||
|
|||||||
+1
-1
@@ -170,7 +170,7 @@ String _resolvePtyc(String repoRoot) {
|
|||||||
|
|
||||||
class _BusEventSink implements DaemonEventSink {
|
class _BusEventSink implements DaemonEventSink {
|
||||||
_BusEventSink(this._bus);
|
_BusEventSink(this._bus);
|
||||||
final EventBus _bus;
|
final DaemonBus _bus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void emit(IpcEvent event) {
|
void emit(IpcEvent event) {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import 'package:clide/kernel/kernel.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('EventBus', () {
|
group('DaemonBus', () {
|
||||||
late EventBus bus;
|
late DaemonBus bus;
|
||||||
|
|
||||||
setUp(() => bus = EventBus());
|
setUp(() => bus = DaemonBus());
|
||||||
tearDown(() => bus.dispose());
|
tearDown(() => bus.dispose());
|
||||||
|
|
||||||
test('emit delivers to stream subscribers', () async {
|
test('emit delivers to stream subscribers', () async {
|
||||||
|
|||||||
Reference in New Issue
Block a user