restructure pql sidebar: Search default, Markdown tab with viewer
Search (PQL DSL query) is now the default left tab; Markdown (filtered to .md files) on the right. Clicking a markdown file publishes on the message bus; the markdown extension activates the context panel viewer and re-publishes on a load channel after the frame so the viewer is guaranteed to be mounted. Viewer subscribes to load channel and editor buffer events, publishes focus for sidebar highlighting. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import 'dart:async';
|
||||
import 'package:clide/builtin/markdown/src/markdown_viewer.dart';
|
||||
import 'package:clide/extension/extension.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MarkdownExtension extends ClideExtension {
|
||||
@override
|
||||
@@ -10,51 +12,35 @@ class MarkdownExtension extends ClideExtension {
|
||||
@override
|
||||
String get title => 'Markdown';
|
||||
@override
|
||||
String get version => '0.2.0';
|
||||
String get version => '0.3.0';
|
||||
@override
|
||||
List<String> get dependsOn => const ['builtin.editor'];
|
||||
List<String> get dependsOn => const [];
|
||||
|
||||
ClideExtensionContext? _ctx;
|
||||
StreamSubscription<DaemonEvent>? _editorSub;
|
||||
bool _viewerSpawned = false;
|
||||
StreamSubscription<Message>? _sub;
|
||||
|
||||
@override
|
||||
List<ContributionPoint> get contributions => const [];
|
||||
List<ContributionPoint> get contributions => [
|
||||
TabContribution(
|
||||
id: 'markdown.viewer',
|
||||
slot: Slots.contextPanel,
|
||||
title: 'Markdown',
|
||||
icon: PhosphorIcons.fileText,
|
||||
build: (_) => const MarkdownViewer(),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> activate(ClideExtensionContext ctx) async {
|
||||
_ctx = ctx;
|
||||
_editorSub = ctx.events.on<DaemonEvent>().listen((e) {
|
||||
if (e.subsystem != 'editor') return;
|
||||
if (e.kind == 'editor.active-changed' || e.kind == 'editor.opened') {
|
||||
final path = e.data['path'] as String?;
|
||||
if (path != null && path.endsWith('.md')) {
|
||||
_spawnViewer();
|
||||
}
|
||||
}
|
||||
_sub = ctx.messages.subscribe(publisher: id, channel: 'selection').listen((msg) {
|
||||
final path = msg.data['path'] as String?;
|
||||
if (path == null) return;
|
||||
ctx.panels.activateTab(Slots.contextPanel, 'markdown.viewer');
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ctx.messages.publish(id, 'load', {'path': path});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deactivate() async {
|
||||
_editorSub?.cancel();
|
||||
if (_viewerSpawned) {
|
||||
_ctx?.panels.uncontribute('markdown.viewer');
|
||||
_viewerSpawned = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _spawnViewer() {
|
||||
final ctx = _ctx;
|
||||
if (ctx == null || _viewerSpawned) return;
|
||||
ctx.panels.contribute(TabContribution(
|
||||
id: 'markdown.viewer',
|
||||
slot: Slots.contextPanel,
|
||||
title: 'Viewer',
|
||||
priority: -100,
|
||||
build: (_) => const MarkdownViewer(),
|
||||
));
|
||||
_viewerSpawned = true;
|
||||
ctx.panels.activateTab(Slots.contextPanel, 'markdown.viewer');
|
||||
}
|
||||
Future<void> deactivate() async => _sub?.cancel();
|
||||
}
|
||||
|
||||
@@ -15,14 +15,19 @@ class _MarkdownViewerState extends State<MarkdownViewer> {
|
||||
String? _path;
|
||||
String? _content;
|
||||
String? _error;
|
||||
StreamSubscription<DaemonEvent>? _eventSub;
|
||||
StreamSubscription<Message>? _selectionSub;
|
||||
StreamSubscription<DaemonEvent>? _editorSub;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_eventSub != null) return;
|
||||
if (_selectionSub != null) return;
|
||||
final kernel = ClideKernel.of(context);
|
||||
_eventSub = kernel.events.on<DaemonEvent>().listen((e) {
|
||||
_selectionSub = kernel.messages.subscribe(publisher: 'builtin.markdown', channel: 'load').listen((msg) {
|
||||
final path = msg.data['path'] as String?;
|
||||
if (path != null) _loadFile(path);
|
||||
});
|
||||
_editorSub = kernel.events.on<DaemonEvent>().listen((e) {
|
||||
if (e.kind == 'editor.buffer_activated') {
|
||||
final path = e.data['path'] as String?;
|
||||
if (path != null && path.endsWith('.md')) {
|
||||
@@ -30,33 +35,21 @@ class _MarkdownViewerState extends State<MarkdownViewer> {
|
||||
}
|
||||
}
|
||||
});
|
||||
final activeTab = kernel.panels.activeTabIn(Slots.workspace);
|
||||
if (activeTab == 'editor.active') {
|
||||
unawaited(_loadActiveBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_eventSub?.cancel();
|
||||
_selectionSub?.cancel();
|
||||
_editorSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadActiveBuffer() async {
|
||||
final kernel = ClideKernel.of(context);
|
||||
final resp = await kernel.ipc.request('editor.active');
|
||||
if (!mounted || !resp.ok) return;
|
||||
final path = resp.data['path'] as String?;
|
||||
if (path != null && path.endsWith('.md')) {
|
||||
await _loadFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadFile(String path) async {
|
||||
final kernel = ClideKernel.of(context);
|
||||
final resp = await kernel.ipc.request('files.read', args: {'path': path});
|
||||
if (!mounted) return;
|
||||
if (resp.ok) {
|
||||
kernel.messages.publish('builtin.markdown', 'focus', {'path': path});
|
||||
setState(() {
|
||||
_path = path;
|
||||
_content = resp.data['content'] as String? ?? '';
|
||||
@@ -84,7 +77,7 @@ class _MarkdownViewerState extends State<MarkdownViewer> {
|
||||
if (_content == null) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: ClideText('Open a .md file to preview it here.', muted: true),
|
||||
child: ClideText('Select a .md file to preview it here.', muted: true),
|
||||
);
|
||||
}
|
||||
return ClidePaneChrome(
|
||||
|
||||
Reference in New Issue
Block a user