From 6d0ebab721ced9b5700439485e47c07a5f50c265 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 11 Jun 2026 12:11:53 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20adopt=20Dart=203.9=20toolchain=20?= =?UTF-8?q?=E2=80=94=20honest=20floor=20+=20tall-style=20reformat=20(T-353?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Raise the declared minimums in pubspec.yaml to what our deps already require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist 0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is the binding floor. Pin the exact build toolchain in .fvmrc (Flutter 3.44.1). Moving to the Dart 3.9 language level switches `dart format` to the new "tall" style and enables two new lints. This commit is the resulting mechanical churn, isolated from any behaviour change: - whole-tree `dart format` reformat (tall style) - `dart fix` for unnecessary_underscores + use_null_aware_elements No runtime behaviour change; `make test` green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .fvmrc | 3 + CHANGELOG.md | 16 +- docs/claude-design/themes/clide_theme.dart | 192 +++--- docs/claude-design/themes/midnight_theme.dart | 58 +- docs/claude-design/themes/paper_theme.dart | 58 +- docs/claude-design/themes/terminal_theme.dart | 60 +- docs/claude-design/tokens/clide_tokens.dart | 8 +- integration_test/app_starts_test.dart | 16 +- .../extension_lifecycle_test.dart | 15 +- integration_test/theme_picker_test.dart | 15 +- lib/app.dart | 178 ++--- lib/builtin/claude/src/agent_bootstrap.dart | 39 +- lib/builtin/claude/src/claude_banner.dart | 12 +- lib/builtin/claude/src/claude_composer.dart | 38 +- lib/builtin/claude/src/claude_config.dart | 89 +-- .../claude/src/claude_meta_sidebar.dart | 269 +++----- lib/builtin/claude/src/claude_pane.dart | 83 +-- .../claude/src/claude_session_host.dart | 24 +- lib/builtin/claude/src/claude_stats.dart | 21 +- lib/builtin/claude/src/claude_status.dart | 5 +- lib/builtin/claude/src/claude_task_dock.dart | 46 +- lib/builtin/claude/src/clipboard_paste.dart | 10 +- lib/builtin/claude/src/conversation_card.dart | 42 +- .../claude/src/conversation_controller.dart | 18 +- lib/builtin/claude/src/conversation_view.dart | 246 ++++--- lib/builtin/claude/src/extension.dart | 537 +++++++-------- lib/builtin/claude/src/image_thumbnail.dart | 22 +- .../claude/src/permission_mode_control.dart | 46 +- lib/builtin/claude/src/prompt_card.dart | 110 ++- lib/builtin/claude/src/session_index.dart | 30 +- .../claude/src/session_orchestrator.dart | 33 +- lib/builtin/claude/src/session_picker.dart | 21 +- lib/builtin/claude/src/session_storage.dart | 26 +- .../claude/src/stream_json_session.dart | 133 ++-- lib/builtin/claude/src/task_list.dart | 8 +- lib/builtin/claude/src/team_broker.dart | 48 +- lib/builtin/claude/src/team_chat_model.dart | 6 +- lib/builtin/claude/src/team_chat_sidebar.dart | 94 +-- lib/builtin/claude/src/team_panel_host.dart | 17 +- lib/builtin/claude/src/ticket_pick_up.dart | 11 +- .../claude/src/transcript_publisher.dart | 23 +- lib/builtin/claude/src/transcript_reader.dart | 145 ++-- lib/builtin/cli_install/src/extension.dart | 45 +- .../decisions/src/decision_colors.dart | 28 +- .../decisions/src/decision_detail_view.dart | 26 +- lib/builtin/decisions/src/decisions_view.dart | 75 +- lib/builtin/decisions/src/extension.dart | 34 +- lib/builtin/deeplink/src/deep_link.dart | 6 +- lib/builtin/deeplink/src/extension.dart | 9 +- lib/builtin/default_layout/src/extension.dart | 153 ++--- lib/builtin/diff/src/diff_controller.dart | 10 +- lib/builtin/diff/src/diff_view.dart | 93 +-- lib/builtin/diff/src/extension.dart | 20 +- lib/builtin/editor/src/editor_controller.dart | 18 +- lib/builtin/editor/src/editor_view.dart | 37 +- lib/builtin/editor/src/extension.dart | 20 +- .../editor/src/syntax_text_controller.dart | 55 +- lib/builtin/editor/src/vim_edit_ops.dart | 98 ++- lib/builtin/files/src/extension.dart | 22 +- lib/builtin/files/src/file_tree_view.dart | 73 +- lib/builtin/git/src/extension.dart | 28 +- lib/builtin/git/src/git_controller.dart | 4 +- lib/builtin/git/src/git_panel_view.dart | 283 +++----- lib/builtin/git/src/git_status_item.dart | 48 +- lib/builtin/graph/src/graph_view.dart | 22 +- lib/builtin/ipc_status/src/extension.dart | 8 +- lib/builtin/ipc_status/src/status_item.dart | 6 +- lib/builtin/keybindings_ui/src/extension.dart | 29 +- lib/builtin/markdown/src/extension.dart | 16 +- lib/builtin/markdown/src/markdown_viewer.dart | 10 +- lib/builtin/menubar/src/about_dialog.dart | 9 +- lib/builtin/menubar/src/extension.dart | 128 ++-- lib/builtin/menubar/src/file_actions.dart | 15 +- lib/builtin/menubar/src/menu_bar.dart | 17 +- lib/builtin/menubar/src/menu_model.dart | 17 +- lib/builtin/output/src/dock_status_item.dart | 4 +- lib/builtin/output/src/extension.dart | 60 +- lib/builtin/output/src/output_view.dart | 42 +- lib/builtin/pql/src/backlinks_view.dart | 49 +- lib/builtin/pql/src/extension.dart | 18 +- lib/builtin/pql/src/pql_controller.dart | 15 +- lib/builtin/pql/src/pql_search_body.dart | 17 +- lib/builtin/problems/src/extension.dart | 24 +- .../problems/src/problems_controller.dart | 36 +- lib/builtin/problems/src/problems_view.dart | 35 +- lib/builtin/search/src/extension.dart | 18 +- .../search/src/find_in_files_controller.dart | 32 +- lib/builtin/search/src/search_panel_view.dart | 128 ++-- lib/builtin/shared/reader_chrome.dart | 38 +- lib/builtin/terminal/src/extension.dart | 20 +- lib/builtin/terminal/src/terminal_pane.dart | 36 +- lib/builtin/theme_picker/src/extension.dart | 45 +- .../theme_picker/src/settings_view.dart | 10 +- .../theme_picker/src/theme_status_item.dart | 14 +- lib/builtin/tickets/src/extension.dart | 34 +- lib/builtin/tickets/src/ticket_colors.dart | 22 +- .../tickets/src/ticket_detail_view.dart | 56 +- lib/builtin/tickets/src/tickets_view.dart | 76 +- lib/builtin/view/src/extension.dart | 62 +- lib/builtin/vim/src/extension.dart | 26 +- lib/builtin/vim/src/vim_mode_indicator.dart | 7 +- lib/builtin/welcome/src/extension.dart | 35 +- lib/builtin/welcome/src/welcome_view.dart | 125 ++-- lib/extension/src/contribution.dart | 48 +- lib/extension/src/extension.dart | 13 +- lib/extension/src/manifest.dart | 9 +- lib/kernel/src/cli_install.dart | 41 +- lib/kernel/src/clipboard.dart | 5 +- lib/kernel/src/commands/keybindings.dart | 4 +- lib/kernel/src/commands/registry.dart | 11 +- lib/kernel/src/dialog.dart | 17 +- lib/kernel/src/events/message_bus.dart | 6 +- lib/kernel/src/events/types.dart | 41 +- lib/kernel/src/facade.dart | 17 +- lib/kernel/src/files.dart | 15 +- lib/kernel/src/focus.dart | 6 +- lib/kernel/src/i18n/fallback_chain.dart | 12 +- lib/kernel/src/i18n/i18n.dart | 50 +- lib/kernel/src/ipc/client.dart | 58 +- lib/kernel/src/keymap/key_chord.dart | 10 +- lib/kernel/src/keymap/keymap.dart | 10 +- lib/kernel/src/keymap/keymap_service.dart | 24 +- lib/kernel/src/keymap/sequence_matcher.dart | 24 +- lib/kernel/src/log.dart | 18 +- lib/kernel/src/notify.dart | 24 +- lib/kernel/src/os.dart | 4 +- lib/kernel/src/panels/arrangement.dart | 36 +- lib/kernel/src/panels/drag_resize.dart | 14 +- lib/kernel/src/panels/layout_preset.dart | 48 +- lib/kernel/src/panels/registry.dart | 8 +- lib/kernel/src/panels/view_pane_snapshot.dart | 8 +- lib/kernel/src/project.dart | 61 +- lib/kernel/src/reader_nav.dart | 17 +- lib/kernel/src/secrets.dart | 16 +- lib/kernel/src/syntax/tree_sitter_ffi.dart | 100 +-- .../src/syntax/tree_sitter_service.dart | 58 +- lib/kernel/src/theme/contrast.dart | 210 ++---- lib/kernel/src/theme/controller.dart | 29 +- lib/kernel/src/theme/loader.dart | 5 +- lib/kernel/src/theme/resolver.dart | 14 +- lib/kernel/src/theme/semantic.dart | 14 +- lib/kernel/src/toast.dart | 24 +- lib/kernel/src/tool_check.dart | 6 +- lib/kernel/src/toolchain.dart | 6 +- lib/kernel/src/toolchain_paths.dart | 34 +- lib/lua/src/adapter.dart | 7 +- lib/main.dart | 60 +- lib/src/cli/argv_dispatch.dart | 12 +- lib/src/cli/argv_to_request.dart | 42 +- lib/src/daemon/dispatcher.dart | 51 +- lib/src/daemon/editor_commands.dart | 48 +- lib/src/daemon/files_commands.dart | 66 +- lib/src/daemon/git_commands.dart | 109 +-- lib/src/daemon/image_commands.dart | 26 +- lib/src/daemon/pane_commands.dart | 73 +- lib/src/daemon/panel_commands.dart | 31 +- lib/src/daemon/pql_commands.dart | 39 +- lib/src/daemon/search_commands.dart | 53 +- lib/src/daemon/ui_command.dart | 29 +- lib/src/editor/buffer.dart | 38 +- lib/src/editor/editor_settings.dart | 42 +- lib/src/editor/editorconfig.dart | 8 +- lib/src/editor/registry.dart | 69 +- lib/src/files/ignore.dart | 12 +- lib/src/files/listing.dart | 53 +- lib/src/files/watcher.dart | 23 +- lib/src/git/client.dart | 39 +- lib/src/git/diff.dart | 124 +--- lib/src/git/operations.dart | 136 +--- lib/src/git/status.dart | 122 +--- lib/src/ipc/command_schema.dart | 10 +- lib/src/ipc/envelope.dart | 103 +-- lib/src/ipc/errno_mapping.dart | 50 +- lib/src/ipc/mcp_server.dart | 25 +- lib/src/ipc/server.dart | 69 +- lib/src/panes/pane.dart | 31 +- lib/src/panes/registry.dart | 34 +- lib/src/panes/view_pane.dart | 18 +- lib/src/pql/client.dart | 43 +- lib/src/pty/env.dart | 18 +- lib/src/pty/ffi/libc.dart | 88 +-- lib/src/pty/native_pty.dart | 73 +- lib/src/search/grep_engine.dart | 41 +- lib/src/search/match.dart | 56 +- lib/src/search/replace_engine.dart | 25 +- lib/src/terminal/src/core/buffer/buffer.dart | 17 +- lib/src/terminal/src/core/buffer/line.dart | 9 +- .../terminal/src/core/buffer/range_block.dart | 10 +- lib/src/terminal/src/core/cell.dart | 14 +- lib/src/terminal/src/core/charset.dart | 5 +- lib/src/terminal/src/core/input/handler.dart | 6 +- lib/src/terminal/src/core/input/keys.dart | 1 - .../src/core/input/keytab/keytab.dart | 5 +- .../src/core/input/keytab/keytab_record.dart | 5 +- .../src/core/input/keytab/keytab_token.dart | 11 +- .../src/core/input/keytab/qt_keyname.dart | 650 +++++++++--------- lib/src/terminal/src/core/mouse/button.dart | 3 +- .../terminal/src/core/mouse/button_state.dart | 6 +- lib/src/terminal/src/core/mouse/handler.dart | 27 +- lib/src/terminal/src/core/mouse/mode.dart | 3 +- lib/src/terminal/src/core/mouse/reporter.dart | 12 +- lib/src/terminal/src/core/platform.dart | 18 +- lib/src/terminal/src/core/reflow.dart | 6 +- lib/src/terminal/src/terminal.dart | 58 +- lib/src/terminal/src/terminal_view.dart | 25 +- lib/src/terminal/src/ui/char_metrics.dart | 5 +- lib/src/terminal/src/ui/controller.dart | 26 +- lib/src/terminal/src/ui/cursor_type.dart | 8 +- lib/src/terminal/src/ui/custom_text_edit.dart | 26 +- .../src/ui/gesture/gesture_detector.dart | 30 +- .../src/ui/gesture/gesture_handler.dart | 43 +- .../terminal/src/ui/keyboard_listener.dart | 7 +- lib/src/terminal/src/ui/painter.dart | 57 +- lib/src/terminal/src/ui/palette_builder.dart | 6 +- lib/src/terminal/src/ui/paragraph_cache.dart | 7 +- lib/src/terminal/src/ui/pointer_input.dart | 8 +- lib/src/terminal/src/ui/render.dart | 127 +--- lib/src/terminal/src/ui/selection_mode.dart | 6 +- lib/src/terminal/src/ui/shortcut/actions.dart | 17 +- .../terminal/src/ui/terminal_text_style.dart | 15 +- .../terminal/src/utils/circular_buffer.dart | 5 +- lib/test_app.dart | 77 +-- lib/widgets/src/clide_accordion.dart | 8 +- lib/widgets/src/clide_anchored.dart | 32 +- lib/widgets/src/clide_button.dart | 10 +- lib/widgets/src/clide_code_block.dart | 19 +- lib/widgets/src/clide_collapser_card.dart | 133 ++-- lib/widgets/src/clide_column_hat.dart | 6 +- lib/widgets/src/clide_divider.dart | 12 +- lib/widgets/src/clide_file_image.dart | 4 +- lib/widgets/src/clide_icon.dart | 7 +- lib/widgets/src/clide_icon_rail.dart | 39 +- lib/widgets/src/clide_lightbox.dart | 19 +- lib/widgets/src/clide_markdown.dart | 90 +-- lib/widgets/src/clide_marquee.dart | 7 +- lib/widgets/src/clide_menu.dart | 25 +- lib/widgets/src/clide_palette.dart | 47 +- lib/widgets/src/clide_pane.dart | 8 +- lib/widgets/src/clide_pane_chrome.dart | 61 +- lib/widgets/src/clide_pty_view.dart | 15 +- lib/widgets/src/clide_scrollbar.dart | 23 +- lib/widgets/src/clide_selection_area.dart | 6 +- lib/widgets/src/clide_spine.dart | 24 +- lib/widgets/src/clide_spinner.dart | 8 +- lib/widgets/src/clide_status_indicator.dart | 5 +- lib/widgets/src/clide_surface.dart | 11 +- lib/widgets/src/clide_svg_view.dart | 4 +- lib/widgets/src/clide_tab_bar.dart | 36 +- lib/widgets/src/clide_tappable.dart | 17 +- lib/widgets/src/clide_text.dart | 7 +- lib/widgets/src/clide_toast.dart | 11 +- lib/widgets/src/clide_tooltip.dart | 19 +- lib/widgets/src/clide_typeahead.dart | 4 +- lib/widgets/src/icons/phosphor.dart | 4 +- lib/widgets/src/multitab_controller.dart | 15 +- lib/widgets/src/multitab_pane.dart | 78 +-- lib/widgets/src/quick_open_overlay.dart | 32 +- pubspec.yaml | 8 +- test/a11y/i18n_coverage_test.dart | 39 +- test/a11y/keyboard_traversal_test.dart | 4 +- test/a11y/semantic_coverage_test.dart | 7 +- test/app_collapse_toggle_test.dart | 53 +- test/app_statusbar_test.dart | 45 +- test/app_test.dart | 11 +- .../builtin/claude/activity_cluster_test.dart | 7 +- test/builtin/claude/agent_bootstrap_test.dart | 34 +- test/builtin/claude/claude_composer_test.dart | 165 ++--- test/builtin/claude/claude_config_test.dart | 57 +- .../claude/claude_meta_sidebar_test.dart | 337 ++++----- test/builtin/claude/claude_pane_test.dart | 70 +- .../claude/claude_session_host_test.dart | 38 +- test/builtin/claude/claude_status_test.dart | 15 +- .../builtin/claude/claude_task_dock_test.dart | 13 +- test/builtin/claude/clipboard_paste_test.dart | 4 +- .../claude/conversation_card_test.dart | 301 ++++---- .../claude/conversation_scroll_test.dart | 34 +- .../claude/conversation_view_test.dart | 182 +++-- .../claude/pane_context_status_test.dart | 46 +- .../claude/permission_mode_control_test.dart | 26 +- test/builtin/claude/prompt_card_test.dart | 205 +++--- .../claude/running_indicator_test.dart | 6 +- test/builtin/claude/session_index_test.dart | 61 +- .../claude/session_lifecycle_test.dart | 42 +- .../claude/session_orchestrator_test.dart | 84 +-- test/builtin/claude/session_picker_test.dart | 26 +- test/builtin/claude/session_storage_test.dart | 34 +- .../claude/stream_json_session_test.dart | 354 +++++----- test/builtin/claude/task_list_test.dart | 10 +- .../claude/team_chat_sidebar_test.dart | 184 ++--- test/builtin/claude/team_panel_host_test.dart | 11 +- test/builtin/claude/ticket_pick_up_test.dart | 14 +- .../claude/transcript_publisher_test.dart | 59 +- .../claude/transcript_reader_test.dart | 254 ++----- .../cli_install_extension_test.dart | 84 +-- .../decisions/decision_reader_test.dart | 412 +++++------ .../decisions/decisions_view_test.dart | 131 +--- .../default_layout/focus_exit_vim_test.dart | 17 +- test/builtin/default_layout/widget_test.dart | 5 +- test/builtin/diff/diff_controller_test.dart | 8 +- test/builtin/diff/diff_view_test.dart | 74 +- .../editor/editor_controller_test.dart | 273 ++++---- test/builtin/editor/editor_view_test.dart | 82 ++- .../editor/syntax_text_controller_test.dart | 37 +- test/builtin/editor/vim_edit_ops_test.dart | 6 +- test/builtin/editor/vim_editor_test.dart | 39 +- .../files/file_tree_controller_test.dart | 91 +-- .../builtin/files/file_tree_view_md_test.dart | 24 +- test/builtin/git/git_controller_test.dart | 49 +- test/builtin/ipc_status/widget_test.dart | 30 +- .../markdown/markdown_viewer_test.dart | 136 +--- test/builtin/menubar/file_actions_test.dart | 32 +- test/builtin/menubar/menu_bar_test.dart | 59 +- test/builtin/menubar/menu_model_test.dart | 50 +- test/builtin/output/output_view_test.dart | 20 +- test/builtin/pql/pql_controller_test.dart | 58 +- .../search/find_in_files_controller_test.dart | 36 +- .../search/search_panel_view_test.dart | 71 +- test/builtin/theme_picker/widget_test.dart | 72 +- test/builtin/tickets/ticket_detail_test.dart | 71 +- test/builtin/tickets/tickets_view_test.dart | 46 +- test/builtin/welcome/dialog_test.dart | 35 +- test/builtin/welcome/widget_test.dart | 82 +-- test/cli/argv_dispatch_test.dart | 40 +- test/cli/clide_cli_e2e_test.dart | 9 +- test/cli/clide_version_test.dart | 6 +- test/daemon/dispatcher_test.dart | 5 +- test/daemon/editor_commands_test.dart | 10 +- test/daemon/files_commands_test.dart | 4 +- test/daemon/git_commands_errors_test.dart | 8 +- test/daemon/git_commands_test.dart | 39 +- test/daemon/image_commands_test.dart | 17 +- test/daemon/pane_commands_test.dart | 9 +- test/daemon/panel_commands_test.dart | 5 +- test/daemon/pql_commands_errors_test.dart | 2 +- test/daemon/pql_commands_test.dart | 19 +- test/daemon/search_commands_test.dart | 7 +- test/daemon/status_command_test.dart | 8 +- test/daemon/ui_command_test.dart | 25 +- test/editor/registry_test.dart | 5 +- test/extension/src/contribution_test.dart | 17 +- .../extension/src/extension_context_test.dart | 22 +- test/extension/src/manifest_test.dart | 15 +- test/files/ignore_test.dart | 5 +- test/files/path_safety_test.dart | 15 +- test/files/walk_test.dart | 8 +- test/files/watcher_test.dart | 22 +- test/git/diff_test.dart | 24 +- test/git/operations_test.dart | 82 +-- test/git/status_test.dart | 30 +- test/goldens/clide_button_goldens_test.dart | 41 +- test/goldens/clide_icon_goldens_test.dart | 9 +- test/goldens/collapser_card_goldens_test.dart | 88 ++- .../conversation_card_goldens_test.dart | 29 +- test/goldens/flutter_test_config.dart | 5 +- test/goldens/tool_collapser_goldens_test.dart | 30 +- test/helpers/fake_ipc.dart | 16 +- test/helpers/golden_harness.dart | 8 +- test/helpers/kernel_fixture.dart | 15 +- test/helpers/widget_harness.dart | 20 +- test/ipc/command_schema_test.dart | 5 +- test/ipc/envelope_test.dart | 38 +- test/ipc/mcp_server_test.dart | 45 +- test/ipc/paths_test.dart | 10 +- test/ipc/schema_v1_test.dart | 7 +- test/ipc/server_events_pull_test.dart | 16 +- test/ipc/server_streaming_test.dart | 40 +- test/ipc/server_test.dart | 25 +- test/kernel/cli_install_test.dart | 98 +-- .../kernel/src/commands/keybindings_test.dart | 28 +- test/kernel/src/commands/palette_test.dart | 20 +- test/kernel/src/commands/registry_test.dart | 15 +- test/kernel/src/events/types_test.dart | 23 +- test/kernel/src/extensions_manager_test.dart | 247 +++---- test/kernel/src/facade_test.dart | 35 +- test/kernel/src/i18n/catalog_loader_test.dart | 16 +- test/kernel/src/i18n/i18n_test.dart | 159 ++--- test/kernel/src/ipc/client_test.dart | 11 +- .../src/keymap/editor_presets_test.dart | 18 +- test/kernel/src/keymap/key_chord_test.dart | 24 +- .../src/keymap/keymap_service_test.dart | 94 +-- .../src/keymap/shipped_presets_test.dart | 4 +- test/kernel/src/log_test.dart | 10 +- test/kernel/src/panels/drag_resize_test.dart | 216 +++--- test/kernel/src/panels/registry_test.dart | 25 +- .../src/panels/view_pane_snapshot_test.dart | 19 +- test/kernel/src/project_test.dart | Bin 12846 -> 12756 bytes test/kernel/src/services_bigger_test.dart | 29 +- test/kernel/src/services_stubs_test.dart | 41 +- test/kernel/src/settings_test.dart | 20 +- .../syntax/tree_sitter_service_fake_test.dart | 68 +- test/kernel/src/theme/controller_test.dart | 50 +- test/kernel/src/theme/loader_test.dart | 20 +- test/kernel/src/theme/mop_up_test.dart | 121 ++-- test/kernel/src/theme/resolver_test.dart | 12 +- test/kernel/src/theme_persistence_test.dart | 30 +- test/kernel/src/toolchain_paths_test.dart | 20 +- test/kernel/src/toolchain_test.dart | 16 +- test/panes/registry_test.dart | 25 +- test/pql/client_test.dart | 17 +- test/pty/env_test.dart | 15 +- test/pty/session_test.dart | 50 +- test/search/grep_engine_test.dart | 5 +- test/search/replace_engine_test.dart | 6 +- test/terminal/buffer/buffer_test.dart | 16 +- test/terminal/buffer/range_test.dart | 10 +- test/terminal/core/core_test.dart | 17 +- test/terminal/coverage_trivials_test.dart | 6 +- test/terminal/custom_text_edit_test.dart | 26 +- test/terminal/escape/parser_test.dart | 62 +- test/terminal/input/input_test.dart | 115 +--- test/terminal/mouse/mouse_test.dart | 95 +-- test/terminal/painter_bold_metrics_test.dart | 21 +- test/terminal/painter_test.dart | 124 +--- test/terminal/parser_and_scroll_test.dart | 12 +- test/terminal/render_test.dart | 38 +- test/terminal/shortcut_event_test.dart | 153 +++-- test/terminal/terminal_test.dart | 6 +- test/terminal/terminal_view_test.dart | 88 +-- test/terminal/ui/ui_pure_test.dart | 17 +- test/terminal/ui/ui_widget_test.dart | 60 +- test/widgets/clide_lightbox_test.dart | 23 +- test/widgets/clide_pane_chrome_test.dart | 32 +- test/widgets/more_widgets_test.dart | 87 +-- test/widgets/multitab_controller_test.dart | 30 +- test/widgets/multitab_pane_test.dart | 115 +--- test/widgets/quick_open_overlay_test.dart | 39 +- test/widgets/src/clide_anchored_test.dart | 16 +- test/widgets/src/clide_button_test.dart | 47 +- .../src/clide_collapser_card_test.dart | 63 +- test/widgets/src/clide_divider_test.dart | 4 +- test/widgets/src/clide_icon_test.dart | 8 +- .../src/clide_markdown_file_ref_test.dart | 58 +- test/widgets/src/clide_marquee_test.dart | 15 +- test/widgets/src/clide_menu_test.dart | 88 ++- test/widgets/src/clide_pane_test.dart | 86 +-- test/widgets/src/clide_selectable_test.dart | 33 +- .../src/clide_status_indicator_test.dart | 38 +- test/widgets/src/clide_surface_test.dart | 18 +- test/widgets/src/clide_tab_bar_test.dart | 16 +- test/widgets/src/clide_tappable_test.dart | 62 +- test/widgets/src/clide_toast_test.dart | 53 +- test/widgets/src/clide_tooltip_test.dart | 7 +- test/widgets/src/clide_typeahead_test.dart | 30 +- test/widgets/zero_coverage_widgets_test.dart | 325 ++++----- 444 files changed, 7587 insertions(+), 12849 deletions(-) create mode 100644 .fvmrc diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 00000000..66c94eab --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.44.1" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f88f3f..4fb2ec0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,15 +16,25 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ## [Unreleased] +### Changed + +- **Minimum toolchain raised to honest values.** `pubspec.yaml` now declares + Flutter `>=3.35.0` / Dart `>=3.9.0` (was `3.19.0` / `3.5.0`) — the real + minimums our deps already required (`alchemist` needs Flutter 3.32; Dart 3.9 + ships in Flutter 3.35). The exact build toolchain is pinned in `.fvmrc` + (Flutter 3.44.1). Moving to the Dart 3.9 language level reformatted the tree + to the new "tall" style and adopted two new lints (`unnecessary_underscores`, + `use_null_aware_elements`). (T-353) + ### Security - **Dependency audit + refresh.** Reviewed every pinned and transitive dependency against the GitHub Advisory Database / OSV (Pub ecosystem) — no advisory affects any current or candidate version. Refreshed the safe pins: `ffi` 2.1.3→2.2.0, `jovial_svg` 1.1.26→1.1.30 (pulls `jovial_misc` 0.10.0 + - `xml` 7.0.1), `mocktail` 1.0.4→1.0.5. Deliberately held with reasons in - `pubspec.yaml`: `markdown` (7.3.1 needs Dart 3.9), `alchemist` (0.13 golden - churn), `test` (Flutter-SDK locked). (T-353) + `xml` 7.0.1), `mocktail` 1.0.4→1.0.5, and `markdown` 7.2.2→7.3.1 (unblocked by + the Dart 3.9 floor below). Deliberately held with reasons in `pubspec.yaml`: + `alchemist` (0.13 golden churn), `test` (Flutter-SDK locked). (T-353) - **Supply-chain gate (`make security`).** A new `security` target runs `osv-scanner` over `pubspec.lock` and fails if any resolved dependency has a known advisory — a hard gate on top of `dart pub`'s passive, non-failing diff --git a/docs/claude-design/themes/clide_theme.dart b/docs/claude-design/themes/clide_theme.dart index 695226fb..309caa96 100644 --- a/docs/claude-design/themes/clide_theme.dart +++ b/docs/claude-design/themes/clide_theme.dart @@ -71,112 +71,112 @@ class ClideTheme { // ─── ThemeData ──────────────────────────────────────────────────────── static ThemeData get data => ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - scaffoldBackgroundColor: _bg, - canvasColor: _bg, + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: _bg, + canvasColor: _bg, - colorScheme: const ColorScheme.dark( - brightness: Brightness.dark, - primary: _accent, - onPrimary: Color(0xFF0D1020), - secondary: _synKeyword, - onSecondary: Color(0xFF0D1020), - surface: _surface, - onSurface: _textHi, - surfaceContainerHighest: _surfaceHi, - outline: _border, - outlineVariant: _borderHi, - error: _err, - onError: Color(0xFF0D1020), - ), + colorScheme: const ColorScheme.dark( + brightness: Brightness.dark, + primary: _accent, + onPrimary: Color(0xFF0D1020), + secondary: _synKeyword, + onSecondary: Color(0xFF0D1020), + surface: _surface, + onSurface: _textHi, + surfaceContainerHighest: _surfaceHi, + outline: _border, + outlineVariant: _borderHi, + error: _err, + onError: Color(0xFF0D1020), + ), - textTheme: const TextTheme( - // Josefin Sans Light for display; JetBrains Mono for code/body. - displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, height: 1.1, letterSpacing: 0.2, color: _textHi), - displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, height: 1.15, letterSpacing: 0.2, color: _textHi), - headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, height: 1.2, letterSpacing: 0.2, color: _textHi), - titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, height: 1.3, letterSpacing: 0.3, color: _text), - labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, height: 1.2, letterSpacing: 1.0, color: _textMute), - bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), - bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, height: 1.4, color: _text), - ), + textTheme: const TextTheme( + // Josefin Sans Light for display; JetBrains Mono for code/body. + displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, height: 1.1, letterSpacing: 0.2, color: _textHi), + displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, height: 1.15, letterSpacing: 0.2, color: _textHi), + headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, height: 1.2, letterSpacing: 0.2, color: _textHi), + titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, height: 1.3, letterSpacing: 0.3, color: _text), + labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, height: 1.2, letterSpacing: 1.0, color: _textMute), + bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), + bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, height: 1.4, color: _text), + ), - dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), + dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), - cardTheme: CardThemeData( - color: _surface, - elevation: 0, - shape: RoundedRectangleBorder( - side: const BorderSide(color: _border), - borderRadius: BorderRadius.circular(6), - ), - margin: EdgeInsets.zero, - ), + cardTheme: CardThemeData( + color: _surface, + elevation: 0, + shape: RoundedRectangleBorder( + side: const BorderSide(color: _border), + borderRadius: BorderRadius.circular(6), + ), + margin: EdgeInsets.zero, + ), - inputDecorationTheme: InputDecorationTheme( - isDense: true, - filled: true, - fillColor: _bgSunken, - hintStyle: const TextStyle(color: _textMute), - contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: const BorderSide(color: _border), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: const BorderSide(color: _border), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - borderSide: const BorderSide(color: _accent, width: 1.5), - ), - ), + inputDecorationTheme: InputDecorationTheme( + isDense: true, + filled: true, + fillColor: _bgSunken, + hintStyle: const TextStyle(color: _textMute), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide(color: _border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide(color: _border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide(color: _accent, width: 1.5), + ), + ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: _accent, - foregroundColor: const Color(0xFF0D1020), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12, fontWeight: FontWeight.w500), - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: _accent, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12), - ), - ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: _accent, + foregroundColor: const Color(0xFF0D1020), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12, fontWeight: FontWeight.w500), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: _accent, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 12), + ), + ), - // Pill-style chips (matches the Projects row in the dashboard). - chipTheme: ChipThemeData( - backgroundColor: _surface, - side: const BorderSide(color: _borderHi), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - labelStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _text), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - ), + // Pill-style chips (matches the Projects row in the dashboard). + chipTheme: ChipThemeData( + backgroundColor: _surface, + side: const BorderSide(color: _borderHi), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + labelStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _text), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + ), - iconTheme: const IconThemeData(color: _textDim, size: 14), + iconTheme: const IconThemeData(color: _textDim, size: 14), - tooltipTheme: TooltipThemeData( - decoration: BoxDecoration( - color: _surfaceHi, - border: Border.all(color: _borderHi), - borderRadius: BorderRadius.circular(3), - ), - textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _textHi), - ), + tooltipTheme: TooltipThemeData( + decoration: BoxDecoration( + color: _surfaceHi, + border: Border.all(color: _borderHi), + borderRadius: BorderRadius.circular(3), + ), + textStyle: const TextStyle(fontFamily: 'JetBrains Mono', fontSize: 11, color: _textHi), + ), - scrollbarTheme: ScrollbarThemeData( - thumbColor: WidgetStatePropertyAll(_border), - thickness: const WidgetStatePropertyAll(6), - radius: const Radius.circular(3), - ), - ); + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStatePropertyAll(_border), + thickness: const WidgetStatePropertyAll(6), + radius: const Radius.circular(3), + ), + ); } /// Raw color tokens — use when Material widgets can't carry the meaning. diff --git a/docs/claude-design/themes/midnight_theme.dart b/docs/claude-design/themes/midnight_theme.dart index 4d396f8e..1d0a6c59 100644 --- a/docs/claude-design/themes/midnight_theme.dart +++ b/docs/claude-design/themes/midnight_theme.dart @@ -63,33 +63,33 @@ class MidnightTheme { ); static ThemeData get data => ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - scaffoldBackgroundColor: _bg, - canvasColor: _bg, - colorScheme: const ColorScheme.dark( - primary: _accent, - onPrimary: Color(0xFF0B1220), - secondary: _synKeyword, - onSecondary: Color(0xFF0B1220), - surface: _surface, - onSurface: _textHi, - surfaceContainerHighest: _surfaceHi, - outline: _border, - outlineVariant: _borderHi, - error: _err, - onError: Color(0xFF0B1220), - ), - textTheme: const TextTheme( - displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), - displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), - headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), - titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), - labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute), - bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), - bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), - ), - dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), - iconTheme: const IconThemeData(color: _textDim, size: 14), - ); + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: _bg, + canvasColor: _bg, + colorScheme: const ColorScheme.dark( + primary: _accent, + onPrimary: Color(0xFF0B1220), + secondary: _synKeyword, + onSecondary: Color(0xFF0B1220), + surface: _surface, + onSurface: _textHi, + surfaceContainerHighest: _surfaceHi, + outline: _border, + outlineVariant: _borderHi, + error: _err, + onError: Color(0xFF0B1220), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), + displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), + headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), + titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), + labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute), + bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), + bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), + ), + dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), + iconTheme: const IconThemeData(color: _textDim, size: 14), + ); } diff --git a/docs/claude-design/themes/paper_theme.dart b/docs/claude-design/themes/paper_theme.dart index ea199823..6cca5e41 100644 --- a/docs/claude-design/themes/paper_theme.dart +++ b/docs/claude-design/themes/paper_theme.dart @@ -65,33 +65,33 @@ class PaperTheme { ); static ThemeData get data => ThemeData( - useMaterial3: true, - brightness: Brightness.light, - scaffoldBackgroundColor: _bg, - canvasColor: _bg, - colorScheme: const ColorScheme.light( - primary: _accent, - onPrimary: Color(0xFFFBF8F1), - secondary: _info, - onSecondary: Color(0xFFFBF8F1), - surface: _surface, - onSurface: _textHi, - surfaceContainerHighest: _surfaceHi, - outline: _border, - outlineVariant: _borderHi, - error: _err, - onError: Color(0xFFFBF8F1), - ), - textTheme: const TextTheme( - displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), - displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), - headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), - titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), - labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textDim), - bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), - bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), - ), - dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), - iconTheme: const IconThemeData(color: _text, size: 14), - ); + useMaterial3: true, + brightness: Brightness.light, + scaffoldBackgroundColor: _bg, + canvasColor: _bg, + colorScheme: const ColorScheme.light( + primary: _accent, + onPrimary: Color(0xFFFBF8F1), + secondary: _info, + onSecondary: Color(0xFFFBF8F1), + surface: _surface, + onSurface: _textHi, + surfaceContainerHighest: _surfaceHi, + outline: _border, + outlineVariant: _borderHi, + error: _err, + onError: Color(0xFFFBF8F1), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), + displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), + headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), + titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), + labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textDim), + bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), + bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), + ), + dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), + iconTheme: const IconThemeData(color: _text, size: 14), + ); } diff --git a/docs/claude-design/themes/terminal_theme.dart b/docs/claude-design/themes/terminal_theme.dart index fe0fb7dc..7e719639 100644 --- a/docs/claude-design/themes/terminal_theme.dart +++ b/docs/claude-design/themes/terminal_theme.dart @@ -65,34 +65,34 @@ class TerminalTheme { ); static ThemeData get data => ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - scaffoldBackgroundColor: _bg, - canvasColor: _bg, - colorScheme: const ColorScheme.dark( - primary: _accent, - onPrimary: Color(0xFF000000), - secondary: _info, - onSecondary: Color(0xFF000000), - surface: _surface, - onSurface: _textHi, - surfaceContainerHighest: _surfaceHi, - outline: _border, - outlineVariant: _borderHi, - error: _err, - onError: Color(0xFF000000), - ), - textTheme: const TextTheme( - displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), - displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), - headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), - titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), - // Terminal mockups go full-mono even for display. - labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute), - bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), - bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), - ), - dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), - iconTheme: const IconThemeData(color: _textDim, size: 14), - ); + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: _bg, + canvasColor: _bg, + colorScheme: const ColorScheme.dark( + primary: _accent, + onPrimary: Color(0xFF000000), + secondary: _info, + onSecondary: Color(0xFF000000), + surface: _surface, + onSurface: _textHi, + surfaceContainerHighest: _surfaceHi, + outline: _border, + outlineVariant: _borderHi, + error: _err, + onError: Color(0xFF000000), + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 48, color: _textHi), + displayMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w300, fontSize: 34, color: _textHi), + headlineSmall: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 20, color: _textHi), + titleMedium: TextStyle(fontFamily: 'Josefin Sans', fontWeight: FontWeight.w400, fontSize: 14, color: _text), + // Terminal mockups go full-mono even for display. + labelSmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w500, fontSize: 10, letterSpacing: 1.0, color: _textMute), + bodyMedium: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 12, height: 1.45, color: _textHi), + bodySmall: TextStyle(fontFamily: 'JetBrains Mono', fontWeight: FontWeight.w400, fontSize: 11, color: _text), + ), + dividerTheme: const DividerThemeData(color: _border, thickness: 1, space: 1), + iconTheme: const IconThemeData(color: _textDim, size: 14), + ); } diff --git a/docs/claude-design/tokens/clide_tokens.dart b/docs/claude-design/tokens/clide_tokens.dart index a4e42322..85e10c50 100644 --- a/docs/claude-design/tokens/clide_tokens.dart +++ b/docs/claude-design/tokens/clide_tokens.dart @@ -10,13 +10,7 @@ import 'dart:ui' show Color; class ClideTheme { - const ClideTheme({ - required this.name, - required this.dark, - required this.subtitle, - required this.palette, - required this.syntax, - }); + const ClideTheme({required this.name, required this.dark, required this.subtitle, required this.palette, required this.syntax}); final String name; final bool dark; final String subtitle; diff --git a/integration_test/app_starts_test.dart b/integration_test/app_starts_test.dart index 72f99df1..d8a80bc3 100644 --- a/integration_test/app_starts_test.dart +++ b/integration_test/app_starts_test.dart @@ -33,23 +33,13 @@ void main() { tester.view.resetDevicePixelRatio(); }); - final themes = [ - await const ThemeLoader().fromAsset( - rootBundle, - 'lib/kernel/src/theme/themes/summer-night.yaml', - ), - ]; + final themes = [await const ThemeLoader().fromAsset(rootBundle, 'lib/kernel/src/theme/themes/summer-night.yaml')]; final services = await KernelServices.boot( appDir: await Directory.systemTemp.createTemp('clide_intg_'), bundledThemes: themes, i18nLoader: AssetCatalogLoader(bundle: rootBundle), - preloadNamespaces: const [ - 'builtin.welcome', - 'builtin.ipc-status', - 'builtin.theme-picker', - 'builtin.default-layout', - ], - daemonClientFactory: (log, events, _, __) => FakeDaemonClient(log: log, events: events), + preloadNamespaces: const ['builtin.welcome', 'builtin.ipc-status', 'builtin.theme-picker', 'builtin.default-layout'], + daemonClientFactory: (log, events, _, _) => FakeDaemonClient(log: log, events: events), autoStartDaemonClient: false, ); services.extensions diff --git a/integration_test/extension_lifecycle_test.dart b/integration_test/extension_lifecycle_test.dart index 80f7215f..c684825a 100644 --- a/integration_test/extension_lifecycle_test.dart +++ b/integration_test/extension_lifecycle_test.dart @@ -25,22 +25,13 @@ void main() { tester.view.resetDevicePixelRatio(); }); - final themes = [ - await const ThemeLoader().fromAsset( - rootBundle, - 'lib/kernel/src/theme/themes/summer-night.yaml', - ), - ]; + final themes = [await const ThemeLoader().fromAsset(rootBundle, 'lib/kernel/src/theme/themes/summer-night.yaml')]; final services = await KernelServices.boot( appDir: await Directory.systemTemp.createTemp('clide_lc_'), bundledThemes: themes, i18nLoader: AssetCatalogLoader(bundle: rootBundle), - preloadNamespaces: const [ - 'builtin.welcome', - 'builtin.ipc-status', - 'builtin.default-layout', - ], - daemonClientFactory: (log, events, _, __) => FakeDaemonClient(log: log, events: events), + preloadNamespaces: const ['builtin.welcome', 'builtin.ipc-status', 'builtin.default-layout'], + daemonClientFactory: (log, events, _, _) => FakeDaemonClient(log: log, events: events), autoStartDaemonClient: false, ); services.extensions diff --git a/integration_test/theme_picker_test.dart b/integration_test/theme_picker_test.dart index dff1791d..e1abaa08 100644 --- a/integration_test/theme_picker_test.dart +++ b/integration_test/theme_picker_test.dart @@ -16,22 +16,13 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('theme.pick command opens modal; selecting dismisses it', (tester) async { - final themes = [ - await const ThemeLoader().fromAsset( - rootBundle, - 'lib/kernel/src/theme/themes/summer-night.yaml', - ), - ]; + final themes = [await const ThemeLoader().fromAsset(rootBundle, 'lib/kernel/src/theme/themes/summer-night.yaml')]; final services = await KernelServices.boot( appDir: await Directory.systemTemp.createTemp('clide_theme_intg_'), bundledThemes: themes, i18nLoader: AssetCatalogLoader(bundle: rootBundle), - preloadNamespaces: const [ - 'builtin.welcome', - 'builtin.theme-picker', - 'builtin.default-layout', - ], - daemonClientFactory: (log, events, _, __) => FakeDaemonClient(log: log, events: events), + preloadNamespaces: const ['builtin.welcome', 'builtin.theme-picker', 'builtin.default-layout'], + daemonClientFactory: (log, events, _, _) => FakeDaemonClient(log: log, events: events), autoStartDaemonClient: false, ); services.extensions diff --git a/lib/app.dart b/lib/app.dart index 72891114..a192b20b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -38,10 +38,7 @@ class _AppRoot extends StatelessWidget { debugShowCheckedModeBanner: false, title: clideName, color: const Color(0xFF000000), - pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder( - settings: settings, - pageBuilder: (ctx, _, __) => builder(ctx), - ), + pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder(settings: settings, pageBuilder: (ctx, _, _) => builder(ctx)), home: _RootShell(services: services), ); } @@ -240,35 +237,19 @@ class RootLayout extends StatelessWidget { child: Row( children: [ if (sidebarVisible && sidebarCollapsed) - ClideSpine( - label: _sidebarSpineLabel(kernel), - side: SpineSide.left, - onExpand: () => a.setCollapsed(Slots.sidebar, false), - ) + ClideSpine(label: _sidebarSpineLabel(kernel), side: SpineSide.left, onExpand: () => a.setCollapsed(Slots.sidebar, false)) else if (sidebarVisible) ...[ SizedBox( width: sidebarSize, child: SlotHost(slot: Slots.sidebar), ), - DragResizeHandle( - arrangement: a, - slot: Slots.sidebar, - axis: Axis.horizontal, - ), + DragResizeHandle(arrangement: a, slot: Slots.sidebar, axis: Axis.horizontal), ], const Expanded(child: SlotHost(slot: Slots.workspace)), if (contextVisible && contextCollapsed) - ClideSpine( - label: 'context', - side: SpineSide.right, - onExpand: () => a.setCollapsed(Slots.contextPanel, false), - ) + ClideSpine(label: 'context', side: SpineSide.right, onExpand: () => a.setCollapsed(Slots.contextPanel, false)) else if (contextVisible) ...[ - DragResizeHandle( - arrangement: a, - slot: Slots.contextPanel, - axis: Axis.horizontal, - ), + DragResizeHandle(arrangement: a, slot: Slots.contextPanel, axis: Axis.horizontal), SizedBox( width: contextSize, child: SlotHost(slot: Slots.contextPanel), @@ -281,14 +262,18 @@ class RootLayout extends StatelessWidget { SizedBox( height: dockHeight, child: DecoratedBox( - decoration: BoxDecoration(border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.chromeBorder))), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.chromeBorder)), + ), child: SlotHost(slot: Slots.dock), ), ), if (statusVisible) Container( height: statusHeight, - decoration: BoxDecoration(border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.chromeBorder))), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: ClideTheme.of(ctx).surface.chromeBorder)), + ), child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -296,12 +281,18 @@ class RootLayout extends StatelessWidget { // children) so they never shift when a pane collapses (T-294). StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: sidebarCollapsed, visible: sidebarVisible), if (sidebarVisible && !sidebarCollapsed) - SizedBox(width: sidebarSize, child: _BottomRail(slot: Slots.sidebar)) + SizedBox( + width: sidebarSize, + child: _BottomRail(slot: Slots.sidebar), + ) else if (sidebarVisible && sidebarCollapsed) const SizedBox(width: ClideSpine.width), const Expanded(child: StatusbarHost()), if (contextVisible && !contextCollapsed) - SizedBox(width: contextSize, child: _BottomRail(slot: Slots.contextPanel)) + SizedBox( + width: contextSize, + child: _BottomRail(slot: Slots.contextPanel), + ) else if (contextVisible && contextCollapsed) const SizedBox(width: ClideSpine.width), StatusbarCollapseToggle(slot: Slots.contextPanel, collapsed: contextCollapsed, visible: contextVisible), @@ -392,11 +383,13 @@ class _RightHatContent extends StatelessWidget { Widget build(BuildContext context) { if (kIsWeb) return const SizedBox.shrink(); if (!kIsWeb && Platform.isMacOS) return const SizedBox.shrink(); - return Row(children: [ - _WinBtn(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens), - _WinBtn(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens), - _WinBtn(icon: PhosphorIcons.byName('x'), onTap: wc.close, tokens: tokens, isClose: true), - ]); + return Row( + children: [ + _WinBtn(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens), + _WinBtn(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens), + _WinBtn(icon: PhosphorIcons.byName('x'), onTap: wc.close, tokens: tokens, isClose: true), + ], + ); } } @@ -417,11 +410,7 @@ class _WinBtn extends StatelessWidget { height: hatHeight, color: hovered ? hoverBg : null, alignment: Alignment.center, - child: ClideIcon( - icon, - size: 14, - color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.chromeForeground, - ), + child: ClideIcon(icon, size: 14, color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.chromeForeground), ), ); } @@ -544,26 +533,29 @@ class _ProjectSwitcherDropdownState extends State<_ProjectSwitcherDropdown> { shrinkWrap: true, padding: EdgeInsets.zero, itemCount: filtered.length, - itemBuilder: (ctx, i) => _RecentProjectRow( - project: filtered[i], - tokens: tokens, - onTap: () => _openProject(filtered[i].path), - ), + itemBuilder: (ctx, i) => _RecentProjectRow(project: filtered[i], tokens: tokens, onTap: () => _openProject(filtered[i].path)), ), ), ] else const Padding(padding: EdgeInsets.all(12), child: ClideText('No recent projects.', muted: true)), Container( - decoration: BoxDecoration(border: Border(top: BorderSide(color: tokens.dividerColor))), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: tokens.dividerColor)), + ), child: Column( children: [ _ActionRow( - label: 'Open Local Project', - shortcut: Platform.isMacOS ? '⌘O' : 'Ctrl+O', - tokens: tokens, - onTap: () => _runFileCommand('file.openFolder')), + label: 'Open Local Project', + shortcut: Platform.isMacOS ? '⌘O' : 'Ctrl+O', + tokens: tokens, + onTap: () => _runFileCommand('file.openFolder'), + ), _ActionRow( - label: 'New Window', shortcut: Platform.isMacOS ? '⌘⇧N' : 'Ctrl+Shift+N', tokens: tokens, onTap: () => _runFileCommand('file.newWindow')), + label: 'New Window', + shortcut: Platform.isMacOS ? '⌘⇧N' : 'Ctrl+Shift+N', + tokens: tokens, + onTap: () => _runFileCommand('file.newWindow'), + ), if (widget.kernel.project.isOpen) _ActionRow(label: 'Close Project', shortcut: '', tokens: tokens, onTap: () => _runFileCommand('file.closeWorkspace')), ], @@ -604,8 +596,14 @@ class _RecentProjectRow extends StatelessWidget { // Elide a long path instead of overflowing the row // (matches the welcome recents row; T-160 discipline). Flexible( - child: ClideText(project.relativePath, - muted: true, fontSize: 12, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis), + child: ClideText( + project.relativePath, + muted: true, + fontSize: 12, + fontFamily: clideMonoFamily, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ClideText(' · ', muted: true, fontSize: 12), ClideIcon(PhosphorIcons.byName('git-branch'), size: 10, color: tokens.globalTextMuted), @@ -706,10 +704,7 @@ class _SlotHostState extends State { return Container(color: tokens.panelBackground); } final activeId = kernel.panels.activeTabIn(widget.slot) ?? tabs.first.id; - final active = tabs.firstWhere( - (t) => t.id == activeId, - orElse: () => tabs.first, - ); + final active = tabs.firstWhere((t) => t.id == activeId, orElse: () => tabs.first); return _SlotBody(slot: widget.slot, tabs: tabs, active: active, activeId: activeId); }, ), @@ -731,21 +726,11 @@ class _SlotBody extends StatelessWidget { final tokens = ClideTheme.of(context).surface; if (slot == Slots.sidebar) { - return _SidebarSlot( - tabs: tabs, - active: active, - activeId: activeId, - onSelect: (id) => kernel.panels.activateTab(slot, id), - ); + return _SidebarSlot(tabs: tabs, active: active, activeId: activeId, onSelect: (id) => kernel.panels.activateTab(slot, id)); } if (slot == Slots.contextPanel) { - return _ContextSlot( - tabs: tabs, - active: active, - activeId: activeId, - onSelect: (id) => kernel.panels.activateTab(slot, id), - ); + return _ContextSlot(tabs: tabs, active: active, activeId: activeId, onSelect: (id) => kernel.panels.activateTab(slot, id)); } if (slot == Slots.workspace) { @@ -757,9 +742,7 @@ class _SlotBody extends StatelessWidget { child: Column( children: [ ClideTabBar( - items: [ - for (final t in tabs) ClideTabItem(id: t.id, title: _resolveTitle(context, t)), - ], + items: [for (final t in tabs) ClideTabItem(id: t.id, title: _resolveTitle(context, t))], activeId: active.id, onSelect: (id) => kernel.panels.activateTab(slot, id), ), @@ -774,21 +757,12 @@ class _SlotBody extends StatelessWidget { final key = t.titleKey; final ns = t.i18nNamespace; if (key == null || ns == null) return t.title; - return ClideKernel.of(context).i18n.string( - key, - namespace: ns, - placeholder: t.title, - ); + return ClideKernel.of(context).i18n.string(key, namespace: ns, placeholder: t.title); } } class _SidebarSlot extends StatelessWidget { - const _SidebarSlot({ - required this.tabs, - required this.active, - required this.activeId, - required this.onSelect, - }); + const _SidebarSlot({required this.tabs, required this.active, required this.activeId, required this.onSelect}); final List tabs; final TabContribution active; @@ -863,10 +837,7 @@ class _WorkspaceSlot extends StatelessWidget { SizedBox( height: topHeight, child: reveal != null - ? _RevealedTab( - tab: reveal, - onClose: () => kernel.panels.activateTab(Slots.workspace, _claudeTabId), - ) + ? _RevealedTab(tab: reveal, onClose: () => kernel.panels.activateTab(Slots.workspace, _claudeTabId)) : topTab.build(ctx), ), _EditorDragHandle(arrangement: kernel.arrangement, totalHeight: totalHeight), @@ -903,12 +874,7 @@ class _RevealedTab extends StatelessWidget { child: Row( children: [ Expanded( - child: ClideText( - _SlotBody._resolveTitle(context, tab), - fontSize: clideFontCaption, - color: tokens.panelHeaderForeground, - maxLines: 1, - ), + child: ClideText(_SlotBody._resolveTitle(context, tab), fontSize: clideFontCaption, color: tokens.panelHeaderForeground, maxLines: 1), ), Semantics( button: true, @@ -918,7 +884,7 @@ class _RevealedTab extends StatelessWidget { child: ClideTappable( onTap: onClose, tooltip: 'Close', - builder: (_, hovered, __) => Padding( + builder: (_, hovered, _) => Padding( padding: const EdgeInsets.all(6), child: ClideIcon(PhosphorIcons.byName('x'), size: 12, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), @@ -1027,12 +993,7 @@ class _EditorBumpIntent extends Intent { } class _ContextSlot extends StatelessWidget { - const _ContextSlot({ - required this.tabs, - required this.active, - required this.activeId, - required this.onSelect, - }); + const _ContextSlot({required this.tabs, required this.active, required this.activeId, required this.onSelect}); final List tabs; final TabContribution active; @@ -1042,12 +1003,7 @@ class _ContextSlot extends StatelessWidget { @override Widget build(BuildContext context) { final tokens = ClideTheme.of(context).surface; - return Container( - color: tokens.panelBackground, - alignment: Alignment.topLeft, - padding: const EdgeInsets.only(right: 2), - child: active.build(context), - ); + return Container(color: tokens.panelBackground, alignment: Alignment.topLeft, padding: const EdgeInsets.only(right: 2), child: active.build(context)); } } @@ -1068,14 +1024,7 @@ class _BottomRail extends StatelessWidget { return Container( color: tokens.chromeBackground, child: ClideIconRail( - items: [ - for (final t in tabs) - ClideIconRailItem( - id: t.id, - icon: _iconFor(slot, t), - tooltip: _SlotBody._resolveTitle(ctx, t), - ), - ], + items: [for (final t in tabs) ClideIconRailItem(id: t.id, icon: _iconFor(slot, t), tooltip: _SlotBody._resolveTitle(ctx, t))], activeId: activeId, onSelect: (id) => kernel.panels.activateTab(slot, id), ), @@ -1209,10 +1158,7 @@ class _WelcomeOverlay extends StatelessWidget { builder: (ctx, _) { if (kernel.project.isOpen) return const SizedBox.shrink(); final tokens = ClideTheme.of(ctx).surface; - return ColoredBox( - color: tokens.globalBackground, - child: const WelcomeView(), - ); + return ColoredBox(color: tokens.globalBackground, child: const WelcomeView()); }, ); } diff --git a/lib/builtin/claude/src/agent_bootstrap.dart b/lib/builtin/claude/src/agent_bootstrap.dart index 99af464c..c69a684f 100644 --- a/lib/builtin/claude/src/agent_bootstrap.dart +++ b/lib/builtin/claude/src/agent_bootstrap.dart @@ -42,7 +42,8 @@ const List clideAllowedToolsArgs = ['--allowedTools', clideBashAllowRule /// the orient-snapshot (`clide status`) and live pane/editor reflection /// arrive with Epic C (T-218..T-221) and are deliberately left out so the /// note never points the agent at a command that returns nothing yet. -String clideContextNote(String workspaceRoot) => 'You are running inside clide, an IDE that is hosting this session. clide exposes its IDE ' +String clideContextNote(String workspaceRoot) => + 'You are running inside clide, an IDE that is hosting this session. clide exposes its IDE ' 'surface as a `clide` command on your PATH; drive it with `clide `. ' 'Subsystems that respond today: `files` (workspace tree — `clide files root`, `files list`), ' '`editor` (`clide editor open `, `editor active`), `git` (`clide git status`), ' @@ -61,16 +62,8 @@ String clideContextNote(String workspaceRoot) => 'You are running inside clide, /// * `CLIDE_WORKSPACE` — the workspace root. /// * `PATH` — prepended with [clideCliDir] when it is non-null (i.e. `clide` /// is not already resolvable), otherwise left untouched. -Map agentEnvDelta({ - required String workspaceRoot, - required String socketPath, - required String? currentPath, - required String? clideCliDir, -}) { - final delta = { - 'CLIDE_SOCK': socketPath, - 'CLIDE_WORKSPACE': workspaceRoot, - }; +Map agentEnvDelta({required String workspaceRoot, required String socketPath, required String? currentPath, required String? clideCliDir}) { + final delta = {'CLIDE_SOCK': socketPath, 'CLIDE_WORKSPACE': workspaceRoot}; if (clideCliDir != null && clideCliDir.isNotEmpty) { delta['PATH'] = (currentPath == null || currentPath.isEmpty) ? clideCliDir : '$clideCliDir:$currentPath'; } @@ -85,11 +78,7 @@ Map agentEnvDelta({ /// /// [candidateDirs] is an ordered fallback list; [isExecutableFile] probes /// `/clide`. Both are injected so the resolver is pure and testable. -String? resolveClideCliDir({ - required String? currentPath, - required List candidateDirs, - required bool Function(String path) isExecutableFile, -}) { +String? resolveClideCliDir({required String? currentPath, required List candidateDirs, required bool Function(String path) isExecutableFile}) { if (currentPath != null) { for (final dir in currentPath.split(':')) { if (dir.isNotEmpty && isExecutableFile('$dir/clide')) return null; @@ -142,21 +131,9 @@ AgentBootstrap agentBootstrap(String workspaceRoot, {Map? base}) '$workspaceRoot/native/${nativeClideDirName()}', File(Platform.resolvedExecutable).parent.path, ]; - final cliDir = resolveClideCliDir( - currentPath: currentPath, - candidateDirs: candidates, - isExecutableFile: _isExecutableFile, - ); - final delta = agentEnvDelta( - workspaceRoot: workspaceRoot, - socketPath: workspaceSocketPath(workspaceRoot), - currentPath: currentPath, - clideCliDir: cliDir, - ); - return AgentBootstrap( - envDelta: {...?base, ...delta}, - extraArgs: ['--allowedTools', clideBashAllowRule], - ); + final cliDir = resolveClideCliDir(currentPath: currentPath, candidateDirs: candidates, isExecutableFile: _isExecutableFile); + final delta = agentEnvDelta(workspaceRoot: workspaceRoot, socketPath: workspaceSocketPath(workspaceRoot), currentPath: currentPath, clideCliDir: cliDir); + return AgentBootstrap(envDelta: {...?base, ...delta}, extraArgs: ['--allowedTools', clideBashAllowRule]); } bool _isExecutableFile(String path) { diff --git a/lib/builtin/claude/src/claude_banner.dart b/lib/builtin/claude/src/claude_banner.dart index 00046c0f..0ea8948c 100644 --- a/lib/builtin/claude/src/claude_banner.dart +++ b/lib/builtin/claude/src/claude_banner.dart @@ -35,20 +35,12 @@ class ClaudeBanner extends StatelessWidget { children: [ const ClideSvgView.asset('assets/logo/logo.svg', width: 60, height: 60), const SizedBox(height: 18), - ClideText( - 'Claude', - fontSize: clideFontDialogTitle, - color: claudeAccent, - fontWeight: FontWeight.w500, - ), + ClideText('Claude', fontSize: clideFontDialogTitle, color: claudeAccent, fontWeight: FontWeight.w500), const SizedBox(height: 2), ClideText(role, fontSize: clideFontSmall, muted: true, fontFamily: clideMonoFamily), const SizedBox(height: 16), if (ws != null) ClideText(ws, fontSize: clideFontCaption, muted: true), - if (statusLine != null) ...[ - const SizedBox(height: 2), - ClideText(statusLine!, fontSize: clideFontSmall, muted: true, fontFamily: clideMonoFamily), - ], + if (statusLine != null) ...[const SizedBox(height: 2), ClideText(statusLine!, fontSize: clideFontSmall, muted: true, fontFamily: clideMonoFamily)], const SizedBox(height: 16), const ClideText('Warming up — your conversation will appear here.', fontSize: clideFontSmall, muted: true), ], diff --git a/lib/builtin/claude/src/claude_composer.dart b/lib/builtin/claude/src/claude_composer.dart index b7fdf909..ea2987d7 100644 --- a/lib/builtin/claude/src/claude_composer.dart +++ b/lib/builtin/claude/src/claude_composer.dart @@ -352,7 +352,12 @@ class _ClaudeComposerState extends State { } } - void _applyHistory(String text) => _applyValue(TextEditingValue(text: text, selection: TextSelection.collapsed(offset: text.length))); + void _applyHistory(String text) => _applyValue( + TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ), + ); /// Set the controller without it being treated as a user edit (so the /// preview doesn't overwrite the persisted draft or exit navigation). @@ -368,10 +373,7 @@ class _ClaudeComposerState extends State { final tokens = _attachments.map((a) => a.pathToken); if (text.trim().isEmpty && _attachments.isEmpty) return; // Typed text first, then the attachment @path references. - final message = [ - if (text.trim().isNotEmpty) text, - ...tokens, - ].join(' '); + final message = [if (text.trim().isNotEmpty) text, ...tokens].join(' '); widget.onSubmit(message); _controller.clear(); setState(() => _attachments.clear()); @@ -454,11 +456,7 @@ class _ClaudeComposerState extends State { if (_attachments.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 8), - child: Wrap( - spacing: 6, - runSpacing: 6, - children: [for (final a in _attachments) _chip(theme, a)], - ), + child: Wrap(spacing: 6, runSpacing: 6, children: [for (final a in _attachments) _chip(theme, a)]), ), Row( crossAxisAlignment: CrossAxisAlignment.end, @@ -489,13 +487,7 @@ class _ClaudeComposerState extends State { }, child: Stack( children: [ - if (!hasText) - Positioned( - left: 0, - top: 0, - right: 0, - child: ClideText(widget.hint, muted: true, fontSize: clideFontBody), - ), + if (!hasText) Positioned(left: 0, top: 0, right: 0, child: ClideText(widget.hint, muted: true, fontSize: clideFontBody)), EditableText( controller: _controller, focusNode: _focus, @@ -516,10 +508,7 @@ class _ClaudeComposerState extends State { const SizedBox(width: 8), Padding( padding: const EdgeInsets.only(bottom: 2), - child: PermissionModeControl( - mode: widget.permissionMode!, - onSelect: widget.onSetPermissionMode!, - ), + child: PermissionModeControl(mode: widget.permissionMode!, onSelect: widget.onSetPermissionMode!), ), ], ], @@ -548,12 +537,7 @@ class _ClaudeComposerState extends State { _chipLeading(theme, a), const SizedBox(width: 6), Flexible( - child: ClideText( - a.fileName, - fontSize: clideFontSmall, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + child: ClideText(a.fileName, fontSize: clideFontSmall, maxLines: 1, overflow: TextOverflow.ellipsis), ), const SizedBox(width: 4), Semantics( diff --git a/lib/builtin/claude/src/claude_config.dart b/lib/builtin/claude/src/claude_config.dart index 7e2d9218..36805f25 100644 --- a/lib/builtin/claude/src/claude_config.dart +++ b/lib/builtin/claude/src/claude_config.dart @@ -94,13 +94,7 @@ class ClaudePermissions { /// + plugin + MCP), the skill names, the default model and permission mode. @immutable class ClaudeProbe { - const ClaudeProbe({ - required this.version, - required this.slashCommands, - required this.skills, - this.model, - this.permissionMode, - }); + const ClaudeProbe({required this.version, required this.slashCommands, required this.skills, this.model, this.permissionMode}); final String version; final List slashCommands; @@ -109,12 +103,12 @@ class ClaudeProbe { final String? permissionMode; Map toJson() => { - 'version': version, - 'slash_commands': slashCommands, - 'skills': skills, - if (model != null) 'model': model, - if (permissionMode != null) 'permission_mode': permissionMode, - }; + 'version': version, + 'slash_commands': slashCommands, + 'skills': skills, + if (model != null) 'model': model, + if (permissionMode != null) 'permission_mode': permissionMode, + }; /// Build from a stream-json `init` event object. Returns null if it doesn't /// look like an init event (no version field). @@ -130,12 +124,12 @@ class ClaudeProbe { } static ClaudeProbe fromCache(Map j) => ClaudeProbe( - version: (j['version'] as String?) ?? '', - slashCommands: _stringList(j['slash_commands']), - skills: _stringList(j['skills']), - model: j['model'] as String?, - permissionMode: j['permission_mode'] as String?, - ); + version: (j['version'] as String?) ?? '', + slashCommands: _stringList(j['slash_commands']), + skills: _stringList(j['skills']), + model: j['model'] as String?, + permissionMode: j['permission_mode'] as String?, + ); } List _stringList(Object? v) => v is List ? v.whereType().toList(growable: false) : const []; @@ -184,13 +178,13 @@ class ClaudeConfig extends ChangeNotifier { ClaudeInitProbe? initProbe, ClaudeConfigWatch? watch, Duration debounce = const Duration(milliseconds: 150), - }) : _globalDir = globalDir, - _cacheDir = cacheDir, - _projectDir = projectDir, - _versionRunner = versionRunner ?? _defaultVersionRunner, - _initProbe = initProbe ?? _defaultInitProbe, - _watch = watch, - _debounceFor = debounce; + }) : _globalDir = globalDir, + _cacheDir = cacheDir, + _projectDir = projectDir, + _versionRunner = versionRunner ?? _defaultVersionRunner, + _initProbe = initProbe ?? _defaultInitProbe, + _watch = watch, + _debounceFor = debounce; final Directory _globalDir; final Directory _cacheDir; @@ -400,10 +394,7 @@ class ClaudeConfig extends ChangeNotifier { /// Global first so that local entries, added later, win on collisions. List<(ConfigScope, Directory)> _scopeDirs() { final pd = _projectDir; - return [ - (ConfigScope.global, _globalDir), - if (pd != null) (ConfigScope.local, Directory('${pd.path}/.claude')), - ]; + return [(ConfigScope.global, _globalDir), if (pd != null) (ConfigScope.local, Directory('${pd.path}/.claude'))]; } Future> _loadSkills(Directory scopeDir, ConfigScope scope) async { @@ -415,12 +406,7 @@ class ClaudeConfig extends ChangeNotifier { final manifest = File('${entry.path}/SKILL.md'); if (!await manifest.exists()) continue; final fm = _parseFrontmatter(await manifest.readAsString()); - out.add(ClaudeSkill( - name: fm.name ?? _basename(entry.path), - description: fm.description, - scope: scope, - path: manifest.path, - )); + out.add(ClaudeSkill(name: fm.name ?? _basename(entry.path), description: fm.description, scope: scope, path: manifest.path)); } return out; } @@ -432,11 +418,7 @@ class ClaudeConfig extends ChangeNotifier { await for (final entry in dir.list()) { if (entry is! File || !entry.path.endsWith('.md')) continue; final base = _basename(entry.path); - out.add(ClaudeCommand( - name: base.substring(0, base.length - 3), - scope: scope, - path: entry.path, - )); + out.add(ClaudeCommand(name: base.substring(0, base.length - 3), scope: scope, path: entry.path)); } return out; } @@ -449,11 +431,7 @@ class ClaudeConfig extends ChangeNotifier { await for (final entry in dir.list()) { if (entry is! File || !entry.path.endsWith('.md')) continue; final base = _basename(entry.path); - out.add(ClaudeAgent( - name: base.substring(0, base.length - 3), - scope: scope, - path: entry.path, - )); + out.add(ClaudeAgent(name: base.substring(0, base.length - 3), scope: scope, path: entry.path)); } return out; } @@ -472,11 +450,7 @@ class ClaudeConfig extends ChangeNotifier { ClaudePermissions _permissionsOf(Map settings) { final p = settings['permissions']; if (p is! Map) return const ClaudePermissions(); - return ClaudePermissions( - allow: _stringList(p['allow']), - deny: _stringList(p['deny']), - ask: _stringList(p['ask']), - ); + return ClaudePermissions(allow: _stringList(p['allow']), deny: _stringList(p['deny']), ask: _stringList(p['ask'])); } // ---- Watching ----------------------------------------------------------- @@ -573,9 +547,7 @@ class ClaudeConfig extends ChangeNotifier { /// Claude's `mcpServers` is a `Map` keyed on server name. static List _parseMcpServers(Object? raw) { if (raw is! Map) return const []; - return [ - for (final key in raw.keys) ClaudeMcpServer(name: '$key'), - ]; + return [for (final key in raw.keys) ClaudeMcpServer(name: '$key')]; } static List _dedupeByName(List all, String Function(T) nameOf) { @@ -608,14 +580,7 @@ Future _defaultVersionRunner() async { Future _defaultInitProbe() async { try { - final r = await Process.run('claude', [ - '-p', - '.', - '--no-session-persistence', - '--output-format', - 'stream-json', - '--verbose', - ]); + final r = await Process.run('claude', ['-p', '.', '--no-session-persistence', '--output-format', 'stream-json', '--verbose']); return r.stdout as String?; } catch (_) { return null; diff --git a/lib/builtin/claude/src/claude_meta_sidebar.dart b/lib/builtin/claude/src/claude_meta_sidebar.dart index e5165dd2..1c0f24bb 100644 --- a/lib/builtin/claude/src/claude_meta_sidebar.dart +++ b/lib/builtin/claude/src/claude_meta_sidebar.dart @@ -218,14 +218,18 @@ class _ClaudeMetaSidebarState extends State { final managed = orch.byMemberName(memberName); if (managed == null) return; final forkId = 'fork:$memberName-${DateTime.now().millisecondsSinceEpoch}'; - unawaited(orch.spawn(SpawnSpec( - id: forkId, - role: 'fork of $memberName', - // sessionId is a placeholder; real claude session id arrives via init. - sessionId: forkId, - cwd: managed.cwd, - forkSourceSessionId: managed.sessionId, - ))); + unawaited( + orch.spawn( + SpawnSpec( + id: forkId, + role: 'fork of $memberName', + // sessionId is a placeholder; real claude session id arrives via init. + sessionId: forkId, + cwd: managed.cwd, + forkSourceSessionId: managed.sessionId, + ), + ), + ); } Future _refreshStats() async { @@ -276,11 +280,7 @@ class _ClaudeMetaSidebarState extends State { _MetaRow('sessions', '${latest.sessionCount}'), _MetaRow('tool calls', '${latest.toolCallCount}'), ]), - if (latest != null) - _MetaSection('LIFETIME', [ - _MetaRow('messages', '${_stats.lifetimeMessages}'), - _MetaRow('sessions', '${_stats.lifetimeSessions}'), - ]), + if (latest != null) _MetaSection('LIFETIME', [_MetaRow('messages', '${_stats.lifetimeMessages}'), _MetaRow('sessions', '${_stats.lifetimeSessions}')]), ..._runtimeSection(tokens), ]; if (sections.isEmpty) { @@ -357,17 +357,10 @@ class _ClaudeMetaSidebarState extends State { final broker = _orchestrator?.broker; if (chatModel != null && broker != null) { children.add(const SizedBox(height: 12)); - children.add(TeamChatSidebar( - model: chatModel, - broker: broker, - onPopOut: _openChatPane, - )); + children.add(TeamChatSidebar(model: chatModel, broker: broker, onPopOut: _openChatPane)); } - return ListView( - padding: const EdgeInsets.all(12), - children: children, - ); + return ListView(padding: const EdgeInsets.all(12), children: children); } Widget _taskSection(SurfaceTokens tokens) { @@ -420,18 +413,11 @@ class _ClaudeMetaSidebarState extends State { // Footer hint. Padding( padding: const EdgeInsets.only(top: 12), - child: ClideText( - 'expand a list to see all · click a skill/agent/command → opens its .md', - muted: true, - fontSize: clideFontSmall, - ), + child: ClideText('expand a list to see all · click a skill/agent/command → opens its .md', muted: true, fontSize: clideFontSmall), ), ]; - return ListView( - padding: const EdgeInsets.all(12), - children: children, - ); + return ListView(padding: const EdgeInsets.all(12), children: children); } /// One key→value row in the pinned SETTINGS table. @@ -454,22 +440,22 @@ class _ClaudeMetaSidebarState extends State { } String _configSectionLabel(_ConfigSection section) => switch (section) { - _ConfigSection.skills => 'SKILLS', - _ConfigSection.agents => 'AGENTS', - _ConfigSection.commands => 'COMMANDS', - _ConfigSection.hooks => 'HOOKS', - _ConfigSection.permissions => 'PERMISSIONS', - _ConfigSection.mcpServers => 'MCP SERVERS', - }; + _ConfigSection.skills => 'SKILLS', + _ConfigSection.agents => 'AGENTS', + _ConfigSection.commands => 'COMMANDS', + _ConfigSection.hooks => 'HOOKS', + _ConfigSection.permissions => 'PERMISSIONS', + _ConfigSection.mcpServers => 'MCP SERVERS', + }; int _configSectionCount(ClaudeConfig config, _ConfigSection section) => switch (section) { - _ConfigSection.skills => config.skills.length, - _ConfigSection.agents => config.agents.length, - _ConfigSection.commands => config.commands.length, - _ConfigSection.hooks => config.hooks.length, - _ConfigSection.permissions => config.permissions.allow.length + config.permissions.deny.length + config.permissions.ask.length, - _ConfigSection.mcpServers => config.mcpServers.length, - }; + _ConfigSection.skills => config.skills.length, + _ConfigSection.agents => config.agents.length, + _ConfigSection.commands => config.commands.length, + _ConfigSection.hooks => config.hooks.length, + _ConfigSection.permissions => config.permissions.allow.length + config.permissions.deny.length + config.permissions.ask.length, + _ConfigSection.mcpServers => config.mcpServers.length, + }; Widget _configAccordion(SurfaceTokens tokens, ClaudeConfig config, _ConfigSection section) { final expanded = _expanded.contains(section); @@ -492,17 +478,11 @@ class _ClaudeMetaSidebarState extends State { List _configSectionChildren(SurfaceTokens tokens, ClaudeConfig config, _ConfigSection section) { switch (section) { case _ConfigSection.skills: - return [ - for (final skill in config.skills) _configFileRow(tokens, skill.name, skill.path), - ]; + return [for (final skill in config.skills) _configFileRow(tokens, skill.name, skill.path)]; case _ConfigSection.agents: - return [ - for (final agent in config.agents) _configFileRow(tokens, agent.name, agent.path), - ]; + return [for (final agent in config.agents) _configFileRow(tokens, agent.name, agent.path)]; case _ConfigSection.commands: - return [ - for (final cmd in config.commands) _configFileRow(tokens, cmd.name, cmd.path), - ]; + return [for (final cmd in config.commands) _configFileRow(tokens, cmd.name, cmd.path)]; case _ConfigSection.hooks: return [ for (final hook in config.hooks) @@ -540,11 +520,7 @@ class _ClaudeMetaSidebarState extends State { Widget _configFileRow(SurfaceTokens tokens, String name, String? path) { final row = Padding( padding: const EdgeInsets.only(left: 16, top: 2, bottom: 2), - child: ClideText( - name, - fontSize: clideFontSmall, - color: path != null ? tokens.globalFocus : tokens.globalForeground, - ), + child: ClideText(name, fontSize: clideFontSmall, color: path != null ? tokens.globalFocus : tokens.globalForeground), ); if (path == null) return row; void openMarkdown() => ClideKernel.of(context).messages.publish('builtin.markdown', 'selection', {'path': path}); @@ -558,11 +534,7 @@ class _ClaudeMetaSidebarState extends State { onTap: openMarkdown, builder: (ctx, hovered, _) => Padding( padding: const EdgeInsets.only(left: 16, top: 2, bottom: 2), - child: ClideText( - name, - fontSize: clideFontSmall, - color: hovered ? tokens.globalForeground : tokens.globalFocus, - ), + child: ClideText(name, fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.globalFocus), ), ), ); @@ -576,22 +548,18 @@ class _ClaudeMetaSidebarState extends State { // allow → statusSuccess, ask → statusWarning, deny → statusError Color kindColor(_ConfigPermKind k) => switch (k) { - _ConfigPermKind.allow => tokens.statusSuccess, - _ConfigPermKind.ask => tokens.statusWarning, - _ConfigPermKind.deny => tokens.statusError, - }; + _ConfigPermKind.allow => tokens.statusSuccess, + _ConfigPermKind.ask => tokens.statusWarning, + _ConfigPermKind.deny => tokens.statusError, + }; String kindLabel(_ConfigPermKind k) => switch (k) { - _ConfigPermKind.allow => kindAllow, - _ConfigPermKind.ask => kindAsk, - _ConfigPermKind.deny => kindDeny, - }; + _ConfigPermKind.allow => kindAllow, + _ConfigPermKind.ask => kindAsk, + _ConfigPermKind.deny => kindDeny, + }; - final groups = [ - (_ConfigPermKind.allow, perms.allow), - (_ConfigPermKind.ask, perms.ask), - (_ConfigPermKind.deny, perms.deny), - ]; + final groups = [(_ConfigPermKind.allow, perms.allow), (_ConfigPermKind.ask, perms.ask), (_ConfigPermKind.deny, perms.deny)]; final rows = []; for (final (kind, rules) in groups) { @@ -631,44 +599,41 @@ class _ClaudeMetaSidebarState extends State { // --- Shared rendering ----------------------------------------------------- Widget _placeholder(String text) => Padding( - padding: const EdgeInsets.all(12), - child: ClideText(text, muted: true, fontSize: clideFontSmall), - ); + padding: const EdgeInsets.all(12), + child: ClideText(text, muted: true, fontSize: clideFontSmall), + ); Widget _metaTable(SurfaceTokens tokens, List<_MetaSection> sections) { final children = []; for (var i = 0; i < sections.length; i++) { final s = sections[i]; - children.add(Padding( - padding: EdgeInsets.only(top: i == 0 ? 0 : 16, bottom: 6), - child: ClideText(s.header, fontSize: clideFontSmall, color: tokens.globalTextMuted), - )); + children.add( + Padding( + padding: EdgeInsets.only(top: i == 0 ? 0 : 16, bottom: 6), + child: ClideText(s.header, fontSize: clideFontSmall, color: tokens.globalTextMuted), + ), + ); for (final r in s.rows) { - children.add(Padding( - padding: const EdgeInsets.symmetric(vertical: _rowPitch), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: _labelColumnWidth, - child: ClideText(r.label, muted: true, fontSize: clideFontSmall), - ), - Expanded( - child: ClideText( - r.value, - fontSize: clideFontSmall, - color: r.valueColor ?? tokens.globalForeground, + children.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: _rowPitch), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: _labelColumnWidth, + child: ClideText(r.label, muted: true, fontSize: clideFontSmall), ), - ), - ], + Expanded( + child: ClideText(r.value, fontSize: clideFontSmall, color: r.valueColor ?? tokens.globalForeground), + ), + ], + ), ), - )); + ); } } - return ListView( - padding: const EdgeInsets.all(12), - children: children, - ); + return ListView(padding: const EdgeInsets.all(12), children: children); } } @@ -788,7 +753,11 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { // Color dot Padding( padding: const EdgeInsets.only(top: 3), - child: Container(width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ), ), const SizedBox(width: 8), // Name + status @@ -840,11 +809,7 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { child: Row( children: [ Expanded( - child: ClideText( - 'Enable bypassPermissions? All tool calls will be auto-allowed.', - fontSize: clideFontSmall, - color: tokens.globalTextMuted, - ), + child: ClideText('Enable bypassPermissions? All tool calls will be auto-allowed.', fontSize: clideFontSmall, color: tokens.globalTextMuted), ), const SizedBox(width: 4), // Confirm @@ -889,14 +854,7 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { ); } - Widget _buildControls( - BuildContext context, - SurfaceTokens tokens, - ManagedSession managed, - bool isVisible, - bool isMuted, - bool isInjecting, - ) { + Widget _buildControls(BuildContext context, SurfaceTokens tokens, ManagedSession managed, bool isVisible, bool isMuted, bool isInjecting) { return Row( mainAxisSize: MainAxisSize.min, children: [ @@ -975,11 +933,11 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { /// Maps a permission-mode string to a single-letter badge label. String _permissionModeBadge(String mode) => switch (mode) { - 'acceptEdits' => 'A', - 'plan' => 'P', - 'bypassPermissions' => 'B', - _ => 'D', // default - }; + 'acceptEdits' => 'A', + 'plan' => 'P', + 'bypassPermissions' => 'B', + _ => 'D', // default +}; /// Clickable permission-mode badge shown in each roster row (T-181). /// @@ -990,12 +948,7 @@ String _permissionModeBadge(String mode) => switch (mode) { /// It is a custom painted label (no Material), consistent with the rendering /// stack rules (D-7, CLAUDE.md guardrails). class _PermissionModeBadge extends StatelessWidget { - const _PermissionModeBadge({ - required this.mode, - required this.tokens, - required this.onCycle, - required this.onBypass, - }); + const _PermissionModeBadge({required this.mode, required this.tokens, required this.onCycle, required this.onBypass}); final String mode; final SurfaceTokens tokens; @@ -1012,7 +965,8 @@ class _PermissionModeBadge extends StatelessWidget { final isBypass = mode == 'bypassPermissions'; final badgeColor = isBypass ? const Color(0xFFF06C6F) : tokens.globalFocus; - final tooltip = 'Permission mode: ${permissionModeLabel(mode)}. ' + final tooltip = + 'Permission mode: ${permissionModeLabel(mode)}. ' 'Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions.'; return Padding( @@ -1046,11 +1000,7 @@ class _PermissionModeBadge extends StatelessWidget { borderRadius: BorderRadius.circular(2), border: Border.all(color: badgeColor.withAlpha(hovered ? 180 : 100), width: 1), ), - child: ClideText( - label, - fontSize: 9, - color: badgeColor, - ), + child: ClideText(label, fontSize: 9, color: badgeColor), ), ), ), @@ -1060,12 +1010,7 @@ class _PermissionModeBadge extends StatelessWidget { /// A single icon-button used in the roster row controls. class _IconButton extends StatelessWidget { - const _IconButton({ - required this.painter, - required this.tooltip, - required this.color, - required this.onTap, - }); + const _IconButton({required this.painter, required this.tooltip, required this.color, required this.onTap}); final ClideIconPainter painter; final String tooltip; @@ -1096,11 +1041,7 @@ class _IconButton extends StatelessWidget { /// Inline text input for injecting a message into a session (T-171). /// Submits on Enter; Cancel is handled by the parent via [_IconButton]. class _InjectTextField extends StatelessWidget { - const _InjectTextField({ - required this.controller, - required this.tokens, - required this.onSubmit, - }); + const _InjectTextField({required this.controller, required this.tokens, required this.onSubmit}); final TextEditingController controller; final SurfaceTokens tokens; @@ -1119,12 +1060,7 @@ class _InjectTextField extends StatelessWidget { child: EditableText( controller: controller, focusNode: FocusNode(debugLabel: 'inject-${controller.hashCode}')..requestFocus(), - style: TextStyle( - fontFamily: 'JetBrains Mono', - fontSize: clideFontSmall, - color: tokens.globalForeground, - height: 1.4, - ), + style: TextStyle(fontFamily: 'JetBrains Mono', fontSize: clideFontSmall, color: tokens.globalForeground, height: 1.4), cursorColor: tokens.globalFocus, backgroundCursorColor: tokens.globalTextMuted, onSubmitted: onSubmit, @@ -1139,11 +1075,7 @@ class _InjectTextField extends StatelessWidget { /// One row in the TASKS section: status marker + title + owner + reassign. class _TaskRow extends StatelessWidget { - const _TaskRow({ - required this.task, - required this.members, - required this.broker, - }); + const _TaskRow({required this.task, required this.members, required this.broker}); final TeamTask task; final List members; @@ -1240,18 +1172,9 @@ class _TabStrip extends StatelessWidget { builder: (ctx, hovered, _) => Container( padding: const EdgeInsets.only(bottom: 3), decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: t == current ? tokens.globalFocus : const Color(0x00000000), - width: 2, - ), - ), - ), - child: ClideText( - _label(t), - fontSize: clideFontSmall, - color: t == current || hovered ? tokens.globalForeground : tokens.globalTextMuted, + border: Border(bottom: BorderSide(color: t == current ? tokens.globalFocus : const Color(0x00000000), width: 2)), ), + child: ClideText(_label(t), fontSize: clideFontSmall, color: t == current || hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ), @@ -1262,8 +1185,8 @@ class _TabStrip extends StatelessWidget { } String _label(SidebarTab t) => switch (t) { - SidebarTab.activity => 'Activity', - SidebarTab.team => memberCount == 0 ? 'Team' : 'Team · $memberCount', - SidebarTab.config => 'Config', - }; + SidebarTab.activity => 'Activity', + SidebarTab.team => memberCount == 0 ? 'Team' : 'Team · $memberCount', + SidebarTab.config => 'Config', + }; } diff --git a/lib/builtin/claude/src/claude_pane.dart b/lib/builtin/claude/src/claude_pane.dart index 40a67041..9a286394 100644 --- a/lib/builtin/claude/src/claude_pane.dart +++ b/lib/builtin/claude/src/claude_pane.dart @@ -197,9 +197,12 @@ class _ClaudePaneState extends State { sub.cancel(); if (!c.isCompleted) c.complete(); }); - await c.future.timeout(const Duration(seconds: 10), onTimeout: () { - sub.cancel(); - }); + await c.future.timeout( + const Duration(seconds: 10), + onTimeout: () { + sub.cancel(); + }, + ); if (!mounted) return; } return _spawn(); @@ -267,13 +270,9 @@ class _ClaudePaneState extends State { // assigned by `--fork-session` and arrives in the init event (T-172). _sessionId ??= freshSessionId(); try { - managed = await orch.spawn(SpawnSpec( - id: _orchId, - role: 'fork ${widget.secondaryIndex}', - sessionId: _sessionId!, - cwd: repoRoot, - forkSourceSessionId: forkSource, - )); + managed = await orch.spawn( + SpawnSpec(id: _orchId, role: 'fork ${widget.secondaryIndex}', sessionId: _sessionId!, cwd: repoRoot, forkSourceSessionId: forkSource), + ); } catch (e) { if (mounted) setState(() => _error = 'Could not start fork: $e'); return; @@ -291,14 +290,16 @@ class _ClaudePaneState extends State { final resume = await File(transcriptFile).exists(); try { - managed = await orch.spawn(SpawnSpec( - id: _orchId, - role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}', - sessionId: _sessionId!, - cwd: repoRoot, - resume: resume, - transcriptPath: resume ? transcriptFile : null, - )); + managed = await orch.spawn( + SpawnSpec( + id: _orchId, + role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}', + sessionId: _sessionId!, + cwd: repoRoot, + resume: resume, + transcriptPath: resume ? transcriptFile : null, + ), + ); } catch (e) { if (mounted) setState(() => _error = 'Could not start claude: $e'); return; @@ -314,10 +315,10 @@ class _ClaudePaneState extends State { // from the transcript/sidecar). Surfaces the resume path in `make run`. final seeded = _conversation?.items.length ?? 0; _kernel()?.log.info( - 'claude', - 'pane $_orchId bound session ${_sessionId ?? '?'} in $repoRoot — ' - '${seeded > 0 ? 'connected to history ($seeded seeded item(s))' : 'fresh session (no history)'}', - ); + 'claude', + 'pane $_orchId bound session ${_sessionId ?? '?'} in $repoRoot — ' + '${seeded > 0 ? 'connected to history ($seeded seeded item(s))' : 'fresh session (no history)'}', + ); _statusSub = managed.session.statusStream.listen((s) { if (!mounted) return; setState(() => _status = s); @@ -426,13 +427,7 @@ class _ClaudePaneState extends State { final dir = Directory(claudeProjectDir(root)); final sessions = await listSessions(dir); if (!mounted) return; - final picked = await dialog.show( - (ctx, dismiss) => SessionPickerDialog( - sessions: sessions, - onPick: (id) => dismiss(id), - onCancel: dismiss, - ), - ); + final picked = await dialog.show((ctx, dismiss) => SessionPickerDialog(sessions: sessions, onPick: (id) => dismiss(id), onCancel: dismiss)); if (picked == null || !mounted) return; setState(() => _statusLine = 'resuming…'); await _respawnWithSession(picked); @@ -479,10 +474,7 @@ class _ClaudePaneState extends State { final Widget body; if (_error != null) { - body = Padding( - padding: const EdgeInsets.all(16), - child: ClideText(_error!, muted: true), - ); + body = Padding(padding: const EdgeInsets.all(16), child: ClideText(_error!, muted: true)); } else if (_conversation != null) { // Rebuild conversation + composer zone together on each prompt change so // the view hides a prompted tool-use card the moment its prompt appears @@ -520,7 +512,7 @@ class _ClaudePaneState extends State { // with the conversation; renders nothing when there are no tasks. ListenableBuilder( listenable: _conversation!, - builder: (_, __) => ClaudeTaskDock(tasks: taskListFrom(_conversation!.items)), + builder: (_, _) => ClaudeTaskDock(tasks: taskListFrom(_conversation!.items)), ), // An open prompt takes the composer's space and hides the text // input until it's answered, so interaction stays out of the @@ -557,22 +549,11 @@ class _ClaudePaneState extends State { body = const Center(child: ClideText('starting…', muted: true)); } - final content = widget.showChrome - ? ClidePaneChrome( - title: title, - subtitle: _error ?? _statusLine, - child: body, - ) - : body; + final content = widget.showChrome ? ClidePaneChrome(title: title, subtitle: _error ?? _statusLine, child: body) : body; // Surface this pane's status to the bottom status-bar slot while it's // the focused pane (T-150). - return ClidePane( - contributionId: widget.contributionId, - active: widget.active, - statusWidget: _statusWidget(tokens), - child: content, - ); + return ClidePane(contributionId: widget.contributionId, active: widget.active, statusWidget: _statusWidget(tokens), child: content); } } @@ -590,13 +571,7 @@ class _ModeBadge extends StatelessWidget { return Semantics( label: 'permission mode: ${permissionModeLabel(mode)}', excludeSemantics: true, - child: ClideText( - permissionModeLabel(mode), - fontSize: clideFontSmall, - fontFamily: clideMonoFamily, - color: permissionModeColor(mode, tokens), - maxLines: 1, - ), + child: ClideText(permissionModeLabel(mode), fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: permissionModeColor(mode, tokens), maxLines: 1), ); } } diff --git a/lib/builtin/claude/src/claude_session_host.dart b/lib/builtin/claude/src/claude_session_host.dart index 121d71b4..d8b37745 100644 --- a/lib/builtin/claude/src/claude_session_host.dart +++ b/lib/builtin/claude/src/claude_session_host.dart @@ -85,11 +85,13 @@ class ClaudeSessionHostState extends State { /// Public entry point used by the `claude.new-secondary` command. void addSecondary() { final index = _nextSecondary++; - _controller.add(MultitabEntry<_Session>( - id: 'secondary-$index', - title: 'session $index', - payload: _Session(isPrimary: false, secondaryIndex: index), - )); + _controller.add( + MultitabEntry<_Session>( + id: 'secondary-$index', + title: 'session $index', + payload: _Session(isPrimary: false, secondaryIndex: index), + ), + ); } /// Open a new pane as a fork of [sourceClaudeSessionId] (T-172). @@ -99,11 +101,13 @@ class ClaudeSessionHostState extends State { /// without touching the original. void addFork(String sourceClaudeSessionId) { final index = _nextSecondary++; - _controller.add(MultitabEntry<_Session>( - id: 'secondary-$index', - title: 'fork $index', - payload: _Session(isPrimary: false, secondaryIndex: index, forkSourceId: sourceClaudeSessionId), - )); + _controller.add( + MultitabEntry<_Session>( + id: 'secondary-$index', + title: 'fork $index', + payload: _Session(isPrimary: false, secondaryIndex: index, forkSourceId: sourceClaudeSessionId), + ), + ); } @override diff --git a/lib/builtin/claude/src/claude_stats.dart b/lib/builtin/claude/src/claude_stats.dart index 1690fb89..21ce1b17 100644 --- a/lib/builtin/claude/src/claude_stats.dart +++ b/lib/builtin/claude/src/claude_stats.dart @@ -8,12 +8,7 @@ library; import 'dart:convert'; class DailyActivity { - const DailyActivity({ - required this.date, - required this.messageCount, - required this.sessionCount, - required this.toolCallCount, - }); + const DailyActivity({required this.date, required this.messageCount, required this.sessionCount, required this.toolCallCount}); final String date; // "YYYY-MM-DD" (sorts chronologically as a string) final int messageCount; @@ -54,12 +49,14 @@ ClaudeStats parseClaudeStats(String jsonStr) { if (da is List) { for (final e in da) { if (e is! Map) continue; - daily.add(DailyActivity( - date: '${e['date']}', - messageCount: _int(e['messageCount']), - sessionCount: _int(e['sessionCount']), - toolCallCount: _int(e['toolCallCount']), - )); + daily.add( + DailyActivity( + date: '${e['date']}', + messageCount: _int(e['messageCount']), + sessionCount: _int(e['sessionCount']), + toolCallCount: _int(e['toolCallCount']), + ), + ); } } return ClaudeStats(lastComputed: j['lastComputedDate'] as String?, daily: daily); diff --git a/lib/builtin/claude/src/claude_status.dart b/lib/builtin/claude/src/claude_status.dart index 5c98bd82..3ee0cea6 100644 --- a/lib/builtin/claude/src/claude_status.dart +++ b/lib/builtin/claude/src/claude_status.dart @@ -62,10 +62,7 @@ String nextSafePermissionMode(String current) { if (s.cost != null) '\$${s.cost!.toStringAsFixed(2)}', if (s.rateLimitInfo != null) s.rateLimitInfo!, ].join(' · '); - return ( - leading: s.model != null ? shortModelLabel(s.model!) : null, - trailing: trailing.isEmpty ? null : trailing, - ); + return (leading: s.model != null ? shortModelLabel(s.model!) : null, trailing: trailing.isEmpty ? null : trailing); } /// Friendly label for Claude's permission modes. diff --git a/lib/builtin/claude/src/claude_task_dock.dart b/lib/builtin/claude/src/claude_task_dock.dart index a814cdde..3bee0fdd 100644 --- a/lib/builtin/claude/src/claude_task_dock.dart +++ b/lib/builtin/claude/src/claude_task_dock.dart @@ -62,10 +62,7 @@ class _ClaudeTaskDockState extends State { ClideDivider(), Padding( padding: const EdgeInsets.fromLTRB(10, 4, 10, 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [for (final t in tasks) _taskRow(tokens, t)], - ), + child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [for (final t in tasks) _taskRow(tokens, t)]), ), ], ], @@ -76,20 +73,22 @@ class _ClaudeTaskDockState extends State { } Widget _summaryRow(SurfaceTokens tokens, String summary, String? current) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - child: Row( - children: [ - ClideIcon(_expanded ? const ChevronDownIcon() : const ChevronRightIcon(), size: 12, color: tokens.globalTextMuted), - const SizedBox(width: 8), - ClideText(summary, fontSize: clideFontCaption, color: tokens.globalTextMuted), - if (!_expanded && current != null) ...[ - const SizedBox(width: 10), - Expanded(child: ClideText(current, fontSize: clideFontCaption, color: tokens.globalTextMuted, maxLines: 1, overflow: TextOverflow.ellipsis)), - ] else - const Spacer(), - ], - ), - ); + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + child: Row( + children: [ + ClideIcon(_expanded ? const ChevronDownIcon() : const ChevronRightIcon(), size: 12, color: tokens.globalTextMuted), + const SizedBox(width: 8), + ClideText(summary, fontSize: clideFontCaption, color: tokens.globalTextMuted), + if (!_expanded && current != null) ...[ + const SizedBox(width: 10), + Expanded( + child: ClideText(current, fontSize: clideFontCaption, color: tokens.globalTextMuted, maxLines: 1, overflow: TextOverflow.ellipsis), + ), + ] else + const Spacer(), + ], + ), + ); Widget _taskRow(SurfaceTokens tokens, TaskItem t) { final (String glyph, Color color, String word) = switch (t.status) { @@ -106,14 +105,13 @@ class _ClaudeTaskDockState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding(padding: const EdgeInsets.only(top: 1), child: ClideIcon(PhosphorIcons.byName(glyph), size: 13, color: color)), + Padding( + padding: const EdgeInsets.only(top: 1), + child: ClideIcon(PhosphorIcons.byName(glyph), size: 13, color: color), + ), const SizedBox(width: 8), Expanded( - child: ClideText( - t.text, - fontSize: clideFontCaption, - color: t.status == TaskStatus.completed ? tokens.globalTextMuted : tokens.globalForeground, - ), + child: ClideText(t.text, fontSize: clideFontCaption, color: t.status == TaskStatus.completed ? tokens.globalTextMuted : tokens.globalForeground), ), ], ), diff --git a/lib/builtin/claude/src/clipboard_paste.dart b/lib/builtin/claude/src/clipboard_paste.dart index adc6eb0d..fa96a781 100644 --- a/lib/builtin/claude/src/clipboard_paste.dart +++ b/lib/builtin/claude/src/clipboard_paste.dart @@ -99,16 +99,10 @@ String pasteCacheDir() { /// written to [tempDir] (default [pasteCacheDir]) and attached by its /// path. Returns an empty list when the clipboard holds neither, so the /// composer pastes text instead. -Future> resolveClipboardAttachment( - ClipboardSource source, { - Directory? tempDir, - DateTime Function() now = DateTime.now, -}) async { +Future> resolveClipboardAttachment(ClipboardSource source, {Directory? tempDir, DateTime Function() now = DateTime.now}) async { final files = await source.readFiles(); if (files.isNotEmpty) { - return [ - for (final p in files) ComposerAttachment(path: p, isImage: _looksLikeImage(p)), - ]; + return [for (final p in files) ComposerAttachment(path: p, isImage: _looksLikeImage(p))]; } final image = await source.readImage(); diff --git a/lib/builtin/claude/src/conversation_card.dart b/lib/builtin/claude/src/conversation_card.dart index 27ce2eb9..9f41f3a3 100644 --- a/lib/builtin/claude/src/conversation_card.dart +++ b/lib/builtin/claude/src/conversation_card.dart @@ -183,20 +183,13 @@ class _ConversationCardState extends State { if (!_collapsed) ...[ const SizedBox(height: 4), widget.body, - for (final seg in widget.extraSegments) ...[ - _segmentLabel(tokens, seg.label), - seg.child, - ], + for (final seg in widget.extraSegments) ...[_segmentLabel(tokens, seg.label), seg.child], ], ], ); return Padding( padding: widget.margin, - child: MouseRegion( - onEnter: (_) => setState(() => _hover = true), - onExit: (_) => setState(() => _hover = false), - child: _frame(tokens, content), - ), + child: MouseRegion(onEnter: (_) => setState(() => _hover = true), onExit: (_) => setState(() => _hover = false), child: _frame(tokens, content)), ); } @@ -289,11 +282,7 @@ class _ConversationCardState extends State { onTap: () => setState(() => _collapsed = !_collapsed), builder: (_, hovered, pressed) => Padding( padding: const EdgeInsets.only(right: 6), - child: ClideIcon( - _collapsed ? PhosphorIcons.byName('caret-right') : PhosphorIcons.byName('caret-down'), - size: 12, - color: tokens.globalTextMuted, - ), + child: ClideIcon(_collapsed ? PhosphorIcons.byName('caret-right') : PhosphorIcons.byName('caret-down'), size: 12, color: tokens.globalTextMuted), ), ), ); @@ -330,15 +319,15 @@ class _ConversationCardState extends State { /// A muted sub-label + hairline divider introducing an [CardSegment] below /// the primary body (T-262), so CALL/PROMPT/RESULT read as distinct parts. Widget _segmentLabel(SurfaceTokens tokens, String label) => Padding( - padding: const EdgeInsets.only(top: 8, bottom: 4), - child: Row( - children: [ - ClideText(label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), - const SizedBox(width: 8), - Expanded(child: Container(height: 1, color: tokens.panelBorder)), - ], - ), - ); + padding: const EdgeInsets.only(top: 8, bottom: 4), + child: Row( + children: [ + ClideText(label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), + const SizedBox(width: 8), + Expanded(child: Container(height: 1, color: tokens.panelBorder)), + ], + ), + ); List _actions(SurfaceTokens tokens) { final items = <_ActionItem>[]; @@ -360,12 +349,7 @@ class _ConversationCardState extends State { onTap: items[i].onTap, builder: (_, hovered, pressed) => Padding( padding: const EdgeInsets.only(left: 10), - child: ClideText( - items[i].label, - fontSize: clideFontMeta, - color: tokens.globalTextMuted, - fontFamily: clideMonoFamily, - ), + child: ClideText(items[i].label, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ), ), ), diff --git a/lib/builtin/claude/src/conversation_controller.dart b/lib/builtin/claude/src/conversation_controller.dart index da700189..eede1dd2 100644 --- a/lib/builtin/claude/src/conversation_controller.dart +++ b/lib/builtin/claude/src/conversation_controller.dart @@ -21,11 +21,8 @@ class ConversationController extends ChangeNotifier { /// over stream-json so the pane would otherwise start empty. [onDispose] /// is invoked from [dispose] — wire it to the reader's `dispose` so /// cancelling the view tears down the underlying tail. - ConversationController({ - required Stream stream, - Iterable? seed, - Future Function()? onDispose, - }) : _onDispose = onDispose { + ConversationController({required Stream stream, Iterable? seed, Future Function()? onDispose}) + : _onDispose = onDispose { if (seed != null) _items.addAll(seed); _sub = stream.listen(_onItem); } @@ -34,13 +31,10 @@ class ConversationController extends ChangeNotifier { /// the [ConversationItem]s a [TranscriptPublisher] writes onto /// [publisher]/[channel]. Decouples the view from the reader so several /// panels can render the same conversation (team work, T-139/T-140). - factory ConversationController.fromBus({ - required MessageBus messages, - String channel = ClaudeConversation.leadChannel, - Future Function()? onDispose, - }) { - final stream = - messages.subscribe(publisher: ClaudeConversation.publisher, channel: channel).map((m) => m.data[ClaudeConversation.itemKey] as ConversationItem); + factory ConversationController.fromBus({required MessageBus messages, String channel = ClaudeConversation.leadChannel, Future Function()? onDispose}) { + final stream = messages + .subscribe(publisher: ClaudeConversation.publisher, channel: channel) + .map((m) => m.data[ClaudeConversation.itemKey] as ConversationItem); return ConversationController(stream: stream, onDispose: onDispose); } diff --git a/lib/builtin/claude/src/conversation_view.dart b/lib/builtin/claude/src/conversation_view.dart index d10f1ed2..89d0dad4 100644 --- a/lib/builtin/claude/src/conversation_view.dart +++ b/lib/builtin/claude/src/conversation_view.dart @@ -181,11 +181,9 @@ class _ConversationViewState extends State { /// - [runByToolUseId]: the rest of the run — prose / thinking / tool cards — /// nested in a holder UNDER the Agent card (T-264), with a successful /// sidechain tool result left out (it folds into its own tool card). - ({ - Set ownedSidechainUuids, - Map> promptsByToolUseId, - Map> runByToolUseId, - }) _sidechainFold(List items) { + ({Set ownedSidechainUuids, Map> promptsByToolUseId, Map> runByToolUseId}) _sidechainFold( + List items, + ) { final agentByMsgUuid = { for (final it in items) if (it is AssistantToolUse && _isAgentTool(it.name)) it.uuid: it, @@ -308,39 +306,39 @@ class _ConversationViewState extends State { // visible list don't reattach State to the wrong card (T-285). return switch (g) { StickyItem(:final item) => _ConversationTurn( - key: ValueKey('turn.${item.uuid}'), - item: item, - tokens: tokens, - collapseTools: true, - toolUseOutcomes: widget.toolUseOutcomes, - quietErrorToolUseIds: widget.quietErrorToolUseIds, - toolUseById: widget.controller.toolUseById, - resultByToolUseId: resultByToolUseId, - promptsByToolUseId: fold.promptsByToolUseId, - runByToolUseId: fold.runByToolUseId, - ), + key: ValueKey('turn.${item.uuid}'), + item: item, + tokens: tokens, + collapseTools: true, + toolUseOutcomes: widget.toolUseOutcomes, + quietErrorToolUseIds: widget.quietErrorToolUseIds, + toolUseById: widget.controller.toolUseById, + resultByToolUseId: resultByToolUseId, + promptsByToolUseId: fold.promptsByToolUseId, + runByToolUseId: fold.runByToolUseId, + ), FoldedCluster(:final items) => _ActivityCard( - key: ValueKey('cluster.${items.first.uuid}'), - items: items, - tokens: tokens, - toolUseOutcomes: widget.toolUseOutcomes, - quietErrorToolUseIds: widget.quietErrorToolUseIds, - toolUseById: widget.controller.toolUseById, - resultByToolUseId: resultByToolUseId, - promptsByToolUseId: fold.promptsByToolUseId, - runByToolUseId: fold.runByToolUseId, - ), + key: ValueKey('cluster.${items.first.uuid}'), + items: items, + tokens: tokens, + toolUseOutcomes: widget.toolUseOutcomes, + quietErrorToolUseIds: widget.quietErrorToolUseIds, + toolUseById: widget.controller.toolUseById, + resultByToolUseId: resultByToolUseId, + promptsByToolUseId: fold.promptsByToolUseId, + runByToolUseId: fold.runByToolUseId, + ), EditRun(:final edits) => _EditRunCard( - key: ValueKey('edits.${edits.first.uuid}'), - edits: edits, - tokens: tokens, - toolUseOutcomes: widget.toolUseOutcomes, - quietErrorToolUseIds: widget.quietErrorToolUseIds, - toolUseById: widget.controller.toolUseById, - resultByToolUseId: resultByToolUseId, - promptsByToolUseId: fold.promptsByToolUseId, - runByToolUseId: fold.runByToolUseId, - ), + key: ValueKey('edits.${edits.first.uuid}'), + edits: edits, + tokens: tokens, + toolUseOutcomes: widget.toolUseOutcomes, + quietErrorToolUseIds: widget.quietErrorToolUseIds, + toolUseById: widget.controller.toolUseById, + resultByToolUseId: resultByToolUseId, + promptsByToolUseId: fold.promptsByToolUseId, + runByToolUseId: fold.runByToolUseId, + ), }; }, ), @@ -413,7 +411,7 @@ String? resolveWorkspaceFilePath(String? root, String raw) { /// Open a clicked workspace file reference in the editor, jumping to [line] /// when present — the Dart-side twin of `clide editor open ` (T-300, D-6). void _openFile(BuildContext context, String path, int? line) { - unawaited(ClideKernel.of(context).ipc.request('editor.open', args: {'path': path, if (line != null) 'line': line})); + unawaited(ClideKernel.of(context).ipc.request('editor.open', args: {'path': path, 'line': ?line})); } /// One conversation item, rendered by kind. @@ -474,63 +472,63 @@ class _ConversationTurn extends StatelessWidget { // sidechain prompt here is an orphan one (its Agent card couldn't be // resolved) — folded prompts are suppressed upstream (T-263). UserMessage() when i.injected || i.isSidechain => ConversationCard( - // Framed like every other card (T-306) — just muted + collapsed, not - // the blue "you" accent (D-78); bare read as unfinished next to the - // carded tool calls. - variant: ConversationCardVariant.bordered, - accent: tokens.globalTextMuted, - label: i.isSidechain ? 'agent prompt' : 'context', - copyText: i.text, - collapsible: true, - collapsedByDefault: true, - collapsedSummary: _firstLine(i.text), - margin: _childMargin, - body: ClideText(i.text, muted: true, fontSize: clideFontMeta), - ), + // Framed like every other card (T-306) — just muted + collapsed, not + // the blue "you" accent (D-78); bare read as unfinished next to the + // carded tool calls. + variant: ConversationCardVariant.bordered, + accent: tokens.globalTextMuted, + label: i.isSidechain ? 'agent prompt' : 'context', + copyText: i.text, + collapsible: true, + collapsedByDefault: true, + collapsedSummary: _firstLine(i.text), + margin: _childMargin, + body: ClideText(i.text, muted: true, fontSize: clideFontMeta), + ), UserMessage() => ConversationCard( - accent: tokens.globalFocus, - label: 'you', - copyText: i.text, - margin: _childMargin, - // Pasted-image @path tokens render as inline thumbnails that open the - // lightbox (T-236/T-254); copyText keeps the original text verbatim. - body: ClideMarkdown( - i.text, - onRecordTap: (id) => _openRecord(context, id), - onImageToken: (path) => ImageThumbnail(path: path, size: 48), - onLinkTap: (url) => _openUrl(context, url), - resolveFileRef: (p) => _resolveRepoFile(context, p), - onOpenFile: (path, line) => _openFile(context, path, line), - ), + accent: tokens.globalFocus, + label: 'you', + copyText: i.text, + margin: _childMargin, + // Pasted-image @path tokens render as inline thumbnails that open the + // lightbox (T-236/T-254); copyText keeps the original text verbatim. + body: ClideMarkdown( + i.text, + onRecordTap: (id) => _openRecord(context, id), + onImageToken: (path) => ImageThumbnail(path: path, size: 48), + onLinkTap: (url) => _openUrl(context, url), + resolveFileRef: (p) => _resolveRepoFile(context, p), + onOpenFile: (path, line) => _openFile(context, path, line), ), + ), // Sub-agent (sidechain) prose is NOT the main Claude — attribute it to the // agent with a muted accent, never the coral "claude" brand (T-265). The // coral claudeAccent is reserved for the real main-thread Claude. AssistantTextMessage() => ConversationCard( - accent: i.isSidechain ? tokens.globalTextMuted : claudeAccent, - label: i.isSidechain ? 'agent' : 'claude', - copyText: i.text, - margin: _childMargin, - body: ClideMarkdown( - i.text, - onRecordTap: (id) => _openRecord(context, id), - onLinkTap: (url) => _openUrl(context, url), - resolveFileRef: (p) => _resolveRepoFile(context, p), - onOpenFile: (path, line) => _openFile(context, path, line), - ), + accent: i.isSidechain ? tokens.globalTextMuted : claudeAccent, + label: i.isSidechain ? 'agent' : 'claude', + copyText: i.text, + margin: _childMargin, + body: ClideMarkdown( + i.text, + onRecordTap: (id) => _openRecord(context, id), + onLinkTap: (url) => _openUrl(context, url), + resolveFileRef: (p) => _resolveRepoFile(context, p), + onOpenFile: (path, line) => _openFile(context, path, line), ), + ), AssistantThinkingMessage() => ConversationCard( - // Framed + muted like the context card (T-306). - variant: ConversationCardVariant.bordered, - accent: tokens.globalTextMuted, - label: i.isSidechain ? 'agent thinking' : 'thinking', - copyText: i.thinking, - collapsible: true, - collapsedByDefault: true, - collapsedSummary: _firstLine(i.thinking), - margin: _childMargin, - body: ClideText(i.thinking, muted: true, fontSize: clideFontMeta), - ), + // Framed + muted like the context card (T-306). + variant: ConversationCardVariant.bordered, + accent: tokens.globalTextMuted, + label: i.isSidechain ? 'agent thinking' : 'thinking', + copyText: i.thinking, + collapsible: true, + collapsedByDefault: true, + collapsedSummary: _firstLine(i.thinking), + margin: _childMargin, + body: ClideText(i.thinking, muted: true, fontSize: clideFontMeta), + ), AssistantToolUse() => collapseTools ? _toolUseCollapser(i) : _toolContentCard(i), ToolResultMessage() => _toolResult(i), ImageMessage() => _image(context, i), @@ -566,16 +564,13 @@ class _ConversationTurn extends StatelessWidget { image: ClideFileImage(m.path), fit: BoxFit.contain, alignment: Alignment.centerLeft, - errorBuilder: (_, __, ___) => _imagePlaceholder(m.path), + errorBuilder: (_, _, _) => _imagePlaceholder(m.path), ), ), ), ), ), - if (caption != null && caption.isNotEmpty) ...[ - const SizedBox(height: 4), - ClideText(caption, fontSize: clideFontMeta, color: tokens.globalTextMuted), - ], + if (caption != null && caption.isNotEmpty) ...[const SizedBox(height: 4), ClideText(caption, fontSize: clideFontMeta, color: tokens.globalTextMuted)], ], ), ); @@ -583,34 +578,30 @@ class _ConversationTurn extends StatelessWidget { void _openLightbox(BuildContext context, String path) { ClideKernel.of(context).dialog.show( - (ctx, dismiss) => ClideLightbox( - onDismiss: dismiss, - child: Image( - image: ClideFileImage(path), - fit: BoxFit.contain, - errorBuilder: (_, __, ___) => _imagePlaceholder(path), - ), - ), - ); + (ctx, dismiss) => ClideLightbox( + onDismiss: dismiss, + child: Image(image: ClideFileImage(path), fit: BoxFit.contain, errorBuilder: (_, _, _) => _imagePlaceholder(path)), + ), + ); } Widget _imagePlaceholder(String path) => Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: Border.all(color: tokens.panelBorder), - borderRadius: BorderRadius.circular(4), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: tokens.panelBorder), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ClideIcon(PhosphorIcons.byName('image'), size: 16, color: tokens.globalTextMuted), + const SizedBox(width: 8), + Flexible( + child: ClideText('could not load $path', fontSize: clideFontMeta, color: tokens.globalTextMuted, maxLines: 1), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - ClideIcon(PhosphorIcons.byName('image'), size: 16, color: tokens.globalTextMuted), - const SizedBox(width: 8), - Flexible( - child: ClideText('could not load $path', fontSize: clideFontMeta, color: tokens.globalTextMuted, maxLines: 1), - ), - ], - ), - ); + ], + ), + ); /// A standalone tool use (T-305): every tool use is a collapser over a /// one-item list. The collapser carries the echoed last line, the count, and @@ -685,8 +676,15 @@ class _ConversationTurn extends StatelessWidget { // (note E): call input (body) → prompt → returned result. final segments = [ for (final p in promptsByToolUseId[t.toolUseId] ?? const []) - CardSegment(label: 'prompt', child: ClideText(p.text, muted: true, fontSize: clideFontMeta)), - if (succeeded && !(isAgent && hasRun)) CardSegment(label: 'result', child: ClideCodeBlock(source: result.content, language: _resultLanguage(t))), + CardSegment( + label: 'prompt', + child: ClideText(p.text, muted: true, fontSize: clideFontMeta), + ), + if (succeeded && !(isAgent && hasRun)) + CardSegment( + label: 'result', + child: ClideCodeBlock(source: result.content, language: _resultLanguage(t)), + ), ]; // A resolved permission-prompted call is tinted green if approved / red if @@ -757,12 +755,7 @@ class _ConversationTurn extends StatelessWidget { collapsible: quiet || multiline, collapsedByDefault: quiet, // genuine errors stay expanded; a denial folds collapsedSummary: (quiet || multiline) ? _firstLine(t.content) : null, - body: ClideText( - t.content, - fontSize: clideFontMeta, - fontFamily: clideMonoFamily, - color: quiet ? tokens.globalTextMuted : tokens.statusError, - ), + body: ClideText(t.content, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: quiet ? tokens.globalTextMuted : tokens.statusError), ); } @@ -783,12 +776,7 @@ class _ConversationTurn extends StatelessWidget { collapsedSummary: multiline ? _firstLine(t.content) : null, body: isOutputTool ? ClideCodeBlock(source: t.content, language: 'text') - : ClideText( - t.content, - fontSize: clideFontMeta, - fontFamily: clideMonoFamily, - color: tokens.globalForeground, - ), + : ClideText(t.content, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground), ); } diff --git a/lib/builtin/claude/src/extension.dart b/lib/builtin/claude/src/extension.dart index b6ca7724..37129b35 100644 --- a/lib/builtin/claude/src/extension.dart +++ b/lib/builtin/claude/src/extension.dart @@ -48,270 +48,259 @@ class ClaudeExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'claude.primary', - slot: Slots.workspace, - title: 'Claude', - titleKey: 'tab.title', - i18nNamespace: id, - priority: 90, - build: (_) => TeamPanelHost(lead: ClaudeSessionHost(key: _hostKey)), - ), - CommandContribution( - id: 'claude.new-secondary', - command: 'claude.new-secondary', - title: 'Claude: open a secondary session', - run: (_) async { - _hostKey.currentState?.addSecondary(); - return IpcResponse.ok(id: '', data: const {'status': 'spawned'}); - }, - ), - CommandContribution( - id: 'claude.kill-all-sessions', - command: 'claude.kill-all-sessions', - title: 'Claude: kill all sessions for this repo', - run: _killAllSessions, - ), - CommandContribution( - id: 'claude.session-storage', - command: 'claude.session-storage', - title: 'Claude: session storage (disk usage + cleanup)', - run: _manageStorage, - ), - // T-235: cycle how aggressively the activity card folds meta steps - // (none → tools → thinking → everything), persisted app-wide. The panes - // read kActivityFoldLevelKey and rebuild via the settings notifier. - CommandContribution( - id: 'claude.activity.fold-level', - command: 'claude.activity.fold-level', - title: 'Claude: cycle activity fold level', - run: _cycleFoldLevel, - ), - // T-171: agent roster controls (D-6 CLI/UI parity). - // Usage: clide claude.agent.show - CommandContribution( - id: 'claude.agent.show', - command: 'claude.agent.show', - title: 'Claude: show an agent session pane', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - _orchestrator?.show(id); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'shown'}); - }, - ), - CommandContribution( - id: 'claude.agent.hide', - command: 'claude.agent.hide', - title: 'Claude: hide an agent session pane', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - _orchestrator?.hide(id); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'hidden'}); - }, - ), - CommandContribution( - id: 'claude.agent.close', - command: 'claude.agent.close', - title: 'Claude: close (kill) an agent session', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - await _orchestrator?.close(id); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'closed'}); - }, - ), - CommandContribution( - id: 'claude.agent.mute', - command: 'claude.agent.mute', - title: 'Claude: mute broker delivery to an agent session', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - _orchestrator?.mute(id); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'muted'}); - }, - ), - CommandContribution( - id: 'claude.agent.unmute', - command: 'claude.agent.unmute', - title: 'Claude: unmute broker delivery to an agent session', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - _orchestrator?.unmute(id); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'unmuted'}); - }, - ), - // Usage: clide claude.agent.inject-message - CommandContribution( - id: 'claude.agent.inject-message', - command: 'claude.agent.inject-message', - title: 'Claude: inject a text turn into an agent session', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - final text = args.skip(1).join(' '); - if (text.isEmpty) return IpcResponse.ok(id: '', data: const {'error': 'missing message text'}); - _orchestrator?.injectMessage(id, text); - return IpcResponse.ok(id: '', data: {'id': id, 'status': 'injected'}); - }, - ), - // T-181: set permission mode for an agent session (D-6 CLI/UI parity). - // Usage: clide claude.agent.set-permission-mode - // must be one of: default, acceptEdits, plan, bypassPermissions. - // Note: bypassPermissions is accepted via CLI — the footgun guard is the - // UI's confirm dialog; the CLI caller is responsible for their own safety. - CommandContribution( - id: 'claude.agent.set-permission-mode', - command: 'claude.agent.set-permission-mode', - title: 'Claude: set permission mode for an agent session', - run: (args) async { - final id = args.firstOrNull; - if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); - final mode = args.length >= 2 ? args[1] : null; - if (mode == null) return IpcResponse.ok(id: '', data: const {'error': 'missing mode (default|acceptEdits|plan|bypassPermissions)'}); - const valid = {'default', 'acceptEdits', 'plan', 'bypassPermissions'}; - if (!valid.contains(mode)) { - return IpcResponse.ok(id: '', data: {'error': 'unknown mode "$mode"; use one of: ${valid.join(', ')}'}); - } - _orchestrator?.byId(id)?.session.setPermissionMode(mode); - return IpcResponse.ok(id: '', data: {'id': id, 'mode': mode, 'status': 'sent'}); - }, - ), - // T-226: cycle the primary session's permission mode through the safe - // trio. Palette-discoverable counterpart to the composer's Ctrl/Cmd+M. - CommandContribution( - id: 'claude.mode.cycle', - command: 'claude.mode.cycle', - title: 'Claude: Cycle permission mode', - run: (_) async { - final managed = _orchestrator?.byId('primary'); - if (managed == null) return IpcResponse.ok(id: '', data: const {'error': 'no primary session'}); - final next = nextSafePermissionMode(managed.session.status.permissionMode ?? 'default'); - managed.session.setPermissionMode(next); - return IpcResponse.ok(id: '', data: {'mode': next, 'status': 'sent'}); - }, - ), - // Usage: clide claude.task.reassign - CommandContribution( - id: 'claude.task.reassign', - command: 'claude.task.reassign', - title: 'Claude: reassign a shared task to an agent', - run: (args) async { - if (args.length < 2) return IpcResponse.ok(id: '', data: const {'error': 'usage: '}); - final taskId = args[0]; - final toId = args[1]; - final ok = _orchestrator?.broker.reassignTask(taskId, toId) ?? false; - return IpcResponse.ok(id: '', data: {'taskId': taskId, 'toId': toId, 'ok': ok}); - }, - ), - // T-180: full team chat pane opened as a workspace tab. - // Shares the TeamChatModel with the sidebar widget. - TabContribution( - id: 'claude.team-chat', - slot: Slots.workspace, - title: 'Team Chat', - titleKey: 'tab.title', - i18nNamespace: id, - priority: 85, - build: (_) { - final orch = _orchestrator; - if (orch == null) return const SizedBox.shrink(); - return TeamChatPane(model: orch.chatModel, broker: orch.broker); - }, - ), - // CLI parity: open the team chat pane from the shell. - // Usage: clide claude.team-chat.open - CommandContribution( - id: 'claude.team-chat.open', - command: 'claude.team-chat.open', - title: 'Claude: open the team chat pane', - run: (args) async { - _ctx?.panels.activateTab(Slots.workspace, 'claude.team-chat'); - return IpcResponse.ok(id: '', data: const {'status': 'opened'}); - }, - ), - // Usage: clide claude.team-chat.post [@name] - // Posts a message into the broker channel as the user. - // Leading @name tag selects the recipient; omit for broadcast. - CommandContribution( - id: 'claude.team-chat.post', - command: 'claude.team-chat.post', - title: 'Claude: post a message into the team channel as the user', - run: (args) async { - if (args.isEmpty) return IpcResponse.ok(id: '', data: const {'error': 'usage: [@name] '}); - final raw = args.join(' '); - String? recipient; - String body = raw; - if (raw.startsWith('@')) { - final ws = raw.indexOf(RegExp(r'\s')); - if (ws > 0) { - final tag = raw.substring(1, ws); - recipient = (tag == 'team' || tag.isEmpty) ? null : tag; - body = raw.substring(ws).trim(); - } - } - _orchestrator?.chatModel.postAsUser(body, toName: recipient); - return IpcResponse.ok(id: '', data: {'status': 'posted', if (recipient != null) 'to': recipient}); - }, - ), - // claude.agent.fork: branch a managed session into a new fork session - // (T-172, D-6 CLI/UI parity for the roster fork button). - // Usage: clide claude.agent.fork [] - // : the clide-internal id of the session to fork. - // : optional working directory; defaults to the source session's cwd. - CommandContribution( - id: 'claude.agent.fork', - command: 'claude.agent.fork', - title: 'Claude: fork a managed session into a new branch session', - run: (args) async { - final sourceId = args.firstOrNull; - if (sourceId == null) { - return IpcResponse.ok(id: '', data: const {'error': 'usage: claude.agent.fork []'}); - } - final orch = _orchestrator; - if (orch == null) { - return IpcResponse.ok(id: '', data: const {'error': 'orchestrator unavailable'}); - } - final source = orch.byId(sourceId); - if (source == null) { - return IpcResponse.ok(id: '', data: {'error': 'unknown session "$sourceId"'}); - } - final cwd = args.length >= 2 ? args[1] : source.cwd; - final forkId = 'fork:$sourceId-${DateTime.now().millisecondsSinceEpoch}'; - await orch.spawn(SpawnSpec( - id: forkId, - role: 'fork of $sourceId', - sessionId: forkId, - cwd: cwd, - forkSourceSessionId: source.sessionId, - )); - return IpcResponse.ok(id: '', data: {'forkId': forkId, 'sourceId': sourceId, 'status': 'spawned'}); - }, - ), - // Always-pickable left-panel tab: Claude activity (from - // stats-cache.json) + the team roster when a team is running (T-141). - TabContribution( - id: 'claude.meta', - slot: Slots.sidebar, - title: 'Activity', - icon: PhosphorIcons.byName('robot'), - priority: 60, - build: (_) => const ClaudeMetaSidebar(), - ), - // In-pane status slot (T-145): the active Claude pane publishes - // its model · permission-mode · context line here. - // flex: 1 → StatusbarHost wraps this in Flexible(loose) so the slot - // yields width under pressure and ClideMarquee scrolls (T-160). - StatusItemContribution( - id: 'claude.status-context', - priority: 50, - flex: 1, - build: (_) => const PaneContextStatusItem(), - ), - ]; + TabContribution( + id: 'claude.primary', + slot: Slots.workspace, + title: 'Claude', + titleKey: 'tab.title', + i18nNamespace: id, + priority: 90, + build: (_) => TeamPanelHost(lead: ClaudeSessionHost(key: _hostKey)), + ), + CommandContribution( + id: 'claude.new-secondary', + command: 'claude.new-secondary', + title: 'Claude: open a secondary session', + run: (_) async { + _hostKey.currentState?.addSecondary(); + return IpcResponse.ok(id: '', data: const {'status': 'spawned'}); + }, + ), + CommandContribution( + id: 'claude.kill-all-sessions', + command: 'claude.kill-all-sessions', + title: 'Claude: kill all sessions for this repo', + run: _killAllSessions, + ), + CommandContribution( + id: 'claude.session-storage', + command: 'claude.session-storage', + title: 'Claude: session storage (disk usage + cleanup)', + run: _manageStorage, + ), + // T-235: cycle how aggressively the activity card folds meta steps + // (none → tools → thinking → everything), persisted app-wide. The panes + // read kActivityFoldLevelKey and rebuild via the settings notifier. + CommandContribution( + id: 'claude.activity.fold-level', + command: 'claude.activity.fold-level', + title: 'Claude: cycle activity fold level', + run: _cycleFoldLevel, + ), + // T-171: agent roster controls (D-6 CLI/UI parity). + // Usage: clide claude.agent.show + CommandContribution( + id: 'claude.agent.show', + command: 'claude.agent.show', + title: 'Claude: show an agent session pane', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + _orchestrator?.show(id); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'shown'}); + }, + ), + CommandContribution( + id: 'claude.agent.hide', + command: 'claude.agent.hide', + title: 'Claude: hide an agent session pane', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + _orchestrator?.hide(id); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'hidden'}); + }, + ), + CommandContribution( + id: 'claude.agent.close', + command: 'claude.agent.close', + title: 'Claude: close (kill) an agent session', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + await _orchestrator?.close(id); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'closed'}); + }, + ), + CommandContribution( + id: 'claude.agent.mute', + command: 'claude.agent.mute', + title: 'Claude: mute broker delivery to an agent session', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + _orchestrator?.mute(id); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'muted'}); + }, + ), + CommandContribution( + id: 'claude.agent.unmute', + command: 'claude.agent.unmute', + title: 'Claude: unmute broker delivery to an agent session', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + _orchestrator?.unmute(id); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'unmuted'}); + }, + ), + // Usage: clide claude.agent.inject-message + CommandContribution( + id: 'claude.agent.inject-message', + command: 'claude.agent.inject-message', + title: 'Claude: inject a text turn into an agent session', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + final text = args.skip(1).join(' '); + if (text.isEmpty) return IpcResponse.ok(id: '', data: const {'error': 'missing message text'}); + _orchestrator?.injectMessage(id, text); + return IpcResponse.ok(id: '', data: {'id': id, 'status': 'injected'}); + }, + ), + // T-181: set permission mode for an agent session (D-6 CLI/UI parity). + // Usage: clide claude.agent.set-permission-mode + // must be one of: default, acceptEdits, plan, bypassPermissions. + // Note: bypassPermissions is accepted via CLI — the footgun guard is the + // UI's confirm dialog; the CLI caller is responsible for their own safety. + CommandContribution( + id: 'claude.agent.set-permission-mode', + command: 'claude.agent.set-permission-mode', + title: 'Claude: set permission mode for an agent session', + run: (args) async { + final id = args.firstOrNull; + if (id == null) return IpcResponse.ok(id: '', data: const {'error': 'missing session id'}); + final mode = args.length >= 2 ? args[1] : null; + if (mode == null) return IpcResponse.ok(id: '', data: const {'error': 'missing mode (default|acceptEdits|plan|bypassPermissions)'}); + const valid = {'default', 'acceptEdits', 'plan', 'bypassPermissions'}; + if (!valid.contains(mode)) { + return IpcResponse.ok(id: '', data: {'error': 'unknown mode "$mode"; use one of: ${valid.join(', ')}'}); + } + _orchestrator?.byId(id)?.session.setPermissionMode(mode); + return IpcResponse.ok(id: '', data: {'id': id, 'mode': mode, 'status': 'sent'}); + }, + ), + // T-226: cycle the primary session's permission mode through the safe + // trio. Palette-discoverable counterpart to the composer's Ctrl/Cmd+M. + CommandContribution( + id: 'claude.mode.cycle', + command: 'claude.mode.cycle', + title: 'Claude: Cycle permission mode', + run: (_) async { + final managed = _orchestrator?.byId('primary'); + if (managed == null) return IpcResponse.ok(id: '', data: const {'error': 'no primary session'}); + final next = nextSafePermissionMode(managed.session.status.permissionMode ?? 'default'); + managed.session.setPermissionMode(next); + return IpcResponse.ok(id: '', data: {'mode': next, 'status': 'sent'}); + }, + ), + // Usage: clide claude.task.reassign + CommandContribution( + id: 'claude.task.reassign', + command: 'claude.task.reassign', + title: 'Claude: reassign a shared task to an agent', + run: (args) async { + if (args.length < 2) return IpcResponse.ok(id: '', data: const {'error': 'usage: '}); + final taskId = args[0]; + final toId = args[1]; + final ok = _orchestrator?.broker.reassignTask(taskId, toId) ?? false; + return IpcResponse.ok(id: '', data: {'taskId': taskId, 'toId': toId, 'ok': ok}); + }, + ), + // T-180: full team chat pane opened as a workspace tab. + // Shares the TeamChatModel with the sidebar widget. + TabContribution( + id: 'claude.team-chat', + slot: Slots.workspace, + title: 'Team Chat', + titleKey: 'tab.title', + i18nNamespace: id, + priority: 85, + build: (_) { + final orch = _orchestrator; + if (orch == null) return const SizedBox.shrink(); + return TeamChatPane(model: orch.chatModel, broker: orch.broker); + }, + ), + // CLI parity: open the team chat pane from the shell. + // Usage: clide claude.team-chat.open + CommandContribution( + id: 'claude.team-chat.open', + command: 'claude.team-chat.open', + title: 'Claude: open the team chat pane', + run: (args) async { + _ctx?.panels.activateTab(Slots.workspace, 'claude.team-chat'); + return IpcResponse.ok(id: '', data: const {'status': 'opened'}); + }, + ), + // Usage: clide claude.team-chat.post [@name] + // Posts a message into the broker channel as the user. + // Leading @name tag selects the recipient; omit for broadcast. + CommandContribution( + id: 'claude.team-chat.post', + command: 'claude.team-chat.post', + title: 'Claude: post a message into the team channel as the user', + run: (args) async { + if (args.isEmpty) return IpcResponse.ok(id: '', data: const {'error': 'usage: [@name] '}); + final raw = args.join(' '); + String? recipient; + String body = raw; + if (raw.startsWith('@')) { + final ws = raw.indexOf(RegExp(r'\s')); + if (ws > 0) { + final tag = raw.substring(1, ws); + recipient = (tag == 'team' || tag.isEmpty) ? null : tag; + body = raw.substring(ws).trim(); + } + } + _orchestrator?.chatModel.postAsUser(body, toName: recipient); + return IpcResponse.ok(id: '', data: {'status': 'posted', 'to': ?recipient}); + }, + ), + // claude.agent.fork: branch a managed session into a new fork session + // (T-172, D-6 CLI/UI parity for the roster fork button). + // Usage: clide claude.agent.fork [] + // : the clide-internal id of the session to fork. + // : optional working directory; defaults to the source session's cwd. + CommandContribution( + id: 'claude.agent.fork', + command: 'claude.agent.fork', + title: 'Claude: fork a managed session into a new branch session', + run: (args) async { + final sourceId = args.firstOrNull; + if (sourceId == null) { + return IpcResponse.ok(id: '', data: const {'error': 'usage: claude.agent.fork []'}); + } + final orch = _orchestrator; + if (orch == null) { + return IpcResponse.ok(id: '', data: const {'error': 'orchestrator unavailable'}); + } + final source = orch.byId(sourceId); + if (source == null) { + return IpcResponse.ok(id: '', data: {'error': 'unknown session "$sourceId"'}); + } + final cwd = args.length >= 2 ? args[1] : source.cwd; + final forkId = 'fork:$sourceId-${DateTime.now().millisecondsSinceEpoch}'; + await orch.spawn(SpawnSpec(id: forkId, role: 'fork of $sourceId', sessionId: forkId, cwd: cwd, forkSourceSessionId: source.sessionId)); + return IpcResponse.ok(id: '', data: {'forkId': forkId, 'sourceId': sourceId, 'status': 'spawned'}); + }, + ), + // Always-pickable left-panel tab: Claude activity (from + // stats-cache.json) + the team roster when a team is running (T-141). + TabContribution( + id: 'claude.meta', + slot: Slots.sidebar, + title: 'Activity', + icon: PhosphorIcons.byName('robot'), + priority: 60, + build: (_) => const ClaudeMetaSidebar(), + ), + // In-pane status slot (T-145): the active Claude pane publishes + // its model · permission-mode · context line here. + // flex: 1 → StatusbarHost wraps this in Flexible(loose) so the slot + // yields width under pressure and ClideMarquee scrolls (T-160). + StatusItemContribution(id: 'claude.status-context', priority: 50, flex: 1, build: (_) => const PaneContextStatusItem()), + ]; @override Future activate(ClideExtensionContext ctx) async { @@ -399,13 +388,15 @@ class ClaudeExtension extends ClideExtension { } final target = _orchestrator?.byId('primary') ?? _orchestrator?.visibleSessions.firstOrNull; if (target == null) return; - target.conversation.inject(ImageMessage( - uuid: 'image-${DateTime.now().microsecondsSinceEpoch}', - timestamp: DateTime.now(), - isSidechain: false, - path: path, - caption: m.data['caption'] as String?, - )); + target.conversation.inject( + ImageMessage( + uuid: 'image-${DateTime.now().microsecondsSinceEpoch}', + timestamp: DateTime.now(), + isSidechain: false, + path: path, + caption: m.data['caption'] as String?, + ), + ); } @override @@ -466,9 +457,7 @@ class ClaudeExtension extends ClideExtension { if (root == null || home == null) return IpcResponse.ok(id: '', data: const {}); final dir = Directory('$home/.claude/projects/${root.replaceAll('/', '-')}'); final sessions = await listSessions(dir); - await ctx.dialog.show( - (c, dismiss) => SessionStorageDialog(dir: dir, sessions: sessions, onClose: dismiss), - ); + await ctx.dialog.show((c, dismiss) => SessionStorageDialog(dir: dir, sessions: sessions, onClose: dismiss)); return IpcResponse.ok(id: '', data: const {'status': 'shown'}); } } diff --git a/lib/builtin/claude/src/image_thumbnail.dart b/lib/builtin/claude/src/image_thumbnail.dart index 1cfa6d87..cb18d319 100644 --- a/lib/builtin/claude/src/image_thumbnail.dart +++ b/lib/builtin/claude/src/image_thumbnail.dart @@ -17,15 +17,11 @@ import 'package:flutter/widgets.dart'; /// Open [path] full-size in the lightbox via the kernel dialog router. void openImageLightbox(BuildContext context, String path) { ClideKernel.of(context).dialog.show( - (ctx, dismiss) => ClideLightbox( - onDismiss: dismiss, - child: Image( - image: ClideFileImage(path), - fit: BoxFit.contain, - errorBuilder: (ctx, _, __) => _placeholder(ctx, 48), - ), - ), - ); + (ctx, dismiss) => ClideLightbox( + onDismiss: dismiss, + child: Image(image: ClideFileImage(path), fit: BoxFit.contain, errorBuilder: (ctx, _, _) => _placeholder(ctx, 48)), + ), + ); } Widget _placeholder(BuildContext context, double size) { @@ -64,13 +60,7 @@ class ImageThumbnail extends StatelessWidget { ), child: ClipRRect( borderRadius: BorderRadius.circular(radius), - child: Image( - image: ClideFileImage(path), - width: size, - height: size, - fit: BoxFit.cover, - errorBuilder: (ctx, _, __) => _placeholder(ctx, size), - ), + child: Image(image: ClideFileImage(path), width: size, height: size, fit: BoxFit.cover, errorBuilder: (ctx, _, _) => _placeholder(ctx, size)), ), ), ), diff --git a/lib/builtin/claude/src/permission_mode_control.dart b/lib/builtin/claude/src/permission_mode_control.dart index 1f0befe1..b056a3e1 100644 --- a/lib/builtin/claude/src/permission_mode_control.dart +++ b/lib/builtin/claude/src/permission_mode_control.dart @@ -67,24 +67,24 @@ class _PermissionModeControlState extends State { } List _entries(SurfaceTokens tokens) => [ - for (final m in kSafePermissionCycle) - ClideMenuItem( - leading: permissionModeIcon(m), - color: permissionModeColor(m, tokens), - label: permissionModeLabel(m), - active: m == widget.mode, - onSelect: () => widget.onSelect(m), - ), - const ClideMenuSeparator(), - ClideMenuItem( - leading: permissionModeIcon('bypassPermissions'), - color: permissionModeColor('bypassPermissions', tokens), - label: permissionModeLabel('bypassPermissions'), - enabled: false, - active: widget.mode == 'bypassPermissions', - onSelect: () {}, - ), - ]; + for (final m in kSafePermissionCycle) + ClideMenuItem( + leading: permissionModeIcon(m), + color: permissionModeColor(m, tokens), + label: permissionModeLabel(m), + active: m == widget.mode, + onSelect: () => widget.onSelect(m), + ), + const ClideMenuSeparator(), + ClideMenuItem( + leading: permissionModeIcon('bypassPermissions'), + color: permissionModeColor('bypassPermissions', tokens), + label: permissionModeLabel('bypassPermissions'), + enabled: false, + active: widget.mode == 'bypassPermissions', + onSelect: () {}, + ), + ]; @override Widget build(BuildContext context) { @@ -94,11 +94,7 @@ class _PermissionModeControlState extends State { side: ClideAnchorSide.above, align: ClideAnchorAlign.end, offset: const Offset(0, -6), - overlayBuilder: (ctx, ctrl) => ClideMenu( - onClose: ctrl.close, - minWidth: 180, - entries: _entries(ClideTheme.of(ctx).surface), - ), + overlayBuilder: (ctx, ctrl) => ClideMenu(onClose: ctrl.close, minWidth: 180, entries: _entries(ClideTheme.of(ctx).surface)), anchor: ListenableBuilder( listenable: _overlay, builder: (ctx, _) { @@ -116,9 +112,7 @@ class _PermissionModeControlState extends State { alignment: Alignment.center, decoration: BoxDecoration( color: hovered ? tokens.listItemHoverBackground : null, - border: Border.all( - color: open ? tokens.globalFocus : (hovered ? tokens.panelActiveBorder : tokens.globalBorder), - ), + border: Border.all(color: open ? tokens.globalFocus : (hovered ? tokens.panelActiveBorder : tokens.globalBorder)), borderRadius: BorderRadius.circular(4), ), child: ClideIcon(permissionModeIcon(widget.mode), size: 16, color: permissionModeColor(widget.mode, tokens)), diff --git a/lib/builtin/claude/src/prompt_card.dart b/lib/builtin/claude/src/prompt_card.dart index 9b8deeb2..2d7593b5 100644 --- a/lib/builtin/claude/src/prompt_card.dart +++ b/lib/builtin/claude/src/prompt_card.dart @@ -27,7 +27,8 @@ const _kOther = '\u0000other'; /// Preformatted note for the "Deny & simplify" permission option (T-311): deny /// THIS action and ask Claude to reformulate it more simply, explicitly without /// touching the permission surface (memories / settings). -const _kDenySimplifyNote = 'Denied — this action is too complex for the permission system to approve cleanly. ' +const _kDenySimplifyNote = + 'Denied — this action is too complex for the permission system to approve cleanly. ' 'Please retry with a simpler, more granular approach (break it into smaller steps or use a plainer command) ' 'to avoid this permission prompt. This is a one-off for THIS action only: do not add a memory and do not ' 'change permission settings — just reformulate and try again. Do not narrate or explain the change; ' @@ -295,11 +296,13 @@ class _ToolPromptCardState extends State { [ if (_q.isNotEmpty) _questionBody(tokens, 0), const SizedBox(height: 6), - Row(children: [ - ClideButton(label: 'Submit', variant: ClideButtonVariant.primary, onPressed: (_q.isNotEmpty && _answer(0).isNotEmpty) ? _submit : null), - const Spacer(), - _chatInstead(tokens), - ]), + Row( + children: [ + ClideButton(label: 'Submit', variant: ClideButtonVariant.primary, onPressed: (_q.isNotEmpty && _answer(0).isNotEmpty) ? _submit : null), + const Spacer(), + _chatInstead(tokens), + ], + ), ], ); } @@ -312,11 +315,13 @@ class _ToolPromptCardState extends State { const SizedBox(height: 8), for (var i = 0; i < _q.length; i++) _reviewRow(tokens, i), const SizedBox(height: 6), - Row(children: [ - ClideButton(label: '‹ Back', onPressed: () => setState(() => _step = _q.length - 1)), - const SizedBox(width: 8), - ClideButton(label: 'Submit answers', variant: ClideButtonVariant.primary, onPressed: _allAnswered ? _submit : null), - ]), + Row( + children: [ + ClideButton(label: '‹ Back', onPressed: () => setState(() => _step = _q.length - 1)), + const SizedBox(width: 8), + ClideButton(label: 'Submit answers', variant: ClideButtonVariant.primary, onPressed: _allAnswered ? _submit : null), + ], + ), ], ); } @@ -330,19 +335,14 @@ class _ToolPromptCardState extends State { const SizedBox(height: 10), _questionBody(tokens, _step), const SizedBox(height: 6), - Row(children: [ - if (_step > 0) ...[ - ClideButton(label: '‹ Back', onPressed: () => setState(() => _step--)), - const SizedBox(width: 8), + Row( + children: [ + if (_step > 0) ...[ClideButton(label: '‹ Back', onPressed: () => setState(() => _step--)), const SizedBox(width: 8)], + ClideButton(label: last ? 'Review ›' : 'Next ›', variant: ClideButtonVariant.primary, onPressed: answered ? () => setState(() => _step++) : null), + const Spacer(), + _chatInstead(tokens), ], - ClideButton( - label: last ? 'Review ›' : 'Next ›', - variant: ClideButtonVariant.primary, - onPressed: answered ? () => setState(() => _step++) : null, - ), - const Spacer(), - _chatInstead(tokens), - ]), + ), ], ); } @@ -354,12 +354,17 @@ class _ToolPromptCardState extends State { final done = _answer(i).isNotEmpty; final text = '${i + 1} · $head${done ? ' ✓' : ''}'; if (i == _step) { - chips.add(Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - decoration: BoxDecoration( - color: tokens.globalFocus.withValues(alpha: 0.18), border: Border.all(color: tokens.statusInfo), borderRadius: BorderRadius.circular(4)), - child: ClideText(text, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground), - )); + chips.add( + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: tokens.globalFocus.withValues(alpha: 0.18), + border: Border.all(color: tokens.statusInfo), + borderRadius: BorderRadius.circular(4), + ), + child: ClideText(text, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground), + ), + ); } else { chips.add(ClideText(text, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: done ? tokens.statusSuccess : tokens.globalTextMuted)); } @@ -376,8 +381,13 @@ class _ToolPromptCardState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(width: 110, child: ClideText(head, fontSize: clideFontMeta, color: tokens.globalTextMuted)), - Expanded(child: ClideText('→ ${_answer(qi)}', fontSize: clideFontSmall, color: tokens.globalForeground)), + SizedBox( + width: 110, + child: ClideText(head, fontSize: clideFontMeta, color: tokens.globalTextMuted), + ), + Expanded( + child: ClideText('→ ${_answer(qi)}', fontSize: clideFontSmall, color: tokens.globalForeground), + ), ], ), ); @@ -403,10 +413,7 @@ class _ToolPromptCardState extends State { _optButton(qi, _kOther, 'Other…', q.multiSelect, '', q.options.length + 1), ], ), - if (hasOther) ...[ - const SizedBox(height: 8), - _NoteField(controller: _other[qi], placeholder: 'type your answer…'), - ], + if (hasOther) ...[const SizedBox(height: 8), _NoteField(controller: _other[qi], placeholder: 'type your answer…')], const SizedBox(height: 8), _NoteField(controller: _qnote[qi], placeholder: '+ note (optional)'), ], @@ -499,10 +506,7 @@ Widget toolInputBody(SurfaceTokens tokens, String toolName, Map /// / timeout annotations. Widget toolBashBody(SurfaceTokens tokens, Map input) { final cmd = (input['command'] as String? ?? '').trimRight(); - final notes = [ - if (input['run_in_background'] == true) 'background', - if (input['timeout'] is num) 'timeout ${input['timeout']}ms', - ]; + final notes = [if (input['run_in_background'] == true) 'background', if (input['timeout'] is num) 'timeout ${input['timeout']}ms']; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, @@ -564,19 +568,14 @@ Widget toolReadLikeBody(SurfaceTokens tokens, String toolName, Map s.isNotEmpty).join(' '); - return ClideText( - label.isNotEmpty ? label : toolName, - fontSize: clideFontMeta, - fontFamily: clideMonoFamily, - color: tokens.globalForeground, - ); + return ClideText(label.isNotEmpty ? label : toolName, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalForeground); } /// A muted file path line, shared across tool bodies. Widget toolPathLine(SurfaceTokens tokens, String path) => Padding( - padding: const EdgeInsets.only(bottom: 6), - child: ClideText(path, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted), - ); + padding: const EdgeInsets.only(bottom: 6), + child: ClideText(path, fontSize: clideFontMeta, fontFamily: clideMonoFamily, color: tokens.globalTextMuted), +); // -- shared note / free-text field ------------------------------------------- @@ -613,7 +612,7 @@ class _NoteFieldState extends State<_NoteField> { children: [ ValueListenableBuilder( valueListenable: widget.controller, - builder: (_, v, __) => v.text.isEmpty ? ClideText(widget.placeholder, muted: true, fontSize: clideFontSmall) : const SizedBox.shrink(), + builder: (_, v, _) => v.text.isEmpty ? ClideText(widget.placeholder, muted: true, fontSize: clideFontSmall) : const SizedBox.shrink(), ), EditableText( controller: widget.controller, @@ -652,14 +651,9 @@ List<_Question> _parseQuestions(Map input) { return [ for (final q in raw) if (q is Map) - _Question( - q['question'] as String? ?? '', - q['header'] as String? ?? '', - q['multiSelect'] as bool? ?? false, - [ - for (final o in (q['options'] as List? ?? const [])) - if (o is Map) _Option(o['label'] as String? ?? '', o['description'] as String? ?? ''), - ], - ), + _Question(q['question'] as String? ?? '', q['header'] as String? ?? '', q['multiSelect'] as bool? ?? false, [ + for (final o in (q['options'] as List? ?? const [])) + if (o is Map) _Option(o['label'] as String? ?? '', o['description'] as String? ?? ''), + ]), ]; } diff --git a/lib/builtin/claude/src/session_index.dart b/lib/builtin/claude/src/session_index.dart index 558356b5..270fccbb 100644 --- a/lib/builtin/claude/src/session_index.dart +++ b/lib/builtin/claude/src/session_index.dart @@ -13,13 +13,7 @@ import 'dart:io'; /// One session in the workspace, summarised for the picker. class SessionSummary { - const SessionSummary({ - required this.id, - required this.modified, - this.firstUser, - this.lastUser, - this.sizeBytes = 0, - }); + const SessionSummary({required this.id, required this.modified, this.firstUser, this.lastUser, this.sizeBytes = 0}); /// The session id (the `` of `.jsonl`). final String id; @@ -94,11 +88,7 @@ String? _userTextOf(String line) { /// Sessions in [dir] (the munged project dir), most-recently-modified first, /// capped at [max]. Each is summarised by bookend user prompts read from a /// bounded [window] at each end of its transcript. -Future> listSessions( - Directory dir, { - int max = 20, - int window = 128 * 1024, -}) async { +Future> listSessions(Directory dir, {int max = 20, int window = 128 * 1024}) async { if (!await dir.exists()) return const []; final files = []; await for (final e in dir.list(followLinks: false)) { @@ -109,13 +99,15 @@ Future> listSessions( final stat = await f.stat(); final bookends = await _bookends(f, window); final id = _sessionId(f.path); - summaries.add(SessionSummary( - id: id, - modified: stat.modified, - firstUser: bookends.first, - lastUser: bookends.last, - sizeBytes: stat.size + await _dirSize(Directory('${dir.path}/$id')), - )); + summaries.add( + SessionSummary( + id: id, + modified: stat.modified, + firstUser: bookends.first, + lastUser: bookends.last, + sizeBytes: stat.size + await _dirSize(Directory('${dir.path}/$id')), + ), + ); } summaries.sort((a, b) => b.modified.compareTo(a.modified)); return summaries.length > max ? summaries.sublist(0, max) : summaries; diff --git a/lib/builtin/claude/src/session_orchestrator.dart b/lib/builtin/claude/src/session_orchestrator.dart index c5028ec3..98125c08 100644 --- a/lib/builtin/claude/src/session_orchestrator.dart +++ b/lib/builtin/claude/src/session_orchestrator.dart @@ -32,11 +32,7 @@ const _resumeTailBytes = 256 * 1024; /// Creates the subprocess for a session — production uses /// [ClaudeStreamJsonProcess.start]; tests inject a fake. -typedef ProcessFactory = Future Function({ - required List sessionArgs, - required String cwd, - Map? env, -}); +typedef ProcessFactory = Future Function({required List sessionArgs, required String cwd, Map? env}); /// What to spawn. [id] is the orchestrator's stable key (e.g. `primary`, /// `teammate:tyre`); [sessionId] is claude's `--session-id`. @@ -150,10 +146,7 @@ ClaudeSessionOrchestrator? activeSessionOrchestrator; class ClaudeSessionOrchestrator extends ChangeNotifier { ClaudeSessionOrchestrator({ProcessFactory? processFactory}) : _factory = processFactory ?? _spawnClaude { - _chatModel = TeamChatModel( - broker: broker, - sessionResolver: (name) => byMemberName(name)?.session, - ); + _chatModel = TeamChatModel(broker: broker, sessionResolver: (name) => byMemberName(name)?.session); } final ProcessFactory _factory; @@ -181,9 +174,9 @@ class ClaudeSessionOrchestrator extends ChangeNotifier { /// Just the sessions a pane should currently render. List get visibleSessions => [ - for (final m in _sessions.values) - if (m.visible) m, - ]; + for (final m in _sessions.values) + if (m.visible) m, + ]; ManagedSession? byId(String id) => _sessions[id]; @@ -224,18 +217,9 @@ class ClaudeSessionOrchestrator extends ChangeNotifier { preambles.add(_teamSystemPrompt(name, spec.role)); } final bootstrap = agentBootstrap(spec.cwd, base: spec.env); - sessionArgs = [ - '--append-system-prompt', - preambles.join('\n\n'), - ...bootstrap.extraArgs, - ...sessionArgs, - ]; + sessionArgs = ['--append-system-prompt', preambles.join('\n\n'), ...bootstrap.extraArgs, ...sessionArgs]; - final proc = await _factory( - sessionArgs: sessionArgs, - cwd: spec.cwd, - env: bootstrap.envDelta, - ); + final proc = await _factory(sessionArgs: sessionArgs, cwd: spec.cwd, env: bootstrap.envDelta); final session = StreamJsonSession(proc, mcpServers: mcpServers)..start(); final seed = spec.resume && spec.transcriptPath != null ? await _readTranscriptTail(spec.transcriptPath!) : null; final conversation = ConversationController(stream: session.items, seed: seed, onDispose: session.dispose); @@ -362,7 +346,8 @@ class ClaudeSessionOrchestrator extends ChangeNotifier { } /// The team-awareness preamble injected via `--append-system-prompt` (T-170). - static String _teamSystemPrompt(String name, String role) => 'You are part of a clide-managed agent team. Your name is "$name" and your role is "$role". ' + static String _teamSystemPrompt(String name, String role) => + 'You are part of a clide-managed agent team. Your name is "$name" and your role is "$role". ' 'Coordinate with teammates using the clide-team MCP tools: ' 'send_message(to, text) to message one teammate by name, broadcast(text) to message all, ' 'list_teammates() to see the roster, inbox() to read messages sent to you, and ' diff --git a/lib/builtin/claude/src/session_picker.dart b/lib/builtin/claude/src/session_picker.dart index bae46158..7145508b 100644 --- a/lib/builtin/claude/src/session_picker.dart +++ b/lib/builtin/claude/src/session_picker.dart @@ -12,12 +12,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class SessionPickerDialog extends StatefulWidget { - const SessionPickerDialog({ - super.key, - required this.sessions, - required this.onPick, - required this.onCancel, - }); + const SessionPickerDialog({super.key, required this.sessions, required this.onPick, required this.onCancel}); final List sessions; final void Function(String id) onPick; @@ -92,11 +87,7 @@ class _SessionPickerDialogState extends State { ) else Flexible( - child: ListView.builder( - shrinkWrap: true, - itemCount: widget.sessions.length, - itemBuilder: (ctx, i) => _row(theme, i), - ), + child: ListView.builder(shrinkWrap: true, itemCount: widget.sessions.length, itemBuilder: (ctx, i) => _row(theme, i)), ), ], ), @@ -117,13 +108,7 @@ class _SessionPickerDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClideText( - s.label, - fontSize: clideFontSmall, - color: theme.globalForeground, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + ClideText(s.label, fontSize: clideFontSmall, color: theme.globalForeground, maxLines: 2, overflow: TextOverflow.ellipsis), const SizedBox(height: 2), ClideText(relativeTime(s.modified), muted: true, fontSize: clideFontSmall), ], diff --git a/lib/builtin/claude/src/session_storage.dart b/lib/builtin/claude/src/session_storage.dart index fda55c8d..4b9b93c8 100644 --- a/lib/builtin/claude/src/session_storage.dart +++ b/lib/builtin/claude/src/session_storage.dart @@ -17,13 +17,7 @@ import 'package:flutter/widgets.dart'; typedef SessionDeleter = Future Function(Directory dir, String id); class SessionStorageDialog extends StatefulWidget { - const SessionStorageDialog({ - super.key, - required this.dir, - required this.sessions, - required this.onClose, - this.deleter = deleteSession, - }); + const SessionStorageDialog({super.key, required this.dir, required this.sessions, required this.onClose, this.deleter = deleteSession}); final Directory dir; final List sessions; @@ -79,19 +73,11 @@ class _SessionStorageDialogState extends State { children: [ Padding( padding: const EdgeInsets.fromLTRB(14, 12, 14, 4), - child: ClideText( - 'Session storage · ${formatBytes(_total)} total', - fontSize: clideFontBody, - color: theme.globalForeground, - ), + child: ClideText('Session storage · ${formatBytes(_total)} total', fontSize: clideFontBody, color: theme.globalForeground), ), Padding( padding: const EdgeInsets.fromLTRB(14, 0, 14, 8), - child: ClideText( - 'Deleting a session you are currently using will break that pane.', - muted: true, - fontSize: clideFontSmall, - ), + child: ClideText('Deleting a session you are currently using will break that pane.', muted: true, fontSize: clideFontSmall), ), if (_sessions.isEmpty) Padding( @@ -100,11 +86,7 @@ class _SessionStorageDialogState extends State { ) else Flexible( - child: ListView.builder( - shrinkWrap: true, - itemCount: _sessions.length, - itemBuilder: (ctx, i) => _row(theme, _sessions[i]), - ), + child: ListView.builder(shrinkWrap: true, itemCount: _sessions.length, itemBuilder: (ctx, i) => _row(theme, _sessions[i])), ), ], ), diff --git a/lib/builtin/claude/src/stream_json_session.dart b/lib/builtin/claude/src/stream_json_session.dart index d34091ac..942be21a 100644 --- a/lib/builtin/claude/src/stream_json_session.dart +++ b/lib/builtin/claude/src/stream_json_session.dart @@ -40,11 +40,7 @@ class ClaudeStreamJsonProcess implements StreamJsonProcess { /// Spawn `claude` in stream-json mode. [sessionArgs] is `['--session-id', id]` /// for a new session or `['--resume', id]` to resume an existing one (T-161). - static Future start({ - required List sessionArgs, - required String cwd, - Map? env, - }) async { + static Future start({required List sessionArgs, required String cwd, Map? env}) async { final proc = await Process.start( 'claude', [ @@ -176,10 +172,10 @@ final class AllowTool extends ToolDecision { final String? followUpNote; @override Map toJson() => { - 'behavior': 'allow', - 'updatedInput': updatedInput, - if (updatedPermissions != null && updatedPermissions!.isNotEmpty) 'updatedPermissions': updatedPermissions, - }; + 'behavior': 'allow', + 'updatedInput': updatedInput, + if (updatedPermissions != null && updatedPermissions!.isNotEmpty) 'updatedPermissions': updatedPermissions, + }; } /// Deny the tool with a user-facing [message] (required by the protocol). @@ -310,15 +306,17 @@ class StreamJsonSession { // makes claude drive their JSON-RPC over `mcp_message` (T-170). Only sent // when we actually host a server, so a plain session is unchanged. if (_mcpServers.isNotEmpty) { - _proc.writeLine(jsonEncode({ - 'type': 'control_request', - 'request_id': 'init-${_localSeq++}', - 'request': { - 'subtype': 'initialize', - 'hooks': {}, - 'sdkMcpServers': [for (final s in _mcpServers) s.name], - }, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'init-${_localSeq++}', + 'request': { + 'subtype': 'initialize', + 'hooks': {}, + 'sdkMcpServers': [for (final s in _mcpServers) s.name], + }, + }), + ); } } @@ -448,15 +446,17 @@ class StreamJsonSession { final input = (request['input'] as Map?)?.cast() ?? {}; final tuid = request['tool_use_id'] as String? ?? ''; if (tuid.isNotEmpty) _promptedToolUses.add(tuid); - _queue.add(ToolPrompt( - promptId: rid, - toolName: toolName, - displayName: request['display_name'] as String? ?? toolName, - description: request['description'] as String?, - toolUseId: request['tool_use_id'] as String? ?? '', - input: input, - permissionSuggestions: (request['permission_suggestions'] as List?) ?? const [], - )); + _queue.add( + ToolPrompt( + promptId: rid, + toolName: toolName, + displayName: request['display_name'] as String? ?? toolName, + description: request['description'] as String?, + toolUseId: request['tool_use_id'] as String? ?? '', + input: input, + permissionSuggestions: (request['permission_suggestions'] as List?) ?? const [], + ), + ); _pendingCtl.add(pendingPrompt); return; // awaits resolvePrompt } @@ -465,10 +465,12 @@ class StreamJsonSession { unawaited(_handleMcpMessage(rid, request.cast())); return; } - _proc.writeLine(jsonEncode({ - 'type': 'control_response', - 'response': {'subtype': 'error', 'request_id': rid, 'error': 'Unsupported control request subtype: ${request['subtype']}'}, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_response', + 'response': {'subtype': 'error', 'request_id': rid, 'error': 'Unsupported control request subtype: ${request['subtype']}'}, + }), + ); } /// Answer an `mcp_message` control_request: dispatch its JSON-RPC to the named @@ -489,14 +491,16 @@ class StreamJsonSession { } else { mcpResponse = await _dispatchMcp(server, message); } - _proc.writeLine(jsonEncode({ - 'type': 'control_response', - 'response': { - 'subtype': 'success', - 'request_id': rid, - 'response': {'mcp_response': mcpResponse} - }, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_response', + 'response': { + 'subtype': 'success', + 'request_id': rid, + 'response': {'mcp_response': mcpResponse}, + }, + }), + ); } McpServer? _mcpServerNamed(String? name) { @@ -559,10 +563,12 @@ class StreamJsonSession { _toolUseOutcome[prompt.toolUseId] = decision is AllowTool; if (decision is DenyTool && decision.quiet) _quietErrorToolUses.add(prompt.toolUseId); } - _proc.writeLine(jsonEncode({ - 'type': 'control_response', - 'response': {'subtype': 'success', 'request_id': promptId, 'response': decision.toJson()}, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_response', + 'response': {'subtype': 'success', 'request_id': promptId, 'response': decision.toJson()}, + }), + ); if (decision is AllowTool) { // The prompt card is ephemeral (it vanishes once resolved), so leave a // compact record of an answered question in the conversation log (D-78). @@ -652,16 +658,13 @@ class StreamJsonSession { /// so it renders immediately (stream-json doesn't replay stdin without /// `--replay-user-messages`). void send(String text) { - _proc.writeLine(jsonEncode({ - 'type': 'user', - 'message': {'role': 'user', 'content': text}, - })); - _items.add(UserMessage( - uuid: 'local-${_localSeq++}', - timestamp: DateTime.now(), - isSidechain: false, - text: text, - )); + _proc.writeLine( + jsonEncode({ + 'type': 'user', + 'message': {'role': 'user', 'content': text}, + }), + ); + _items.add(UserMessage(uuid: 'local-${_localSeq++}', timestamp: DateTime.now(), isSidechain: false, text: text)); _setBusy(true); } @@ -669,11 +672,13 @@ class StreamJsonSession { /// the `interrupt` control_request; claude cancels the current turn and ends /// it with a `result`, which clears [busy]. Safe to call when idle. void interrupt() { - _proc.writeLine(jsonEncode({ - 'type': 'control_request', - 'request_id': 'interrupt-${_localSeq++}', - 'request': {'subtype': 'interrupt'}, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'interrupt-${_localSeq++}', + 'request': {'subtype': 'interrupt'}, + }), + ); } /// Set the session's permission mode (T-181, D-77). Sends a @@ -685,11 +690,13 @@ class StreamJsonSession { /// cockpit badge's plain click; bypassPermissions is reachable only via a /// confirmed shift-click (T-181). void setPermissionMode(String mode) { - _proc.writeLine(jsonEncode({ - 'type': 'control_request', - 'request_id': 'set-perm-${_localSeq++}', - 'request': {'subtype': 'set_permission_mode', 'mode': mode}, - })); + _proc.writeLine( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'set-perm-${_localSeq++}', + 'request': {'subtype': 'set_permission_mode', 'mode': mode}, + }), + ); // Optimistically reflect the change so the badge / status line update // immediately (T-250) — the control_request emits no status event, and a // fresh system/init only arrives later. The next init reconciles if the diff --git a/lib/builtin/claude/src/task_list.dart b/lib/builtin/claude/src/task_list.dart index 25b0c06a..e62f8e5a 100644 --- a/lib/builtin/claude/src/task_list.dart +++ b/lib/builtin/claude/src/task_list.dart @@ -47,7 +47,7 @@ List taskListFrom(List items) { } TaskStatus _statusFrom(Object? raw) => switch (raw) { - 'in_progress' => TaskStatus.inProgress, - 'completed' => TaskStatus.completed, - _ => TaskStatus.pending, - }; + 'in_progress' => TaskStatus.inProgress, + 'completed' => TaskStatus.completed, + _ => TaskStatus.pending, +}; diff --git a/lib/builtin/claude/src/team_broker.dart b/lib/builtin/claude/src/team_broker.dart index c5a7fc8e..9dbad0f1 100644 --- a/lib/builtin/claude/src/team_broker.dart +++ b/lib/builtin/claude/src/team_broker.dart @@ -35,13 +35,7 @@ class TeamMemberRef { /// A message left for a member, in arrival order. class TeamMessage { - const TeamMessage({ - required this.from, - required this.text, - required this.at, - this.to, - this.broadcast = false, - }); + const TeamMessage({required this.from, required this.text, required this.at, this.to, this.broadcast = false}); final String from; /// Recipient name: a single member's display name (direct message), `null` @@ -52,13 +46,7 @@ class TeamMessage { final DateTime at; final bool broadcast; - Map toJson() => { - 'from': from, - if (to != null) 'to': to, - 'text': text, - 'at': at.toIso8601String(), - if (broadcast) 'broadcast': true, - }; + Map toJson() => {'from': from, if (to != null) 'to': to, 'text': text, 'at': at.toIso8601String(), if (broadcast) 'broadcast': true}; } /// A shared task. Status is one of `open` / `claimed` / `done`. @@ -69,12 +57,7 @@ class TeamTask { String status; String? owner; - Map toJson() => { - 'id': id, - 'title': title, - 'status': status, - if (owner != null) 'owner': owner, - }; + Map toJson() => {'id': id, 'title': title, 'status': status, if (owner != null) 'owner': owner}; } /// Pushes [text] into the member identified by [toMemberId] as a user message @@ -304,7 +287,7 @@ class TeamBroker { return {'ok': true, 'task': t.toJson()}; } return { - 'tasks': [for (final t in _tasks.values) t.toJson()] + 'tasks': [for (final t in _tasks.values) t.toJson()], }; } @@ -362,25 +345,26 @@ class TeamMcpServer implements McpServer { return _result(broker.claimTask(memberId, id: arguments['id'] as String?, title: arguments['title'] as String?)); case 'task_status': return _result( - broker.taskStatus(memberId, id: arguments['id'] as String?, status: arguments['status'] as String?, title: arguments['title'] as String?)); + broker.taskStatus(memberId, id: arguments['id'] as String?, status: arguments['status'] as String?, title: arguments['title'] as String?), + ); default: return _error('Unknown team tool: $name'); } } Map _result(Map value) => { - 'content': [ - {'type': 'text', 'text': jsonEncode(value)}, - ], - 'isError': value['ok'] == false, - }; + 'content': [ + {'type': 'text', 'text': jsonEncode(value)}, + ], + 'isError': value['ok'] == false, + }; Map _error(String message) => { - 'content': [ - {'type': 'text', 'text': message}, - ], - 'isError': true, - }; + 'content': [ + {'type': 'text', 'text': message}, + ], + 'isError': true, + }; } const _toolDefs = >[ diff --git a/lib/builtin/claude/src/team_chat_model.dart b/lib/builtin/claude/src/team_chat_model.dart index 8df1dd81..b140c038 100644 --- a/lib/builtin/claude/src/team_chat_model.dart +++ b/lib/builtin/claude/src/team_chat_model.dart @@ -29,11 +29,7 @@ typedef SessionResolver = StreamJsonSession? Function(String memberName); /// Lifetime matches the orchestrator: created once, subscribed to the broker, /// disposed when the orchestrator is torn down. class TeamChatModel { - TeamChatModel({ - required TeamBroker broker, - SessionResolver? sessionResolver, - }) : _broker = broker, - _sessionResolver = sessionResolver { + TeamChatModel({required TeamBroker broker, SessionResolver? sessionResolver}) : _broker = broker, _sessionResolver = sessionResolver { _sub = broker.messages.listen(_onMessage); } diff --git a/lib/builtin/claude/src/team_chat_sidebar.dart b/lib/builtin/claude/src/team_chat_sidebar.dart index 530f97f7..49110773 100644 --- a/lib/builtin/claude/src/team_chat_sidebar.dart +++ b/lib/builtin/claude/src/team_chat_sidebar.dart @@ -26,12 +26,7 @@ import 'package:flutter/widgets.dart'; /// [onPopOut] is called when the user taps the pop-out icon to open the full /// pane — the extension wires this to `panels.activateTab`. class TeamChatSidebar extends StatefulWidget { - const TeamChatSidebar({ - super.key, - required this.model, - required this.broker, - required this.onPopOut, - }); + const TeamChatSidebar({super.key, required this.model, required this.broker, required this.onPopOut}); final TeamChatModel model; final TeamBroker broker; @@ -149,11 +144,7 @@ class _TeamChatSidebarState extends State { onTap: widget.onPopOut, builder: (ctx, hovered, _) => Padding( padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1), - child: ClideIcon( - PhosphorIcons.byName('arrows-out-simple'), - size: 10, - color: hovered ? tokens.globalForeground : tokens.globalTextMuted, - ), + child: ClideIcon(PhosphorIcons.byName('arrows-out-simple'), size: 10, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ), @@ -177,13 +168,7 @@ class _TeamChatSidebarState extends State { formatLabel: (n) => '@$n', child: Focus( onKeyEvent: _handleKeyEvent, - child: _ChatInputField( - controller: _controller, - focusNode: _focusNode, - tokens: tokens, - onSubmit: _submit, - placeholder: '@name or @team …', - ), + child: _ChatInputField(controller: _controller, focusNode: _focusNode, tokens: tokens, onSubmit: _submit, placeholder: '@name or @team …'), ), ), ], @@ -200,11 +185,7 @@ class _TeamChatSidebarState extends State { /// Reads from the same [TeamChatModel] as [TeamChatSidebar]. Supports the /// interrupt tickbox and full @-completion. class TeamChatPane extends StatefulWidget { - const TeamChatPane({ - super.key, - required this.model, - required this.broker, - }); + const TeamChatPane({super.key, required this.model, required this.broker}); final TeamChatModel model; final TeamBroker broker; @@ -231,11 +212,7 @@ class _TeamChatPaneState extends State { // Scroll to bottom on new message. WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { - _scrollController.animateTo( - _scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 120), - curve: Curves.easeOut, - ); + _scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 120), curve: Curves.easeOut); } }); } @@ -297,11 +274,7 @@ class _TeamChatPaneState extends State { final text = raw.trim(); if (text.isEmpty) return; final parsed = parseAtTag(text); - widget.model.postAsUser( - parsed.body.isEmpty ? text : parsed.body, - toName: parsed.recipient, - interrupt: _interrupt, - ); + widget.model.postAsUser(parsed.body.isEmpty ? text : parsed.body, toName: parsed.recipient, interrupt: _interrupt); _controller.clear(); } @@ -340,11 +313,7 @@ class _TeamChatPaneState extends State { controller: _scrollController, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), itemCount: messages.length, - itemBuilder: (_, i) => _ChatRow( - key: ValueKey(messages[i].at.microsecondsSinceEpoch), - message: messages[i], - tokens: tokens, - ), + itemBuilder: (_, i) => _ChatRow(key: ValueKey(messages[i].at.microsecondsSinceEpoch), message: messages[i], tokens: tokens), ), ), // Composer + interrupt tickbox. @@ -375,24 +344,13 @@ class _TeamChatPaneState extends State { margin: const EdgeInsets.only(right: 5), decoration: BoxDecoration( color: _interrupt ? tokens.globalFocus.withAlpha(40) : const Color(0x00000000), - border: Border.all( - color: _interrupt ? tokens.globalFocus : tokens.globalTextMuted, - width: 1, - ), + border: Border.all(color: _interrupt ? tokens.globalFocus : tokens.globalTextMuted, width: 1), borderRadius: BorderRadius.circular(2), ), - child: _interrupt - ? Center( - child: ClideIcon(PhosphorIcons.byName('check'), size: 9, color: tokens.globalFocus), - ) - : null, + child: _interrupt ? Center(child: ClideIcon(PhosphorIcons.byName('check'), size: 9, color: tokens.globalFocus)) : null, ), ), - ClideText( - 'Interrupt', - fontSize: clideFontSmall, - color: _interrupt ? tokens.globalForeground : tokens.globalTextMuted, - ), + ClideText('Interrupt', fontSize: clideFontSmall, color: _interrupt ? tokens.globalForeground : tokens.globalTextMuted), ], ), ), @@ -405,13 +363,7 @@ class _TeamChatPaneState extends State { formatLabel: (n) => '@$n', child: Focus( onKeyEvent: _handleKeyEvent, - child: _ChatInputField( - controller: _controller, - focusNode: _focusNode, - tokens: tokens, - onSubmit: _submit, - placeholder: '@name or @team …', - ), + child: _ChatInputField(controller: _controller, focusNode: _focusNode, tokens: tokens, onSubmit: _submit, placeholder: '@name or @team …'), ), ), ], @@ -439,8 +391,8 @@ class _ChatRow extends StatelessWidget { final toLabel = message.broadcast ? '→ all' : message.to != null - ? '→ ${message.to}' - : null; + ? '→ ${message.to}' + : null; return Padding( padding: const EdgeInsets.only(bottom: 3), @@ -451,10 +403,7 @@ class _ChatRow extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), margin: const EdgeInsets.only(right: 5, top: 1), - decoration: BoxDecoration( - color: senderColor.withAlpha(30), - borderRadius: BorderRadius.circular(2), - ), + decoration: BoxDecoration(color: senderColor.withAlpha(30), borderRadius: BorderRadius.circular(2)), child: ClideText(message.from, fontSize: clideFontSmall, color: senderColor), ), if (toLabel != null) @@ -480,13 +429,7 @@ class _ChatRow extends StatelessWidget { /// Inline text input for the chat composer. class _ChatInputField extends StatelessWidget { - const _ChatInputField({ - required this.controller, - required this.focusNode, - required this.tokens, - required this.onSubmit, - required this.placeholder, - }); + const _ChatInputField({required this.controller, required this.focusNode, required this.tokens, required this.onSubmit, required this.placeholder}); final TextEditingController controller; final FocusNode focusNode; @@ -507,12 +450,7 @@ class _ChatInputField extends StatelessWidget { child: EditableText( controller: controller, focusNode: focusNode, - style: TextStyle( - fontFamily: 'JetBrains Mono', - fontSize: clideFontSmall, - color: tokens.globalForeground, - height: 1.4, - ), + style: TextStyle(fontFamily: 'JetBrains Mono', fontSize: clideFontSmall, color: tokens.globalForeground, height: 1.4), cursorColor: tokens.globalFocus, backgroundCursorColor: tokens.globalTextMuted, onSubmitted: onSubmit, diff --git a/lib/builtin/claude/src/team_panel_host.dart b/lib/builtin/claude/src/team_panel_host.dart index c8111f47..05e3c43b 100644 --- a/lib/builtin/claude/src/team_panel_host.dart +++ b/lib/builtin/claude/src/team_panel_host.dart @@ -51,10 +51,7 @@ class _TeamPanelHostState extends State { void _onJoined(TeamMemberJoined m) { if (_controllers.containsKey(m.agentId)) return; final kernel = ClideKernel.of(context); - _controllers[m.agentId] = ConversationController.fromBus( - messages: kernel.messages, - channel: ClaudeConversation.teammateChannel(m.agentId), - ); + _controllers[m.agentId] = ConversationController.fromBus(messages: kernel.messages, channel: ClaudeConversation.teammateChannel(m.agentId)); setState(() => _members.add(m)); } @@ -92,11 +89,7 @@ class _TeamPanelHostState extends State { }), ), Expanded( - child: _TeammateGrid( - members: _members, - controllers: _controllers, - tokens: tokens, - ), + child: _TeammateGrid(members: _members, controllers: _controllers, tokens: tokens), ), ], ); @@ -145,11 +138,7 @@ class _TeammateGrid extends StatelessWidget { children: [ for (var r = 0; r < rows; r++) Expanded( - child: Row( - children: [ - for (var c = 0; c < cols; c++) Expanded(child: _cell(r * cols + c)), - ], - ), + child: Row(children: [for (var c = 0; c < cols; c++) Expanded(child: _cell(r * cols + c))]), ), ], ), diff --git a/lib/builtin/claude/src/ticket_pick_up.dart b/lib/builtin/claude/src/ticket_pick_up.dart index b3c6e7a7..aee0eece 100644 --- a/lib/builtin/claude/src/ticket_pick_up.dart +++ b/lib/builtin/claude/src/ticket_pick_up.dart @@ -36,10 +36,13 @@ Future applyTicketPickUp( final id = data['id'] as String?; final status = data['status'] as String?; if (id != null && id.isNotEmpty && kPickUpStartableStatuses.contains(status)) { - final resp = await ipc.request('pql.tickets.status', args: { - 'ids': [id], - 'status': 'in_progress', - }); + final resp = await ipc.request( + 'pql.tickets.status', + args: { + 'ids': [id], + 'status': 'in_progress', + }, + ); if (resp.ok) messages.publish('builtin.tickets', 'changed', {'id': id}); } return true; diff --git a/lib/builtin/claude/src/transcript_publisher.dart b/lib/builtin/claude/src/transcript_publisher.dart index e9afd985..e74ce8c3 100644 --- a/lib/builtin/claude/src/transcript_publisher.dart +++ b/lib/builtin/claude/src/transcript_publisher.dart @@ -38,11 +38,11 @@ abstract final class ClaudeConversation { /// Encode [status] for [agentId] as a [memberStatusChannel] message body. static Map memberStatusData(String agentId, SessionStatus status) => { - 'agentId': agentId, - if (status.model != null) 'model': status.model, - if (status.permissionMode != null) 'permissionMode': status.permissionMode, - if (status.contextTokens != null) 'contextTokens': status.contextTokens, - }; + 'agentId': agentId, + if (status.model != null) 'model': status.model, + if (status.permissionMode != null) 'permissionMode': status.permissionMode, + if (status.contextTokens != null) 'contextTokens': status.contextTokens, + }; } class TranscriptPublisher { @@ -50,16 +50,11 @@ class TranscriptPublisher { /// [ClaudeConversation.publisher] / [channel]. The subscription is /// attached synchronously, so a controller that subscribes before the /// reader's first poll never misses the initial tail. - TranscriptPublisher({ - required MessageBus messages, - required TranscriptReader reader, - this.channel = ClaudeConversation.leadChannel, - }) : _messages = messages, - _reader = reader { + TranscriptPublisher({required MessageBus messages, required TranscriptReader reader, this.channel = ClaudeConversation.leadChannel}) + : _messages = messages, + _reader = reader { _sub = _reader.stream.listen((item) { - _messages.publish(ClaudeConversation.publisher, channel, { - ClaudeConversation.itemKey: item, - }); + _messages.publish(ClaudeConversation.publisher, channel, {ClaudeConversation.itemKey: item}); }); } diff --git a/lib/builtin/claude/src/transcript_reader.dart b/lib/builtin/claude/src/transcript_reader.dart index 65bebc01..97f86fe5 100644 --- a/lib/builtin/claude/src/transcript_reader.dart +++ b/lib/builtin/claude/src/transcript_reader.dart @@ -171,13 +171,7 @@ final class AssistantToolUse extends ConversationItem { /// driver has already resolved (workspace-relative paths are resolved before /// injection); [caption] is an optional one-line label. final class ImageMessage extends ConversationItem { - const ImageMessage({ - required super.uuid, - required super.timestamp, - required super.isSidechain, - required this.path, - this.caption, - }); + const ImageMessage({required super.uuid, required super.timestamp, required super.isSidechain, required this.path, this.caption}); /// Absolute path to the image file on disk. final String path; @@ -215,14 +209,7 @@ const _isolateParseThreshold = 64 * 1024; const _knownMajorVersions = {1, 2}; /// Record types to skip (do not emit as conversation items). -const _skipTypes = { - 'attachment', - 'system', - 'last-prompt', - 'permission-mode', - 'file-history-snapshot', - 'queue-operation', -}; +const _skipTypes = {'attachment', 'system', 'last-prompt', 'permission-mode', 'file-history-snapshot', 'queue-operation'}; /// Tails Claude Code's transcript JSONL and emits [ConversationItem]s. /// @@ -242,11 +229,11 @@ class TranscriptReader { String? projectsBase, int? initialTailBytes, String? file, - }) : _pollInterval = pollInterval, - _onWarn = onWarn ?? _defaultWarn, - _projectsBase = projectsBase ?? _defaultProjectsBase(), - _initialTailBytes = initialTailBytes ?? _defaultInitialTailBytes, - _explicitFile = file; + }) : _pollInterval = pollInterval, + _onWarn = onWarn ?? _defaultWarn, + _projectsBase = projectsBase ?? _defaultProjectsBase(), + _initialTailBytes = initialTailBytes ?? _defaultInitialTailBytes, + _explicitFile = file; final String workspacePath; final Duration _pollInterval; @@ -439,14 +426,7 @@ class TranscriptReader { /// (T-145, T-168). All fields nullable — a chunk only carries what it saw, /// and the reader [merge]s deltas into a running status. class SessionStatus { - const SessionStatus({ - this.model, - this.permissionMode, - this.contextTokens, - this.cost, - this.contextWindow, - this.rateLimitInfo, - }); + const SessionStatus({this.model, this.permissionMode, this.contextTokens, this.cost, this.contextWindow, this.rateLimitInfo}); /// Assistant `message.model`, e.g. `claude-opus-4-7`. final String? model; @@ -475,13 +455,13 @@ class SessionStatus { /// Overlay [other]'s non-null fields onto this one. SessionStatus merge(SessionStatus other) => SessionStatus( - model: other.model ?? model, - permissionMode: other.permissionMode ?? permissionMode, - contextTokens: other.contextTokens ?? contextTokens, - cost: other.cost ?? cost, - contextWindow: other.contextWindow ?? contextWindow, - rateLimitInfo: other.rateLimitInfo ?? rateLimitInfo, - ); + model: other.model ?? model, + permissionMode: other.permissionMode ?? permissionMode, + contextTokens: other.contextTokens ?? contextTokens, + cost: other.cost ?? cost, + contextWindow: other.contextWindow ?? contextWindow, + rateLimitInfo: other.rateLimitInfo ?? rateLimitInfo, + ); @override bool operator ==(Object other) => @@ -509,13 +489,13 @@ class _StatusAcc { int? contextWindow; String? rateLimitInfo; SessionStatus toStatus() => SessionStatus( - model: model, - permissionMode: permissionMode, - contextTokens: contextTokens, - cost: cost, - contextWindow: contextWindow, - rateLimitInfo: rateLimitInfo, - ); + model: model, + permissionMode: permissionMode, + contextTokens: contextTokens, + cost: cost, + contextWindow: contextWindow, + rateLimitInfo: rateLimitInfo, + ); } // --------------------------------------------------------------------------- @@ -550,8 +530,10 @@ void _parseLineInto(String line, List out, List warnin final majorStr = dotIdx > 0 ? rawVersion.substring(0, dotIdx) : rawVersion; final major = int.tryParse(majorStr); if (major != null && !_knownMajorVersions.contains(major)) { - warnings.add('unfamiliar transcript version "$rawVersion" (major=$major); ' - 'parsing will degrade gracefully'); + warnings.add( + 'unfamiliar transcript version "$rawVersion" (major=$major); ' + 'parsing will degrade gracefully', + ); } } @@ -628,14 +610,17 @@ void _parseUserInto( if (content is String) { if (content.isNotEmpty) { - out.add(UserMessage( + out.add( + UserMessage( uuid: uuid, timestamp: timestamp, isSidechain: isSidechain, parentUuid: parentUuid, parentToolUseId: parentToolUseId, text: content, - injected: injected)); + injected: injected, + ), + ); } return; } @@ -650,16 +635,18 @@ void _parseUserInto( if (text.isNotEmpty) textParts.add(text); case 'tool_result': final rawContent = item['content']; - out.add(ToolResultMessage( - uuid: uuid, - timestamp: timestamp, - isSidechain: isSidechain, - parentUuid: parentUuid, - parentToolUseId: parentToolUseId, - toolUseId: item['tool_use_id'] as String? ?? '', - content: rawContent is String ? rawContent : jsonEncode(rawContent), - isError: item['is_error'] as bool? ?? false, - )); + out.add( + ToolResultMessage( + uuid: uuid, + timestamp: timestamp, + isSidechain: isSidechain, + parentUuid: parentUuid, + parentToolUseId: parentToolUseId, + toolUseId: item['tool_use_id'] as String? ?? '', + content: rawContent is String ? rawContent : jsonEncode(rawContent), + isError: item['is_error'] as bool? ?? false, + ), + ); default: break; } @@ -689,27 +676,45 @@ void _parseAssistantInto( case 'text': final text = item['text'] as String? ?? ''; if (text.isNotEmpty) { - out.add(AssistantTextMessage( - uuid: uuid, timestamp: timestamp, isSidechain: isSidechain, parentUuid: parentUuid, parentToolUseId: parentToolUseId, text: text)); + out.add( + AssistantTextMessage( + uuid: uuid, + timestamp: timestamp, + isSidechain: isSidechain, + parentUuid: parentUuid, + parentToolUseId: parentToolUseId, + text: text, + ), + ); } case 'thinking': final thinking = item['thinking'] as String? ?? ''; if (thinking.isNotEmpty) { - out.add(AssistantThinkingMessage( - uuid: uuid, timestamp: timestamp, isSidechain: isSidechain, parentUuid: parentUuid, parentToolUseId: parentToolUseId, thinking: thinking)); + out.add( + AssistantThinkingMessage( + uuid: uuid, + timestamp: timestamp, + isSidechain: isSidechain, + parentUuid: parentUuid, + parentToolUseId: parentToolUseId, + thinking: thinking, + ), + ); } case 'tool_use': final rawInput = item['input']; - out.add(AssistantToolUse( - uuid: uuid, - timestamp: timestamp, - isSidechain: isSidechain, - parentUuid: parentUuid, - parentToolUseId: parentToolUseId, - toolUseId: item['id'] as String? ?? '', - name: item['name'] as String? ?? '', - input: rawInput is Map ? rawInput.cast() : {}, - )); + out.add( + AssistantToolUse( + uuid: uuid, + timestamp: timestamp, + isSidechain: isSidechain, + parentUuid: parentUuid, + parentToolUseId: parentToolUseId, + toolUseId: item['id'] as String? ?? '', + name: item['name'] as String? ?? '', + input: rawInput is Map ? rawInput.cast() : {}, + ), + ); default: break; } diff --git a/lib/builtin/cli_install/src/extension.dart b/lib/builtin/cli_install/src/extension.dart index ac5949d4..0566f354 100644 --- a/lib/builtin/cli_install/src/extension.dart +++ b/lib/builtin/cli_install/src/extension.dart @@ -61,30 +61,23 @@ class CliInstallExtension extends ClideExtension { @override List get contributions => [ - CommandContribution( - id: 'clide.installCli', - command: 'clide.installCli', - title: "clide: Install 'clide' command in PATH", - run: (_) async { - final r = _resolved.install(); - final ctx = _ctx; - if (r.ok) { - ctx?.notify.success(r.message, title: 'clide CLI installed'); - return IpcResponse.ok(id: '', data: { - 'installed': r.installedPath, - 'onPath': r.onPath, - }); - } - ctx?.notify.error(r.message, title: 'clide CLI install failed'); - return IpcResponse.err( - id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: r.message, - ), - ); - }, - ), - ]; + CommandContribution( + id: 'clide.installCli', + command: 'clide.installCli', + title: "clide: Install 'clide' command in PATH", + run: (_) async { + final r = _resolved.install(); + final ctx = _ctx; + if (r.ok) { + ctx?.notify.success(r.message, title: 'clide CLI installed'); + return IpcResponse.ok(id: '', data: {'installed': r.installedPath, 'onPath': r.onPath}); + } + ctx?.notify.error(r.message, title: 'clide CLI install failed'); + return IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: r.message), + ); + }, + ), + ]; } diff --git a/lib/builtin/decisions/src/decision_colors.dart b/lib/builtin/decisions/src/decision_colors.dart index 9e78df2f..3338aef7 100644 --- a/lib/builtin/decisions/src/decision_colors.dart +++ b/lib/builtin/decisions/src/decision_colors.dart @@ -1,34 +1,22 @@ import 'dart:ui' show Color; class DecisionTypeColors { - const DecisionTypeColors({ - required this.confirmed, - required this.question, - required this.rejected, - }); + const DecisionTypeColors({required this.confirmed, required this.question, required this.rejected}); final Color confirmed; final Color question; final Color rejected; Color forType(String? type) => switch (type) { - 'confirmed' => confirmed, - 'question' => question, - 'rejected' => rejected, - _ => confirmed, - }; + 'confirmed' => confirmed, + 'question' => question, + 'rejected' => rejected, + _ => confirmed, + }; - static const dark = DecisionTypeColors( - confirmed: Color(0xFF7DD3A8), - question: Color(0xFFE6C370), - rejected: Color(0xFFE87D7D), - ); + static const dark = DecisionTypeColors(confirmed: Color(0xFF7DD3A8), question: Color(0xFFE6C370), rejected: Color(0xFFE87D7D)); - static const light = DecisionTypeColors( - confirmed: Color(0xFF1D7A4E), - question: Color(0xFFB08A20), - rejected: Color(0xFFC03030), - ); + static const light = DecisionTypeColors(confirmed: Color(0xFF1D7A4E), question: Color(0xFFB08A20), rejected: Color(0xFFC03030)); static DecisionTypeColors forTheme({required bool dark}) => dark ? DecisionTypeColors.dark : DecisionTypeColors.light; } diff --git a/lib/builtin/decisions/src/decision_detail_view.dart b/lib/builtin/decisions/src/decision_detail_view.dart index 47468a41..7d7b59c6 100644 --- a/lib/builtin/decisions/src/decision_detail_view.dart +++ b/lib/builtin/decisions/src/decision_detail_view.dart @@ -110,10 +110,7 @@ class _DecisionDetailViewState extends State { return ClidePaneChrome( title: id, subtitle: title, - leading: ReaderPinButton( - pinned: _nav?.hasPinned ?? false, - onTap: _decision != null ? _onPin : null, - ), + leading: ReaderPinButton(pinned: _nav?.hasPinned ?? false, onTap: _decision != null ? _onPin : null), trailing: [ ReaderActionBar( canGoBack: _nav?.canGoBack ?? false, @@ -144,7 +141,11 @@ class _DecisionDetailViewState extends State { children: [ ClideTooltip( message: type ?? 'confirmed', - child: Container(width: 10, height: 10, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle), + ), ), const SizedBox(width: 8), ClideText(id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily), @@ -159,21 +160,12 @@ class _DecisionDetailViewState extends State { ), const SizedBox(height: 8), ClideText(title, fontSize: 15, fontWeight: FontWeight.w500), - if (date != null) ...[ - const SizedBox(height: 6), - ClideText(date, muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily), - ], - if (status != null && status != 'active') ...[ - const SizedBox(height: 8), - _StatusBadge(status: status, tokens: tokens), - ], + if (date != null) ...[const SizedBox(height: 6), ClideText(date, muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily)], + if (status != null && status != 'active') ...[const SizedBox(height: 8), _StatusBadge(status: status, tokens: tokens)], ], ), ), - if (body != null && body.isNotEmpty) ...[ - const SizedBox(height: 12), - ClideMarkdown(body, onRecordTap: (id) => _navigateToRecord(context, id)), - ], + if (body != null && body.isNotEmpty) ...[const SizedBox(height: 12), ClideMarkdown(body, onRecordTap: (id) => _navigateToRecord(context, id))], if (refs.isNotEmpty) ...[ const SizedBox(height: 16), ClideText('CROSS-REFERENCES', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), diff --git a/lib/builtin/decisions/src/decisions_view.dart b/lib/builtin/decisions/src/decisions_view.dart index 2db9bb9c..327ae338 100644 --- a/lib/builtin/decisions/src/decisions_view.dart +++ b/lib/builtin/decisions/src/decisions_view.dart @@ -139,12 +139,14 @@ class _DecisionsViewState extends State { final hasFilter = lf.isNotEmpty; final filtered = hasFilter ? _decisions - .where((d) => - d.id.toLowerCase().contains(lf) || - d.title.toLowerCase().contains(lf) || - (d.domain ?? '').toLowerCase().contains(lf) || - (d.type ?? '').contains(lf)) - .toList() + .where( + (d) => + d.id.toLowerCase().contains(lf) || + d.title.toLowerCase().contains(lf) || + (d.domain ?? '').toLowerCase().contains(lf) || + (d.type ?? '').contains(lf), + ) + .toList() : _decisions; final confirmed = filtered.where((d) => d.type == 'confirmed').toList(); @@ -158,7 +160,9 @@ class _DecisionsViewState extends State { children: [ Row( children: [ - Expanded(child: ClideFilterBox(address: 'decisions.panel', hint: 'Filter decisions…', onChanged: (v) => setState(() => _filter = v))), + Expanded( + child: ClideFilterBox(address: 'decisions.panel', hint: 'Filter decisions…', onChanged: (v) => setState(() => _filter = v)), + ), Padding( padding: const EdgeInsets.only(right: 8), child: ClideTappable( @@ -180,39 +184,66 @@ class _DecisionsViewState extends State { ClideAccordion( label: 'CONFIRMED', count: confirmed.length, - leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.confirmed, shape: BoxShape.circle)), + leading: Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: typeColors.confirmed, shape: BoxShape.circle), + ), expanded: hasFilter || _isSectionExpanded('confirmed'), onToggle: () => _toggleSection('confirmed'), children: [ for (final d in confirmed) _DecisionCard( - entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null) + entry: d, + tokens: tokens, + typeColors: typeColors, + focused: d.id == _focusedId, + focusKey: d.id == _focusedId ? _focusedKey : null, + ), ], ), if (questions.isNotEmpty) ClideAccordion( label: 'QUESTIONS', count: questions.length, - leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.question, shape: BoxShape.circle)), + leading: Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: typeColors.question, shape: BoxShape.circle), + ), expanded: hasFilter || _isSectionExpanded('question'), onToggle: () => _toggleSection('question'), children: [ for (final d in questions) _DecisionCard( - entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null) + entry: d, + tokens: tokens, + typeColors: typeColors, + focused: d.id == _focusedId, + focusKey: d.id == _focusedId ? _focusedKey : null, + ), ], ), if (rejected.isNotEmpty) ClideAccordion( label: 'REJECTED', count: rejected.length, - leading: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColors.rejected, shape: BoxShape.circle)), + leading: Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: typeColors.rejected, shape: BoxShape.circle), + ), expanded: hasFilter || _isSectionExpanded('rejected'), onToggle: () => _toggleSection('rejected'), children: [ for (final d in rejected) _DecisionCard( - entry: d, tokens: tokens, typeColors: typeColors, focused: d.id == _focusedId, focusKey: d.id == _focusedId ? _focusedKey : null) + entry: d, + tokens: tokens, + typeColors: typeColors, + focused: d.id == _focusedId, + focusKey: d.id == _focusedId ? _focusedKey : null, + ), ], ), ], @@ -233,12 +264,12 @@ class _DecisionEntry { final String? status; factory _DecisionEntry.fromJson(Map json) => _DecisionEntry( - id: json['id'] as String? ?? '', - title: json['title'] as String? ?? '', - type: json['type'] as String?, - domain: json['domain'] as String?, - status: json['status'] as String?, - ); + id: json['id'] as String? ?? '', + title: json['title'] as String? ?? '', + type: json['type'] as String?, + domain: json['domain'] as String?, + status: json['status'] as String?, + ); } class _DecisionCard extends StatelessWidget { @@ -271,7 +302,11 @@ class _DecisionCard extends StatelessWidget { children: [ ClideTooltip( message: entry.type ?? 'confirmed', - child: Container(width: 8, height: 8, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle), + ), ), const SizedBox(width: 6), ClideText(entry.id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), diff --git a/lib/builtin/decisions/src/extension.dart b/lib/builtin/decisions/src/extension.dart index 9dfed878..805929cd 100644 --- a/lib/builtin/decisions/src/extension.dart +++ b/lib/builtin/decisions/src/extension.dart @@ -37,21 +37,21 @@ class DecisionsExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'decisions.panel', - slot: Slots.sidebar, - title: 'Decisions', - titleKey: 'tab.title', - i18nNamespace: id, - icon: PhosphorIcons.byName('lightbulb'), - build: (_) => const DecisionsView(), - ), - TabContribution( - id: 'decisions.detail', - slot: Slots.contextPanel, - title: 'Decision', - icon: PhosphorIcons.byName('lightbulb'), - build: (_) => const DecisionDetailView(), - ), - ]; + TabContribution( + id: 'decisions.panel', + slot: Slots.sidebar, + title: 'Decisions', + titleKey: 'tab.title', + i18nNamespace: id, + icon: PhosphorIcons.byName('lightbulb'), + build: (_) => const DecisionsView(), + ), + TabContribution( + id: 'decisions.detail', + slot: Slots.contextPanel, + title: 'Decision', + icon: PhosphorIcons.byName('lightbulb'), + build: (_) => const DecisionDetailView(), + ), + ]; } diff --git a/lib/builtin/deeplink/src/deep_link.dart b/lib/builtin/deeplink/src/deep_link.dart index fc91ad70..8b9413bb 100644 --- a/lib/builtin/deeplink/src/deep_link.dart +++ b/lib/builtin/deeplink/src/deep_link.dart @@ -23,9 +23,9 @@ class DeepLinkAction { /// A human-readable description for the confirmation prompt. String get describe => switch (name) { - 'open' => 'Open $path${line != null ? ' (line $line)' : ''}', - _ => name, - }; + 'open' => 'Open $path${line != null ? ' (line $line)' : ''}', + _ => name, + }; } /// Parse [url] into a [DeepLinkAction], or null when it is malformed, not a diff --git a/lib/builtin/deeplink/src/extension.dart b/lib/builtin/deeplink/src/extension.dart index 442238d9..e6862835 100644 --- a/lib/builtin/deeplink/src/extension.dart +++ b/lib/builtin/deeplink/src/extension.dart @@ -25,13 +25,8 @@ class DeepLinkExtension extends ClideExtension { @override List get contributions => [ - CommandContribution( - id: 'deeplink.invoke', - command: 'deeplink.invoke', - title: 'Open a clide:// deep link', - run: _invoke, - ), - ]; + CommandContribution(id: 'deeplink.invoke', command: 'deeplink.invoke', title: 'Open a clide:// deep link', run: _invoke), + ]; @override Future activate(ClideExtensionContext ctx) async => _ctx = ctx; diff --git a/lib/builtin/default_layout/src/extension.dart b/lib/builtin/default_layout/src/extension.dart index 8fb8f7d9..daedb0c7 100644 --- a/lib/builtin/default_layout/src/extension.dart +++ b/lib/builtin/default_layout/src/extension.dart @@ -15,101 +15,54 @@ class DefaultLayoutExtension extends ClideExtension { @override List get contributions => [ - _preset ?? classicPreset(), - CommandContribution( - id: 'layout.reset', - command: 'layout.reset', - title: 'Layout: Reset to Classic', - run: _reset, - ), - CommandContribution( - id: 'palette.toggle', - command: 'palette.toggle', - title: 'Command Palette', - defaultBinding: 'ctrl+shift+p', - run: _togglePalette, - ), - // Collapse toggles (D-051, D-054) - CommandContribution( - id: 'sidebar.collapse', - command: 'sidebar.collapse', - title: 'Toggle Sidebar Collapse', - defaultBinding: 'ctrl+shift+1', - run: _collapseSidebar, - ), - CommandContribution( - id: 'context.collapse', - command: 'context.collapse', - title: 'Toggle Context Panel Collapse', - defaultBinding: 'ctrl+shift+3', - run: _collapseContext, - ), - // Panel focus (D-054) - CommandContribution( - id: 'panel.focus.left', - command: 'panel.focus.left', - title: 'Focus Left Panel', - defaultBinding: 'ctrl+1', - run: _focusLeft, - ), - CommandContribution( - id: 'panel.focus.middle', - command: 'panel.focus.middle', - title: 'Focus Middle Panel', - defaultBinding: 'ctrl+2', - run: _focusMiddle, - ), - CommandContribution( - id: 'panel.focus.right', - command: 'panel.focus.right', - title: 'Focus Right Panel', - defaultBinding: 'ctrl+3', - run: _focusRight, - ), - // Focus mode (D-052, D-054) - CommandContribution( - id: 'panel.focusMode', - command: 'panel.focusMode', - title: 'Toggle Focus Mode', - defaultBinding: 'ctrl+.', - run: _toggleFocusMode, - ), - CommandContribution( - id: 'panel.focusMode.exit', - command: 'panel.focusMode.exit', - title: 'Exit Focus Mode', - defaultBinding: 'escape', - // Stand down in Vim insert/visual mode so Esc returns to normal - // mode instead of closing the editor (T-257). Symmetric with - // vim.yaml's `vim.mode.normal` (escape when vim.insert||vim.visual). - bindingWhen: '!vim.insert && !vim.visual', - run: _exitFocusMode, - ), - // Editor split (D-049, D-054) - CommandContribution( - id: 'editor.open', - command: 'editor.open', - title: 'Open Editor', - defaultBinding: 'ctrl+e', - run: _openEditor, - ), - CommandContribution( - id: 'editor.close', - command: 'editor.close', - title: 'Close Editor', - defaultBinding: 'ctrl+w', - run: _closeEditor, - ), - // Sidebar section switching (D-054): alt+1 through alt+5 - for (var i = 0; i < 5; i++) - CommandContribution( - id: 'sidebar.section.${i + 1}', - command: 'sidebar.section.${i + 1}', - title: 'Sidebar: Section ${i + 1}', - defaultBinding: 'alt+${i + 1}', - run: (args) => _switchSidebarSection(i), - ), - ]; + _preset ?? classicPreset(), + CommandContribution(id: 'layout.reset', command: 'layout.reset', title: 'Layout: Reset to Classic', run: _reset), + CommandContribution(id: 'palette.toggle', command: 'palette.toggle', title: 'Command Palette', defaultBinding: 'ctrl+shift+p', run: _togglePalette), + // Collapse toggles (D-051, D-054) + CommandContribution( + id: 'sidebar.collapse', + command: 'sidebar.collapse', + title: 'Toggle Sidebar Collapse', + defaultBinding: 'ctrl+shift+1', + run: _collapseSidebar, + ), + CommandContribution( + id: 'context.collapse', + command: 'context.collapse', + title: 'Toggle Context Panel Collapse', + defaultBinding: 'ctrl+shift+3', + run: _collapseContext, + ), + // Panel focus (D-054) + CommandContribution(id: 'panel.focus.left', command: 'panel.focus.left', title: 'Focus Left Panel', defaultBinding: 'ctrl+1', run: _focusLeft), + CommandContribution(id: 'panel.focus.middle', command: 'panel.focus.middle', title: 'Focus Middle Panel', defaultBinding: 'ctrl+2', run: _focusMiddle), + CommandContribution(id: 'panel.focus.right', command: 'panel.focus.right', title: 'Focus Right Panel', defaultBinding: 'ctrl+3', run: _focusRight), + // Focus mode (D-052, D-054) + CommandContribution(id: 'panel.focusMode', command: 'panel.focusMode', title: 'Toggle Focus Mode', defaultBinding: 'ctrl+.', run: _toggleFocusMode), + CommandContribution( + id: 'panel.focusMode.exit', + command: 'panel.focusMode.exit', + title: 'Exit Focus Mode', + defaultBinding: 'escape', + // Stand down in Vim insert/visual mode so Esc returns to normal + // mode instead of closing the editor (T-257). Symmetric with + // vim.yaml's `vim.mode.normal` (escape when vim.insert||vim.visual). + bindingWhen: '!vim.insert && !vim.visual', + run: _exitFocusMode, + ), + // Editor split (D-049, D-054) + CommandContribution(id: 'editor.open', command: 'editor.open', title: 'Open Editor', defaultBinding: 'ctrl+e', run: _openEditor), + CommandContribution(id: 'editor.close', command: 'editor.close', title: 'Close Editor', defaultBinding: 'ctrl+w', run: _closeEditor), + // Sidebar section switching (D-054): alt+1 through alt+5 + for (var i = 0; i < 5; i++) + CommandContribution( + id: 'sidebar.section.${i + 1}', + command: 'sidebar.section.${i + 1}', + title: 'Sidebar: Section ${i + 1}', + defaultBinding: 'alt+${i + 1}', + run: (args) => _switchSidebarSection(i), + ), + ]; @override Future activate(ClideExtensionContext ctx) async { @@ -313,11 +266,7 @@ class DefaultLayoutExtension extends ClideExtension { } static IpcResponse _notActivated() => IpcResponse.err( - id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'not activated', - ), - ); + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'not activated'), + ); } diff --git a/lib/builtin/diff/src/diff_controller.dart b/lib/builtin/diff/src/diff_controller.dart index 5cb71d4a..314ff8ea 100644 --- a/lib/builtin/diff/src/diff_controller.dart +++ b/lib/builtin/diff/src/diff_controller.dart @@ -50,18 +50,12 @@ class DiffController extends ChangeNotifier { } /// Load diffs. Optionally filter to [paths] and toggle [staged]. - Future load({ - bool staged = false, - List paths = const [], - }) async { + Future load({bool staged = false, List paths = const []}) async { _staged = staged; _loading = true; notifyListeners(); - final r = await ipc.request('git.diff', args: { - 'staged': staged, - if (paths.isNotEmpty) 'paths': paths, - }); + final r = await ipc.request('git.diff', args: {'staged': staged, if (paths.isNotEmpty) 'paths': paths}); _loading = false; if (!r.ok) { diff --git a/lib/builtin/diff/src/diff_view.dart b/lib/builtin/diff/src/diff_view.dart index bc2d47ed..8466489c 100644 --- a/lib/builtin/diff/src/diff_view.dart +++ b/lib/builtin/diff/src/diff_view.dart @@ -98,24 +98,11 @@ class _DiffViewState extends State { if (c.error != null) Padding( padding: const EdgeInsets.all(12), - child: ClideText( - c.error!, - color: tokens.statusError, - ), - ), - if (c.loading && c.diffs.isEmpty) - const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Loading…', muted: true), + child: ClideText(c.error!, color: tokens.statusError), ), + if (c.loading && c.diffs.isEmpty) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)), if (!c.loading && c.diffs.isEmpty && c.error == null) - Padding( - padding: const EdgeInsets.all(12), - child: ClideText( - c.showStaged ? 'No staged changes.' : 'No unstaged changes.', - muted: true, - ), - ), + Padding(padding: const EdgeInsets.all(12), child: ClideText(c.showStaged ? 'No staged changes.' : 'No unstaged changes.', muted: true)), Expanded( child: SingleChildScrollView( controller: _scroll, @@ -163,11 +150,7 @@ class _DiffToolbar extends StatelessWidget { label: 'show unstaged changes', child: GestureDetector( onTap: controller.showStaged ? controller.toggleStaged : null, - child: ClideText( - 'Unstaged', - fontSize: clideFontCaption, - color: controller.showStaged ? tokens.globalTextMuted : tokens.globalForeground, - ), + child: ClideText('Unstaged', fontSize: clideFontCaption, color: controller.showStaged ? tokens.globalTextMuted : tokens.globalForeground), ), ), const SizedBox(width: 12), @@ -177,11 +160,7 @@ class _DiffToolbar extends StatelessWidget { label: 'show staged changes', child: GestureDetector( onTap: controller.showStaged ? null : controller.toggleStaged, - child: ClideText( - 'Staged', - fontSize: clideFontCaption, - color: controller.showStaged ? tokens.globalForeground : tokens.globalTextMuted, - ), + child: ClideText('Staged', fontSize: clideFontCaption, color: controller.showStaged ? tokens.globalForeground : tokens.globalTextMuted), ), ), ], @@ -233,11 +212,7 @@ class _FileDiff extends StatelessWidget { child: Row( children: [ Expanded( - child: ClideText( - path, - fontSize: clideFontCaption, - color: focused ? tokens.globalFocus : tokens.panelHeaderForeground, - ), + child: ClideText(path, fontSize: clideFontCaption, color: focused ? tokens.globalFocus : tokens.panelHeaderForeground), ), if (additions > 0) ClideText('+$additions ', fontSize: clideFontCaption, color: tokens.statusSuccess), if (removals > 0) ClideText('-$removals', fontSize: clideFontCaption, color: tokens.statusError), @@ -250,12 +225,7 @@ class _FileDiff extends StatelessWidget { child: ClideText(meta.join(' · '), fontSize: clideFontCaption, muted: true), ), if (!isBinary) - for (final hunk in hunks) - _HunkView( - hunk: (hunk as Map).cast(), - filePath: path, - controller: controller, - ), + for (final hunk in hunks) _HunkView(hunk: (hunk as Map).cast(), filePath: path, controller: controller), const SizedBox(height: 8), ], ); @@ -263,11 +233,7 @@ class _FileDiff extends StatelessWidget { } class _HunkView extends StatelessWidget { - const _HunkView({ - required this.hunk, - required this.filePath, - required this.controller, - }); + const _HunkView({required this.hunk, required this.filePath, required this.controller}); final Map hunk; final String filePath; @@ -284,17 +250,9 @@ class _HunkView extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), - child: ClideText( - header, - fontSize: clideFontMono, - muted: true, - fontFamily: clideMonoFamily, - ), + child: ClideText(header, fontSize: clideFontMono, muted: true, fontFamily: clideMonoFamily), ), - for (final lineObj in lines) - _DiffLineRow( - line: (lineObj as Map).cast(), - ), + for (final lineObj in lines) _DiffLineRow(line: (lineObj as Map).cast()), ], ); } @@ -313,18 +271,9 @@ class _DiffLineRow extends StatelessWidget { final newLineNo = line['newLineNo'] as num?; final (Color bg, Color fg) = switch (kind) { - 'addition' => ( - tokens.statusSuccess.withValues(alpha: 0.15), - tokens.statusSuccess, - ), - 'removal' => ( - tokens.statusError.withValues(alpha: 0.15), - tokens.statusError, - ), - _ => ( - const Color(0x00000000), - tokens.globalForeground, - ), + 'addition' => (tokens.statusSuccess.withValues(alpha: 0.15), tokens.statusSuccess), + 'removal' => (tokens.statusError.withValues(alpha: 0.15), tokens.statusError), + _ => (const Color(0x00000000), tokens.globalForeground), }; final prefix = switch (kind) { @@ -361,22 +310,10 @@ class _DiffLineRow extends StatelessWidget { ), ), const SizedBox(width: 4), - ClideText( - prefix, - fontSize: clideFontMono, - color: fg, - fontFamily: clideMonoFamily, - ), + ClideText(prefix, fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily), const SizedBox(width: 2), Expanded( - child: ClideText( - text, - fontSize: clideFontMono, - color: fg, - fontFamily: clideMonoFamily, - maxLines: 1, - overflow: TextOverflow.clip, - ), + child: ClideText(text, fontSize: clideFontMono, color: fg, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.clip), ), ], ), diff --git a/lib/builtin/diff/src/extension.dart b/lib/builtin/diff/src/extension.dart index d17d9e5a..66f96579 100644 --- a/lib/builtin/diff/src/extension.dart +++ b/lib/builtin/diff/src/extension.dart @@ -24,16 +24,16 @@ class DiffExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'diff.view', - slot: Slots.workspace, - title: 'Diff', - titleKey: 'tab.title', - i18nNamespace: id, - priority: -70, - build: (_) => DiffView(controller: _controller), - ), - ]; + TabContribution( + id: 'diff.view', + slot: Slots.workspace, + title: 'Diff', + titleKey: 'tab.title', + i18nNamespace: id, + priority: -70, + build: (_) => DiffView(controller: _controller), + ), + ]; @override Future activate(ClideExtensionContext ctx) async { diff --git a/lib/builtin/editor/src/editor_controller.dart b/lib/builtin/editor/src/editor_controller.dart index 733853df..fea8a9b3 100644 --- a/lib/builtin/editor/src/editor_controller.dart +++ b/lib/builtin/editor/src/editor_controller.dart @@ -99,12 +99,7 @@ class EditorController extends ChangeNotifier { if (raw is! List) return; _buffers = [ for (final b in raw) - if (b is Map) - ( - id: b['id']! as String, - path: b['path']! as String, - dirty: (b['dirty'] as bool?) ?? false, - ), + if (b is Map) (id: b['id']! as String, path: b['path']! as String, dirty: (b['dirty'] as bool?) ?? false), ]; notifyListeners(); } @@ -143,10 +138,7 @@ class EditorController extends ChangeNotifier { } /// Called by the widget on every local text edit. - void pushLocalEdit({ - required String newContent, - required Selection newSelection, - }) { + void pushLocalEdit({required String newContent, required Selection newSelection}) { final id = _activeId; if (id == null) return; @@ -162,11 +154,7 @@ class EditorController extends ChangeNotifier { // large buffers, so event broadcasts stay small. _pendingLocalEdits++; _suppressNextRemoteEdit = true; - ipc.request('editor.set-content', args: { - 'id': id, - 'text': newContent, - 'selection': newSelection.toJson(), - }).whenComplete(() => _pendingLocalEdits--); + ipc.request('editor.set-content', args: {'id': id, 'text': newContent, 'selection': newSelection.toJson()}).whenComplete(() => _pendingLocalEdits--); } Future save() async { diff --git a/lib/builtin/editor/src/editor_view.dart b/lib/builtin/editor/src/editor_view.dart index 0c4ce7c0..6f73e22b 100644 --- a/lib/builtin/editor/src/editor_view.dart +++ b/lib/builtin/editor/src/editor_view.dart @@ -65,10 +65,7 @@ class _EditorViewState extends State { final kernel = ClideKernel.of(context); _controller = EditorController(ipc: kernel.ipc, events: kernel.events)..addListener(_onControllerChanged); _keymap = kernel.keymap; - _matcher = SequenceMatcher( - keymap: () => kernel.keymap.keymap ?? Keymap(const []), - context: () => kernel.keymap.scope, - ); + _matcher = SequenceMatcher(keymap: () => kernel.keymap.keymap ?? Keymap(const []), context: () => kernel.keymap.scope); // Rebuild when the Vim mode flips so the editor toggles read-only. kernel.keymap.addListener(_onModeChanged); unawaited(_controller!.hydrate()); @@ -105,10 +102,7 @@ class _EditorViewState extends State { _text.updatePath(c.activePath); if (c.content != _lastRemoteContent) { _lastRemoteContent = c.content; - final sel = TextSelection( - baseOffset: c.selection.start.clamp(0, c.content.length), - extentOffset: c.selection.end.clamp(0, c.content.length), - ); + final sel = TextSelection(baseOffset: c.selection.start.clamp(0, c.content.length), extentOffset: c.selection.end.clamp(0, c.content.length)); _text.removeListener(_onTextChanged); _text.value = TextEditingValue(text: c.content, selection: sel); _text.addListener(_onTextChanged); @@ -306,9 +300,7 @@ class _EditorViewState extends State { return ClidePaneChrome( title: 'editor', subtitle: 'no buffer · use `clide open ` or pick a file in the tree', - child: const Center( - child: ClideText('Open a file to begin editing.', muted: true), - ), + child: const Center(child: ClideText('Open a file to begin editing.', muted: true)), ); } return MultitabPane( @@ -357,12 +349,7 @@ class _TextBody extends StatelessWidget { @override Widget build(BuildContext context) { - final style = TextStyle( - color: foreground, - fontSize: clideFontMono, - fontFamily: clideMonoFamily, - fontFamilyFallback: clideMonoFamilyFallback, - ); + final style = TextStyle(color: foreground, fontSize: clideFontMono, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback); final editable = EditableText( controller: controller, focusNode: focus, @@ -398,7 +385,10 @@ class _TextBody extends StatelessWidget { /// Advance width of one monospace glyph in [style]. static double _charWidth(TextStyle style) { - final tp = TextPainter(text: TextSpan(text: '0', style: style), textDirection: TextDirection.ltr)..layout(); + final tp = TextPainter( + text: TextSpan(text: '0', style: style), + textDirection: TextDirection.ltr, + )..layout(); return tp.width; } } @@ -413,11 +403,12 @@ class _RulerPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { canvas.drawLine( - Offset(x, 0), - Offset(x, size.height), - Paint() - ..color = color - ..strokeWidth = 1); + Offset(x, 0), + Offset(x, size.height), + Paint() + ..color = color + ..strokeWidth = 1, + ); } @override diff --git a/lib/builtin/editor/src/extension.dart b/lib/builtin/editor/src/extension.dart index c8ef386e..09cf4513 100644 --- a/lib/builtin/editor/src/extension.dart +++ b/lib/builtin/editor/src/extension.dart @@ -50,14 +50,14 @@ class EditorExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'editor.active', - slot: Slots.workspace, - title: 'Editor', - titleKey: 'tab.title', - i18nNamespace: id, - priority: 80, // between Claude (90) and welcome (-100) - build: (_) => const EditorView(), - ), - ]; + TabContribution( + id: 'editor.active', + slot: Slots.workspace, + title: 'Editor', + titleKey: 'tab.title', + i18nNamespace: id, + priority: 80, // between Claude (90) and welcome (-100) + build: (_) => const EditorView(), + ), + ]; } diff --git a/lib/builtin/editor/src/syntax_text_controller.dart b/lib/builtin/editor/src/syntax_text_controller.dart index 0dd7dd4e..fd988a2d 100644 --- a/lib/builtin/editor/src/syntax_text_controller.dart +++ b/lib/builtin/editor/src/syntax_text_controller.dart @@ -36,18 +36,23 @@ class SyntaxTextController extends TextEditingController { if (source == _highlightedText) return; _highlighting = true; - _syntax.highlight(path, source).then((result) { - _highlighting = false; - if (text != source) { - _requestHighlight(); - return; - } - _highlightedText = source; - _spans = result.spans; - notifyListeners(); - }, onError: (_) { - _highlighting = false; - }); + _syntax + .highlight(path, source) + .then( + (result) { + _highlighting = false; + if (text != source) { + _requestHighlight(); + return; + } + _highlightedText = source; + _spans = result.spans; + notifyListeners(); + }, + onError: (_) { + _highlighting = false; + }, + ); } @override @@ -57,11 +62,7 @@ class SyntaxTextController extends TextEditingController { } @override - TextSpan buildTextSpan({ - required BuildContext context, - TextStyle? style, - required bool withComposing, - }) { + TextSpan buildTextSpan({required BuildContext context, TextStyle? style, required bool withComposing}) { final tokens = _tokens; if (_spans.isEmpty || tokens == null || text.isEmpty) { return TextSpan(text: text, style: style); @@ -125,20 +126,17 @@ class SyntaxTextController extends TextEditingController { // Gap before this span — plain text. if (spanCharStart > charPos) { - children.add(TextSpan( - text: source.substring(charPos, spanCharStart), - style: style, - )); + children.add(TextSpan(text: source.substring(charPos, spanCharStart), style: style)); } // The highlighted span. if (spanCharEnd > spanCharStart) { - children.add(TextSpan( - text: source.substring(spanCharStart, spanCharEnd), - style: style?.copyWith( - color: TreeSitterService.colorForRole(span.role, tokens), + children.add( + TextSpan( + text: source.substring(spanCharStart, spanCharEnd), + style: style?.copyWith(color: TreeSitterService.colorForRole(span.role, tokens)), ), - )); + ); } charPos = spanCharEnd; @@ -146,10 +144,7 @@ class SyntaxTextController extends TextEditingController { // Trailing plain text. if (charPos < source.length) { - children.add(TextSpan( - text: source.substring(charPos), - style: style, - )); + children.add(TextSpan(text: source.substring(charPos), style: style)); } return TextSpan(style: style, children: children); diff --git a/lib/builtin/editor/src/vim_edit_ops.dart b/lib/builtin/editor/src/vim_edit_ops.dart index 74b291e1..0642498f 100644 --- a/lib/builtin/editor/src/vim_edit_ops.dart +++ b/lib/builtin/editor/src/vim_edit_ops.dart @@ -79,13 +79,7 @@ class VimResult { /// Apply [action] to [v]. [count] repeats motions/line-edits; [visual] /// selects between collapse-to-caret (normal) and extend-from-anchor /// (visual) for motions, and enables the `visual*` range ops. -VimResult applyVim( - String action, - TextEditingValue v, { - VimRegister register = VimRegister.empty, - bool visual = false, - int count = 1, -}) { +VimResult applyVim(String action, TextEditingValue v, {VimRegister register = VimRegister.empty, bool visual = false, int count = 1}) { final t = v.text; final caret = v.selection.extentOffset.clamp(0, t.length); final anchor = v.selection.baseOffset.clamp(0, t.length); @@ -226,7 +220,8 @@ int _up(String t, int off) { int _cls(String ch) { if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') return 0; final c = ch.codeUnitAt(0); - final isWord = (c >= 0x30 && c <= 0x39) || // 0-9 + final isWord = + (c >= 0x30 && c <= 0x39) || // 0-9 (c >= 0x41 && c <= 0x5A) || // A-Z (c >= 0x61 && c <= 0x7A) || // a-z c == 0x5F; // _ @@ -279,13 +274,19 @@ int _wordEnd(String t, int off) { // -- Edit helpers ----------------------------------------------------------- VimResult _collapsed(String text, int caret) => VimResult( - TextEditingValue(text: text, selection: TextSelection.collapsed(offset: _clamp(caret, 0, text.length))), - ); + TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: _clamp(caret, 0, text.length)), + ), +); VimResult _insertAt(String t, int caret) => VimResult( - TextEditingValue(text: t, selection: TextSelection.collapsed(offset: _clamp(caret, 0, t.length))), - enterInsert: true, - ); + TextEditingValue( + text: t, + selection: TextSelection.collapsed(offset: _clamp(caret, 0, t.length)), + ), + enterInsert: true, +); VimResult _deleteChar(String t, int caret, int count) { final le = _lineEnd(t, caret); @@ -298,7 +299,10 @@ VimResult _deleteChar(String t, int caret, int count) { final nle = _lineEnd(nt, caret); final ncaret = _clamp(caret, nls, nle > nls ? nle - 1 : nls); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: ncaret)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: ncaret), + ), register: VimRegister(removed), ); } @@ -324,7 +328,10 @@ VimResult _deleteLines(String t, int caret, int count) { caretLineStart = ls; } return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: _firstNonBlank(nt, caretLineStart))), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: _firstNonBlank(nt, caretLineStart)), + ), register: reg, ); } @@ -337,7 +344,10 @@ VimResult _deleteToEnd(String t, int caret) { final ls = _lineStart(nt, caret); final nle = _lineEnd(nt, caret); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: _clamp(caret, ls, nle > ls ? nle - 1 : ls))), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: _clamp(caret, ls, nle > ls ? nle - 1 : ls)), + ), register: VimRegister(removed), ); } @@ -349,7 +359,10 @@ VimResult _deleteToOffset(String t, int caret, int target, {bool insert = false} final removed = t.substring(lo, hi); final nt = t.replaceRange(lo, hi, ''); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: lo)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: lo), + ), register: VimRegister(removed), enterInsert: insert, ); @@ -366,7 +379,10 @@ VimResult _changeLine(String t, int caret, int count) { final removed = t.substring(ls, end); final nt = t.replaceRange(ls, end, ''); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: ls)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: ls), + ), register: VimRegister(removed.endsWith('\n') ? removed : '$removed\n', linewise: true), enterInsert: true, ); @@ -382,7 +398,10 @@ VimResult _yankLines(String t, int caret, int count) { } final yanked = t.substring(ls, end); return VimResult( - TextEditingValue(text: t, selection: TextSelection.collapsed(offset: caret)), + TextEditingValue( + text: t, + selection: TextSelection.collapsed(offset: caret), + ), register: VimRegister(yanked.endsWith('\n') ? yanked : '$yanked\n', linewise: true), ); } @@ -394,7 +413,12 @@ VimResult _paste(String t, int caret, VimRegister reg, {required bool before}) { if (before) { final ls = _lineStart(t, caret); final nt = t.replaceRange(ls, ls, body); - return VimResult(TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: _firstNonBlank(nt, ls)))); + return VimResult( + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: _firstNonBlank(nt, ls)), + ), + ); } final le = _lineEnd(t, caret); final insertAt = le < t.length ? le + 1 : t.length; @@ -402,12 +426,22 @@ VimResult _paste(String t, int caret, VimRegister reg, {required bool before}) { final chunk = le < t.length ? body : '\n${body.substring(0, body.length - 1)}'; final nt = t.replaceRange(insertAt, insertAt, chunk); final caretLine = le < t.length ? insertAt : insertAt + 1; - return VimResult(TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: _firstNonBlank(nt, caretLine)))); + return VimResult( + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: _firstNonBlank(nt, caretLine)), + ), + ); } // Charwise: p pastes after the caret, P at the caret. final at = before ? caret : _clamp(caret + 1, 0, t.length); final nt = t.replaceRange(at, at, reg.text); - return VimResult(TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: at + reg.text.length - 1))); + return VimResult( + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: at + reg.text.length - 1), + ), + ); } VimResult _openLine(String t, int caret, {required bool below}) { @@ -415,14 +449,20 @@ VimResult _openLine(String t, int caret, {required bool below}) { final le = _lineEnd(t, caret); final nt = t.replaceRange(le, le, '\n'); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: le + 1)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: le + 1), + ), enterInsert: true, ); } final ls = _lineStart(t, caret); final nt = t.replaceRange(ls, ls, '\n'); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: ls)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: ls), + ), enterInsert: true, ); } @@ -435,7 +475,10 @@ VimResult _deleteRange(String t, int anchor, int caret, {required bool insert}) final removed = t.substring(lo, hi); final nt = t.replaceRange(lo, hi, ''); return VimResult( - TextEditingValue(text: nt, selection: TextSelection.collapsed(offset: lo)), + TextEditingValue( + text: nt, + selection: TextSelection.collapsed(offset: lo), + ), register: VimRegister(removed), enterInsert: insert, ); @@ -445,7 +488,10 @@ VimResult _yankRange(String t, int anchor, int caret) { final lo = anchor < caret ? anchor : caret; final hi = _clamp((anchor < caret ? caret : anchor) + 1, 0, t.length); return VimResult( - TextEditingValue(text: t, selection: TextSelection.collapsed(offset: lo)), + TextEditingValue( + text: t, + selection: TextSelection.collapsed(offset: lo), + ), register: VimRegister(t.substring(lo, hi)), ); } diff --git a/lib/builtin/files/src/extension.dart b/lib/builtin/files/src/extension.dart index 24111ab1..7ee12a2e 100644 --- a/lib/builtin/files/src/extension.dart +++ b/lib/builtin/files/src/extension.dart @@ -19,15 +19,15 @@ class FilesExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'files.tree', - slot: Slots.sidebar, - title: 'Files', - icon: PhosphorIcons.byName('folder'), - titleKey: 'tab.title', - i18nNamespace: id, - priority: -100, - build: (_) => const FileTreeView(), - ), - ]; + TabContribution( + id: 'files.tree', + slot: Slots.sidebar, + title: 'Files', + icon: PhosphorIcons.byName('folder'), + titleKey: 'tab.title', + i18nNamespace: id, + priority: -100, + build: (_) => const FileTreeView(), + ), + ]; } diff --git a/lib/builtin/files/src/file_tree_view.dart b/lib/builtin/files/src/file_tree_view.dart index b7ea02e4..be30af16 100644 --- a/lib/builtin/files/src/file_tree_view.dart +++ b/lib/builtin/files/src/file_tree_view.dart @@ -50,17 +50,11 @@ class _FileTreeViewState extends State { listenable: c, builder: (context, _) { if (c.error != null && c.rootPath == null) { - return Padding( - padding: const EdgeInsets.all(12), - child: ClideText(c.error!, muted: true), - ); + return Padding(padding: const EdgeInsets.all(12), child: ClideText(c.error!, muted: true)); } final root = c.rootPath; if (root == null) { - return const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Loading…', muted: true), - ); + return const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)); } final rootName = root.split(Platform.pathSeparator).last; return Column( @@ -98,18 +92,12 @@ class _FileTreeViewState extends State { final matches = c.allLoadedEntries().where((e) { return e.path.toLowerCase().contains(lowerFilter) || e.name.toLowerCase().contains(lowerFilter); }).toList(); - return [ - for (final e in matches) _FilteredFileRow(entry: e), - ]; + return [for (final e in matches) _FilteredFileRow(entry: e)]; } } class _Children extends StatelessWidget { - const _Children({ - required this.path, - required this.controller, - required this.depth, - }); + const _Children({required this.path, required this.controller, required this.depth}); final String path; final FileTreeController controller; @@ -129,33 +117,19 @@ class _Children extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ - _DirRow( - name: e.name, - path: e.path, - controller: controller, - depth: depth, - ), + _DirRow(name: e.name, path: e.path, controller: controller, depth: depth), if (controller.isExpanded(e.path)) _Children(path: e.path, controller: controller, depth: depth + 1), ], ) else - _FileRow( - name: e.name, - path: e.path, - depth: depth, - ), + _FileRow(name: e.name, path: e.path, depth: depth), ], ); } } class _DirRow extends StatelessWidget { - const _DirRow({ - required this.name, - required this.path, - required this.controller, - required this.depth, - }); + const _DirRow({required this.name, required this.path, required this.controller, required this.depth}); final String name; final String path; @@ -173,11 +147,7 @@ class _DirRow extends StatelessWidget { child: _Row( depth: depth, onTap: () => controller.toggle(path), - leading: ClideIcon( - const ChevronRightIcon(), - size: 10, - color: tokens.sidebarForeground, - ), + leading: ClideIcon(const ChevronRightIcon(), size: 10, color: tokens.sidebarForeground), label: name, rotateLeading: expanded, ), @@ -186,11 +156,7 @@ class _DirRow extends StatelessWidget { } class _FileRow extends StatelessWidget { - const _FileRow({ - required this.name, - required this.path, - required this.depth, - }); + const _FileRow({required this.name, required this.path, required this.depth}); final String name; final String path; @@ -202,11 +168,7 @@ class _FileRow extends StatelessWidget { button: true, label: 'Open $name', onTap: () => _openFile(context, path), - child: _Row( - depth: depth, - onTap: () => _openFile(context, path), - label: name, - ), + child: _Row(depth: depth, onTap: () => _openFile(context, path), label: name), ); } @@ -218,13 +180,7 @@ class _FileRow extends StatelessWidget { } class _Row extends StatelessWidget { - const _Row({ - required this.depth, - required this.onTap, - required this.label, - this.leading, - this.rotateLeading = false, - }); + const _Row({required this.depth, required this.onTap, required this.label, this.leading, this.rotateLeading = false}); final int depth; final VoidCallback onTap; @@ -252,12 +208,7 @@ class _Row extends StatelessWidget { ] else const SizedBox(width: 16), Expanded( - child: ClideText( - label, - maxLines: 1, - overflow: TextOverflow.ellipsis, - color: tokens.sidebarForeground, - ), + child: ClideText(label, maxLines: 1, overflow: TextOverflow.ellipsis, color: tokens.sidebarForeground), ), ], ), diff --git a/lib/builtin/git/src/extension.dart b/lib/builtin/git/src/extension.dart index b6603620..20001a5a 100644 --- a/lib/builtin/git/src/extension.dart +++ b/lib/builtin/git/src/extension.dart @@ -16,20 +16,16 @@ class GitExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'git.panel', - slot: Slots.sidebar, - title: 'Git', - icon: PhosphorIcons.byName('git-branch'), - titleKey: 'tab.title', - i18nNamespace: id, - priority: -80, - build: (_) => const GitPanelView(), - ), - StatusItemContribution( - id: 'git.branch', - priority: 10, - build: (_) => const GitStatusItem(), - ), - ]; + TabContribution( + id: 'git.panel', + slot: Slots.sidebar, + title: 'Git', + icon: PhosphorIcons.byName('git-branch'), + titleKey: 'tab.title', + i18nNamespace: id, + priority: -80, + build: (_) => const GitPanelView(), + ), + StatusItemContribution(id: 'git.branch', priority: 10, build: (_) => const GitStatusItem()), + ]; } diff --git a/lib/builtin/git/src/git_controller.dart b/lib/builtin/git/src/git_controller.dart index 0a7872e3..75474af1 100644 --- a/lib/builtin/git/src/git_controller.dart +++ b/lib/builtin/git/src/git_controller.dart @@ -114,9 +114,7 @@ class GitController extends ChangeNotifier { } Future stash({String? message}) async { - final r = await ipc.request('git.stash', args: { - if (message != null) 'message': message, - }); + final r = await ipc.request('git.stash', args: {'message': ?message}); return r.ok; } diff --git a/lib/builtin/git/src/git_panel_view.dart b/lib/builtin/git/src/git_panel_view.dart index 26339e14..44586cac 100644 --- a/lib/builtin/git/src/git_panel_view.dart +++ b/lib/builtin/git/src/git_panel_view.dart @@ -77,90 +77,58 @@ class _GitPanelViewState extends State { children: [ ClideFilterBox(address: 'git.panel', hint: 'Filter changes…', onChanged: (v) => setState(() => _filter = v)), Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - _BranchHeader(controller: c), - if (c.error != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: ClideText( - c.error!, - color: tokens.statusError, - fontSize: clideFontCaption, - maxLines: 3, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + _BranchHeader(controller: c), + if (c.error != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: ClideText(c.error!, color: tokens.statusError, fontSize: clideFontCaption, maxLines: 3), + ), + if (c.loading && c.isClean) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)), + if (!c.loading && c.isClean && c.error == null) + const Padding(padding: EdgeInsets.all(12), child: ClideText('Nothing to commit, working tree clean.', muted: true)), + if (c.conflicted.isNotEmpty) _FileGroup(label: 'Merge conflicts', entries: _applyFilter(c.conflicted), actions: const []), + if (c.staged.isNotEmpty) ...[ + _FileGroup( + label: 'Staged', + entries: _applyFilter(c.staged), + actions: [_GroupAction(label: 'Unstage all', onTap: () => unawaited(c.unstage(const [])))], + onUnstage: (path) => unawaited(c.unstage([path])), + ), + _CommitInput(commitMsg: _commitMsg, commitFocus: _commitFocus, controller: c), + ], + if (c.unstaged.isNotEmpty) + _FileGroup( + label: 'Changes', + entries: _applyFilter(c.unstaged), + actions: [_GroupAction(label: 'Stage all', onTap: () => unawaited(c.stageAll()))], + onStage: (path) => unawaited(c.stage([path])), + onDiscard: (path) => _confirmDiscard(context, c, path), + ), + if (c.untracked.isNotEmpty) + _FileGroup( + label: 'Untracked', + entries: _applyFilter(c.untracked), + actions: [ + _GroupAction( + label: 'Stage all', + onTap: () { + final paths = [for (final e in c.untracked) e['path'] as String]; + unawaited(c.stage(paths)); + }, + ), + ], + onStage: (path) => unawaited(c.stage([path])), ), - ), - if (c.loading && c.isClean) - const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Loading…', muted: true), - ), - if (!c.loading && c.isClean && c.error == null) - const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Nothing to commit, working tree clean.', muted: true), - ), - if (c.conflicted.isNotEmpty) - _FileGroup( - label: 'Merge conflicts', - entries: _applyFilter(c.conflicted), - actions: const [], - ), - if (c.staged.isNotEmpty) ...[ - _FileGroup( - label: 'Staged', - entries: _applyFilter(c.staged), - actions: [ - _GroupAction( - label: 'Unstage all', - onTap: () => unawaited(c.unstage(const [])), - ), - ], - onUnstage: (path) => unawaited(c.unstage([path])), - ), - _CommitInput( - commitMsg: _commitMsg, - commitFocus: _commitFocus, - controller: c, - ), ], - if (c.unstaged.isNotEmpty) - _FileGroup( - label: 'Changes', - entries: _applyFilter(c.unstaged), - actions: [ - _GroupAction( - label: 'Stage all', - onTap: () => unawaited(c.stageAll()), - ), - ], - onStage: (path) => unawaited(c.stage([path])), - onDiscard: (path) => _confirmDiscard(context, c, path), - ), - if (c.untracked.isNotEmpty) - _FileGroup( - label: 'Untracked', - entries: _applyFilter(c.untracked), - actions: [ - _GroupAction( - label: 'Stage all', - onTap: () { - final paths = [ - for (final e in c.untracked) e['path'] as String, - ]; - unawaited(c.stage(paths)); - }, - ), - ], - onStage: (path) => unawaited(c.stage([path])), - ), - ], + ), ), - )), + ), ], ), ); @@ -185,23 +153,11 @@ class _BranchHeader extends StatelessWidget { child: Row( children: [ Expanded( - child: ClideText( - parts.join(' '), - fontSize: clideFontCaption, - color: tokens.sidebarForeground, - ), - ), - _SmallAction( - label: 'Pull', - semanticsLabel: 'git pull', - onTap: () => unawaited(controller.pull()), + child: ClideText(parts.join(' '), fontSize: clideFontCaption, color: tokens.sidebarForeground), ), + _SmallAction(label: 'Pull', semanticsLabel: 'git pull', onTap: () => unawaited(controller.pull())), const SizedBox(width: 4), - _SmallAction( - label: 'Push', - semanticsLabel: 'git push', - onTap: () => unawaited(controller.push()), - ), + _SmallAction(label: 'Push', semanticsLabel: 'git push', onTap: () => unawaited(controller.push())), ], ), ); @@ -209,11 +165,7 @@ class _BranchHeader extends StatelessWidget { } class _CommitInput extends StatelessWidget { - const _CommitInput({ - required this.commitMsg, - required this.commitFocus, - required this.controller, - }); + const _CommitInput({required this.commitMsg, required this.commitFocus, required this.controller}); final TextEditingController commitMsg; final FocusNode commitFocus; @@ -232,19 +184,12 @@ class _CommitInput extends StatelessWidget { label: 'commit message', textField: true, child: Container( - decoration: BoxDecoration( - border: Border.all(color: tokens.globalBorder), - ), + decoration: BoxDecoration(border: Border.all(color: tokens.globalBorder)), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), child: EditableText( controller: commitMsg, focusNode: commitFocus, - style: TextStyle( - fontFamily: clideUiFamily, - fontWeight: clideUiDefaultWeight, - fontSize: clideFontCaption, - color: tokens.globalForeground, - ), + style: TextStyle(fontFamily: clideUiFamily, fontWeight: clideUiDefaultWeight, fontSize: clideFontCaption, color: tokens.globalForeground), cursorColor: tokens.globalFocus, backgroundCursorColor: tokens.globalFocus, maxLines: 3, @@ -268,9 +213,11 @@ class _CommitInput extends StatelessWidget { void _doCommit() { final msg = commitMsg.text.trim(); if (msg.isEmpty) return; - unawaited(controller.commit(msg).then((hash) { - if (hash != null) commitMsg.clear(); - })); + unawaited( + controller.commit(msg).then((hash) { + if (hash != null) commitMsg.clear(); + }), + ); } } @@ -281,14 +228,7 @@ class _GroupAction { } class _FileGroup extends StatelessWidget { - const _FileGroup({ - required this.label, - required this.entries, - this.actions = const [], - this.onStage, - this.onUnstage, - this.onDiscard, - }); + const _FileGroup({required this.label, required this.entries, this.actions = const [], this.onStage, this.onUnstage, this.onDiscard}); final String label; final List> entries; @@ -309,39 +249,20 @@ class _FileGroup extends StatelessWidget { child: Row( children: [ Expanded( - child: ClideText( - '$label (${entries.length})', - fontSize: clideFontCaption, - muted: true, - color: tokens.sidebarForeground, - ), + child: ClideText('$label (${entries.length})', fontSize: clideFontCaption, muted: true, color: tokens.sidebarForeground), ), - for (final a in actions) ...[ - _SmallAction(label: a.label, onTap: a.onTap), - const SizedBox(width: 4), - ], + for (final a in actions) ...[_SmallAction(label: a.label, onTap: a.onTap), const SizedBox(width: 4)], ], ), ), - for (final entry in entries) - _GitFileRow( - entry: entry, - onStage: onStage, - onUnstage: onUnstage, - onDiscard: onDiscard, - ), + for (final entry in entries) _GitFileRow(entry: entry, onStage: onStage, onUnstage: onUnstage, onDiscard: onDiscard), ], ); } } class _GitFileRow extends StatelessWidget { - const _GitFileRow({ - required this.entry, - this.onStage, - this.onUnstage, - this.onDiscard, - }); + const _GitFileRow({required this.entry, this.onStage, this.onUnstage, this.onDiscard}); final Map entry; final void Function(String path)? onStage; @@ -371,39 +292,15 @@ class _GitFileRow extends StatelessWidget { padding: const EdgeInsets.only(left: 20, right: 8, top: 2, bottom: 2), child: Row( children: [ - ClideText( - _stateIndicator(state), - fontSize: clideFontCaption, - color: _stateColor(state, tokens), - ), + ClideText(_stateIndicator(state), fontSize: clideFontCaption, color: _stateColor(state, tokens)), const SizedBox(width: 6), Expanded( - child: ClideText( - name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - color: tokens.sidebarForeground, - ), + child: ClideText(name, maxLines: 1, overflow: TextOverflow.ellipsis, color: tokens.sidebarForeground), ), if (hovered) ...[ - if (onStage != null) - _SmallAction( - label: '+', - semanticsLabel: 'stage $name', - onTap: () => onStage!(path), - ), - if (onUnstage != null) - _SmallAction( - label: '-', - semanticsLabel: 'unstage $name', - onTap: () => onUnstage!(path), - ), - if (onDiscard != null) - _SmallAction( - label: 'x', - semanticsLabel: 'discard changes to $name', - onTap: () => onDiscard!(path), - ), + if (onStage != null) _SmallAction(label: '+', semanticsLabel: 'stage $name', onTap: () => onStage!(path)), + if (onUnstage != null) _SmallAction(label: '-', semanticsLabel: 'unstage $name', onTap: () => onUnstage!(path)), + if (onDiscard != null) _SmallAction(label: 'x', semanticsLabel: 'discard changes to $name', onTap: () => onDiscard!(path)), ], ], ), @@ -447,11 +344,7 @@ class _GitFileRow extends StatelessWidget { } class _SmallAction extends StatelessWidget { - const _SmallAction({ - required this.label, - required this.onTap, - this.semanticsLabel, - }); + const _SmallAction({required this.label, required this.onTap, this.semanticsLabel}); final String label; final String? semanticsLabel; @@ -467,11 +360,7 @@ class _SmallAction extends StatelessWidget { onTap: onTap, child: MouseRegion( cursor: SystemMouseCursors.click, - child: ClideText( - label, - fontSize: clideFontCaption, - color: tokens.sidebarForeground, - ), + child: ClideText(label, fontSize: clideFontCaption, color: tokens.sidebarForeground), ), ), ); @@ -479,11 +368,7 @@ class _SmallAction extends StatelessWidget { } class _DiscardConfirmDialog extends StatelessWidget { - const _DiscardConfirmDialog({ - required this.path, - required this.onConfirm, - required this.onCancel, - }); + const _DiscardConfirmDialog({required this.path, required this.onConfirm, required this.onCancel}); final String path; final VoidCallback onConfirm; @@ -505,30 +390,16 @@ class _DiscardConfirmDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClideText( - 'Discard changes?', - color: tokens.globalForeground, - ), + ClideText('Discard changes?', color: tokens.globalForeground), const SizedBox(height: 8), - ClideText( - 'Unstaged changes to $name will be permanently lost.', - fontSize: clideFontCaption, - color: tokens.statusError, - ), + ClideText('Unstaged changes to $name will be permanently lost.', fontSize: clideFontCaption, color: tokens.statusError), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - ClideButton( - label: 'Cancel', - variant: ClideButtonVariant.subtle, - onPressed: onCancel, - ), + ClideButton(label: 'Cancel', variant: ClideButtonVariant.subtle, onPressed: onCancel), const SizedBox(width: 8), - ClideButton( - label: 'Discard', - onPressed: onConfirm, - ), + ClideButton(label: 'Discard', onPressed: onConfirm), ], ), ], diff --git a/lib/builtin/git/src/git_status_item.dart b/lib/builtin/git/src/git_status_item.dart index ec9c757c..fc7c25d1 100644 --- a/lib/builtin/git/src/git_status_item.dart +++ b/lib/builtin/git/src/git_status_item.dart @@ -51,13 +51,7 @@ class _GitStatusItemState extends State { void _openBranchPicker() { final kernel = ClideKernel.of(context); - kernel.dialog.show( - (ctx, dismiss) => _BranchPicker( - ipc: kernel.ipc, - currentBranch: _branch, - onDismiss: dismiss, - ), - ); + kernel.dialog.show((ctx, dismiss) => _BranchPicker(ipc: kernel.ipc, currentBranch: _branch, onDismiss: dismiss)); } @override @@ -79,17 +73,9 @@ class _GitStatusItemState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - ClideIcon( - const GitBranchIcon(), - size: 12, - color: tokens.statusBarForeground, - ), + ClideIcon(const GitBranchIcon(), size: 12, color: tokens.statusBarForeground), const SizedBox(width: 4), - ClideText( - parts.join(' '), - fontSize: clideFontCaption, - color: tokens.statusBarForeground, - ), + ClideText(parts.join(' '), fontSize: clideFontCaption, color: tokens.statusBarForeground), ], ), ), @@ -100,11 +86,7 @@ class _GitStatusItemState extends State { } class _BranchPicker extends StatefulWidget { - const _BranchPicker({ - required this.ipc, - required this.currentBranch, - required this.onDismiss, - }); + const _BranchPicker({required this.ipc, required this.currentBranch, required this.onDismiss}); final DaemonClient ipc; final String? currentBranch; @@ -139,9 +121,7 @@ class _BranchPickerState extends State<_BranchPicker> { setState(() { _loading = false; if (r.ok) { - _branches = [ - for (final b in (r.data['branches'] as List? ?? const [])) (b as Map).cast(), - ]; + _branches = [for (final b in (r.data['branches'] as List? ?? const [])) (b as Map).cast()]; } else { _error = r.error?.message ?? 'failed to load branches'; } @@ -210,11 +190,7 @@ class _BranchPickerState extends State<_BranchPicker> { final b = _branches[i]; final name = b['name'] as String? ?? ''; final current = b['current'] as bool? ?? false; - return _BranchRow( - name: name, - current: current, - onTap: current ? null : () => unawaited(_checkout(name)), - ); + return _BranchRow(name: name, current: current, onTap: current ? null : () => unawaited(_checkout(name))); }, ), ), @@ -226,11 +202,7 @@ class _BranchPickerState extends State<_BranchPicker> { } class _BranchRow extends StatelessWidget { - const _BranchRow({ - required this.name, - required this.current, - this.onTap, - }); + const _BranchRow({required this.name, required this.current, this.onTap}); final String name; final bool current; @@ -250,11 +222,7 @@ class _BranchRow extends StatelessWidget { if (current) Padding( padding: const EdgeInsets.only(right: 8), - child: ClideIcon( - const CheckIcon(), - size: 12, - color: tokens.statusSuccess, - ), + child: ClideIcon(const CheckIcon(), size: 12, color: tokens.statusSuccess), ) else const SizedBox(width: 20), diff --git a/lib/builtin/graph/src/graph_view.dart b/lib/builtin/graph/src/graph_view.dart index e6c2391d..2fea4c63 100644 --- a/lib/builtin/graph/src/graph_view.dart +++ b/lib/builtin/graph/src/graph_view.dart @@ -26,9 +26,12 @@ class _GraphViewState extends State { Future _load() async { final kernel = ClideKernel.of(context); - final resp = await kernel.ipc.request('pql.exec', args: { - 'argv': ['search', '--connections', '--limit', '50'], - }); + final resp = await kernel.ipc.request( + 'pql.exec', + args: { + 'argv': ['search', '--connections', '--limit', '50'], + }, + ); if (!mounted) return; if (!resp.ok) { setState(() { @@ -62,10 +65,7 @@ class _GraphViewState extends State { return Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true)); } if (_nodes.isEmpty) { - return const Padding( - padding: EdgeInsets.all(12), - child: ClideText('No linked files found.\nAdd wikilinks to your markdown files.', muted: true), - ); + return const Padding(padding: EdgeInsets.all(12), child: ClideText('No linked files found.\nAdd wikilinks to your markdown files.', muted: true)); } return ListView.builder( itemCount: _nodes.length, @@ -84,10 +84,10 @@ class _GraphNode { final int outbound; factory _GraphNode.fromJson(Map json) => _GraphNode( - path: json['path'] as String? ?? json['relative_path'] as String? ?? '', - inbound: (json['inbound_count'] as num?)?.toInt() ?? 0, - outbound: (json['outbound_count'] as num?)?.toInt() ?? 0, - ); + path: json['path'] as String? ?? json['relative_path'] as String? ?? '', + inbound: (json['inbound_count'] as num?)?.toInt() ?? 0, + outbound: (json['outbound_count'] as num?)?.toInt() ?? 0, + ); } class _NodeRow extends StatelessWidget { diff --git a/lib/builtin/ipc_status/src/extension.dart b/lib/builtin/ipc_status/src/extension.dart index 9ddf1bab..ed2e6087 100644 --- a/lib/builtin/ipc_status/src/extension.dart +++ b/lib/builtin/ipc_status/src/extension.dart @@ -10,11 +10,5 @@ class IpcStatusExtension extends ClideExtension { String get version => '0.2.0'; @override - List get contributions => [ - StatusItemContribution( - id: 'ipc-status.indicator', - priority: 100, - build: (_) => const ToolStatusItem(), - ), - ]; + List get contributions => [StatusItemContribution(id: 'ipc-status.indicator', priority: 100, build: (_) => const ToolStatusItem())]; } diff --git a/lib/builtin/ipc_status/src/status_item.dart b/lib/builtin/ipc_status/src/status_item.dart index 633bf3ff..8f1f3e00 100644 --- a/lib/builtin/ipc_status/src/status_item.dart +++ b/lib/builtin/ipc_status/src/status_item.dart @@ -36,7 +36,11 @@ class ToolStatusItem extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), + Container( + width: 7, + height: 7, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ), const SizedBox(width: 6), ClideText(label, fontSize: clideFontCaption, color: color), ], diff --git a/lib/builtin/keybindings_ui/src/extension.dart b/lib/builtin/keybindings_ui/src/extension.dart index 5c4a245a..617a5957 100644 --- a/lib/builtin/keybindings_ui/src/extension.dart +++ b/lib/builtin/keybindings_ui/src/extension.dart @@ -28,24 +28,19 @@ class KeybindingsUiExtension extends ClideExtension { /// Presets that ship today, each exposed as a `keymap.preset.` /// command that activates it. - static const _presets = { - 'default': 'Keymap: Default', - 'vim': 'Keymap: Vim', - 'vscode': 'Keymap: VS Code', - 'jetbrains': 'Keymap: JetBrains', - }; + static const _presets = {'default': 'Keymap: Default', 'vim': 'Keymap: Vim', 'vscode': 'Keymap: VS Code', 'jetbrains': 'Keymap: JetBrains'}; @override List get contributions => [ - for (final entry in _presets.entries) - CommandContribution( - id: 'keymap.preset.${entry.key}', - command: 'keymap.preset.${entry.key}', - title: entry.value, - run: (_) async { - await _keymap?.setPreset(entry.key); - return IpcResponse.ok(id: '', data: {'preset': entry.key}); - }, - ), - ]; + for (final entry in _presets.entries) + CommandContribution( + id: 'keymap.preset.${entry.key}', + command: 'keymap.preset.${entry.key}', + title: entry.value, + run: (_) async { + await _keymap?.setPreset(entry.key); + return IpcResponse.ok(id: '', data: {'preset': entry.key}); + }, + ), + ]; } diff --git a/lib/builtin/markdown/src/extension.dart b/lib/builtin/markdown/src/extension.dart index f32abf58..66824981 100644 --- a/lib/builtin/markdown/src/extension.dart +++ b/lib/builtin/markdown/src/extension.dart @@ -19,14 +19,14 @@ class MarkdownExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'markdown.viewer', - slot: Slots.contextPanel, - title: 'Markdown', - icon: PhosphorIcons.byName('file-text'), - build: (_) => const MarkdownViewer(), - ), - ]; + TabContribution( + id: 'markdown.viewer', + slot: Slots.contextPanel, + title: 'Markdown', + icon: PhosphorIcons.byName('file-text'), + build: (_) => const MarkdownViewer(), + ), + ]; @override Future activate(ClideExtensionContext ctx) async { diff --git a/lib/builtin/markdown/src/markdown_viewer.dart b/lib/builtin/markdown/src/markdown_viewer.dart index d0ef13d7..eb571842 100644 --- a/lib/builtin/markdown/src/markdown_viewer.dart +++ b/lib/builtin/markdown/src/markdown_viewer.dart @@ -95,18 +95,12 @@ class _MarkdownViewerState extends State { return Padding(padding: const EdgeInsets.all(12), child: ClideText(_error!, muted: true)); } if (_content == null) { - return const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Select a .md file to preview it here.', muted: true), - ); + return const Padding(padding: EdgeInsets.all(12), child: ClideText('Select a .md file to preview it here.', muted: true)); } return ClidePaneChrome( title: _path ?? 'viewer', subtitle: '${_content!.split('\n').length} lines', - leading: ReaderPinButton( - pinned: _nav?.hasPinned ?? false, - onTap: _path != null ? _onPin : null, - ), + leading: ReaderPinButton(pinned: _nav?.hasPinned ?? false, onTap: _path != null ? _onPin : null), trailing: [ ReaderActionBar( canGoBack: _nav?.canGoBack ?? false, diff --git a/lib/builtin/menubar/src/about_dialog.dart b/lib/builtin/menubar/src/about_dialog.dart index 49ff198b..6b1b60e3 100644 --- a/lib/builtin/menubar/src/about_dialog.dart +++ b/lib/builtin/menubar/src/about_dialog.dart @@ -66,7 +66,9 @@ class _Kv extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(width: 90, child: ClideText(label, fontSize: 13, color: tokens.globalTextMuted)), - Expanded(child: ClideText(value, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)), + Expanded( + child: ClideText(value, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis), + ), ], ), ); @@ -89,7 +91,10 @@ class _Licenses extends StatelessWidget { final deps = snap.data!.dependencies; return Container( constraints: const BoxConstraints(maxHeight: 260), - decoration: BoxDecoration(border: Border.all(color: tokens.globalBorder), borderRadius: BorderRadius.circular(4)), + decoration: BoxDecoration( + border: Border.all(color: tokens.globalBorder), + borderRadius: BorderRadius.circular(4), + ), child: ListView.builder( shrinkWrap: true, padding: const EdgeInsets.symmetric(vertical: 4), diff --git a/lib/builtin/menubar/src/extension.dart b/lib/builtin/menubar/src/extension.dart index 5b63dc88..6ccc5943 100644 --- a/lib/builtin/menubar/src/extension.dart +++ b/lib/builtin/menubar/src/extension.dart @@ -26,67 +26,77 @@ class MenuBarExtension extends ClideExtension { @override List get contributions => [ - CommandContribution( - id: 'file.openFolder', - command: 'file.openFolder', - title: 'File: Open Folder…', - run: (_) async { - await _file.openFolder(); - return IpcResponse.ok(id: '', data: const {}); - }, - ), - CommandContribution( - id: 'file.newWindow', - command: 'file.newWindow', - title: 'File: New Window', - run: (_) async { - _file.newWindow(); - return IpcResponse.ok(id: '', data: const {}); - }, - ), - CommandContribution( - id: 'file.closeWorkspace', - command: 'file.closeWorkspace', - title: 'File: Close Project', - run: (_) async { - _file.closeWorkspace(); - return IpcResponse.ok(id: '', data: const {}); - }, - ), - CommandContribution( - id: 'help.about', - command: 'help.about', - title: 'Help: About clide', - run: (_) async { - services.dialog.show((ctx, dismiss) => AboutDialog(onDismiss: () => dismiss())); - return IpcResponse.ok(id: '', data: const {}); - }, - ), - ]; + CommandContribution( + id: 'file.openFolder', + command: 'file.openFolder', + title: 'File: Open Folder…', + run: (_) async { + await _file.openFolder(); + return IpcResponse.ok(id: '', data: const {}); + }, + ), + CommandContribution( + id: 'file.newWindow', + command: 'file.newWindow', + title: 'File: New Window', + run: (_) async { + _file.newWindow(); + return IpcResponse.ok(id: '', data: const {}); + }, + ), + CommandContribution( + id: 'file.closeWorkspace', + command: 'file.closeWorkspace', + title: 'File: Close Project', + run: (_) async { + _file.closeWorkspace(); + return IpcResponse.ok(id: '', data: const {}); + }, + ), + CommandContribution( + id: 'help.about', + command: 'help.about', + title: 'Help: About clide', + run: (_) async { + services.dialog.show((ctx, dismiss) => AboutDialog(onDismiss: () => dismiss())); + return IpcResponse.ok(id: '', data: const {}); + }, + ), + ]; } /// The curated File / View / Help tree (T-48). View ends with a `view.*` /// auto-fill so newly-registered view commands surface without edits here. List buildClideMenuTree() => [ - TopMenu(title: 'File', mnemonic: 0, nodes: [ - const MenuCommandItem('file.openFolder', fallbackTitle: 'Open Folder…'), - const MenuCommandItem('file.newWindow', fallbackTitle: 'New Window'), - const MenuSeparator(), - MenuCommandItem('file.closeWorkspace', fallbackTitle: 'Close Project', enabledWhen: (s) => s.project.isOpen), - ]), - TopMenu(title: 'View', mnemonic: 0, nodes: const [ - MenuCommandItem('view.zoomIn'), - MenuCommandItem('view.zoomOut'), - MenuCommandItem('view.zoomReset'), - MenuSeparator(), - MenuCommandItem('sidebar.collapse'), - MenuCommandItem('context.collapse'), - MenuCommandItem('dock.toggle'), - MenuCommandItem('panel.focusMode'), - MenuSeparator(), - MenuAutoFill('view.'), - ]), - TopMenu(title: 'Help', mnemonic: 0, nodes: const [ - MenuCommandItem('help.about', fallbackTitle: 'About clide'), - ]), - ]; + TopMenu( + title: 'File', + mnemonic: 0, + nodes: [ + const MenuCommandItem('file.openFolder', fallbackTitle: 'Open Folder…'), + const MenuCommandItem('file.newWindow', fallbackTitle: 'New Window'), + const MenuSeparator(), + MenuCommandItem('file.closeWorkspace', fallbackTitle: 'Close Project', enabledWhen: (s) => s.project.isOpen), + ], + ), + TopMenu( + title: 'View', + mnemonic: 0, + nodes: const [ + MenuCommandItem('view.zoomIn'), + MenuCommandItem('view.zoomOut'), + MenuCommandItem('view.zoomReset'), + MenuSeparator(), + MenuCommandItem('sidebar.collapse'), + MenuCommandItem('context.collapse'), + MenuCommandItem('dock.toggle'), + MenuCommandItem('panel.focusMode'), + MenuSeparator(), + MenuAutoFill('view.'), + ], + ), + TopMenu( + title: 'Help', + mnemonic: 0, + nodes: const [MenuCommandItem('help.about', fallbackTitle: 'About clide')], + ), +]; diff --git a/lib/builtin/menubar/src/file_actions.dart b/lib/builtin/menubar/src/file_actions.dart index 56ec893d..053d6c44 100644 --- a/lib/builtin/menubar/src/file_actions.dart +++ b/lib/builtin/menubar/src/file_actions.dart @@ -143,10 +143,7 @@ class _OpenFolderDialogState extends State { onSubmitted: (_) => unawaited(_submit()), ), ), - if (_error != null) ...[ - const SizedBox(height: 8), - ClideText(_error!, color: tokens.statusError, fontSize: 12), - ], + if (_error != null) ...[const SizedBox(height: 8), ClideText(_error!, color: tokens.statusError, fontSize: 12)], const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -188,17 +185,11 @@ class NotARepoDialog extends StatelessWidget { const SizedBox(height: 8), ClideText(path, muted: true, fontSize: 13), const SizedBox(height: 8), - const ClideText( - 'A clide project root requires a git repository.', - muted: true, - fontSize: 13, - ), + const ClideText('A clide project root requires a git repository.', muted: true, fontSize: 13), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, - children: [ - ClideButton(label: 'OK', onPressed: () => onDismiss()), - ], + children: [ClideButton(label: 'OK', onPressed: () => onDismiss())], ), ], ), diff --git a/lib/builtin/menubar/src/menu_bar.dart b/lib/builtin/menubar/src/menu_bar.dart index 41e5d3a0..ac063f4e 100644 --- a/lib/builtin/menubar/src/menu_bar.dart +++ b/lib/builtin/menubar/src/menu_bar.dart @@ -72,18 +72,11 @@ class MenuBar extends StatelessWidget { return ListenableBuilder( listenable: Listenable.merge([controller, kernel.commands, kernel.project]), builder: (ctx, _) { - final menus = resolveMenus( - buildClideMenuTree(), - kernel.commands, - kernel, - bindingLabel: (id) => keymapBindingLabel(kernel.keymap, id), - ); + final menus = resolveMenus(buildClideMenuTree(), kernel.commands, kernel, bindingLabel: (id) => keymapBindingLabel(kernel.keymap, id)); controller.setMnemonics([for (final m in menus) m.title[m.mnemonic].toLowerCase()]); return Row( mainAxisSize: MainAxisSize.min, - children: [ - for (var i = 0; i < menus.length; i++) _TopMenuButton(index: i, menu: menus[i], controller: controller, kernel: kernel), - ], + children: [for (var i = 0; i < menus.length; i++) _TopMenuButton(index: i, menu: menus[i], controller: controller, kernel: kernel)], ); }, ); @@ -189,11 +182,7 @@ class _TopMenuButtonState extends State<_TopMenuButton> { alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: clideInsetStandard), color: open || hovered ? tokens.listItemHoverBackground : null, - child: ClideText( - widget.menu.title, - fontSize: 12, - color: open || hovered ? tokens.globalForeground : tokens.chromeForeground, - ), + child: ClideText(widget.menu.title, fontSize: 12, color: open || hovered ? tokens.globalForeground : tokens.chromeForeground), ), ), ), diff --git a/lib/builtin/menubar/src/menu_model.dart b/lib/builtin/menubar/src/menu_model.dart index e0d8b071..e472c253 100644 --- a/lib/builtin/menubar/src/menu_model.dart +++ b/lib/builtin/menubar/src/menu_model.dart @@ -108,12 +108,7 @@ String? keymapBindingLabel(KeymapService keymap, String commandId) { /// menus. [bindingLabel] supplies the keybinding string for a command id /// (typically [keymapBindingLabel] bound to the keymap); when it returns null /// the command's own `defaultBinding` is used as a fallback. -List resolveMenus( - List tree, - CommandRegistry registry, - KernelServices services, { - String? Function(String commandId)? bindingLabel, -}) { +List resolveMenus(List tree, CommandRegistry registry, KernelServices services, {String? Function(String commandId)? bindingLabel}) { final placed = { for (final m in tree) for (final n in m.nodes) @@ -140,12 +135,10 @@ List resolveMenus( } List expand(MenuNode n) => switch (n) { - MenuCommandItem() => [resolveItem(n)], - MenuSeparator() => const [ResolvedSeparator()], - MenuAutoFill(:final prefix) => [ - for (final c in _autoFill(registry, prefix, placed)) resolveItem(MenuCommandItem(c.command)), - ], - }; + MenuCommandItem() => [resolveItem(n)], + MenuSeparator() => const [ResolvedSeparator()], + MenuAutoFill(:final prefix) => [for (final c in _autoFill(registry, prefix, placed)) resolveItem(MenuCommandItem(c.command))], + }; return [ for (final m in tree) ResolvedMenu(title: m.title, mnemonic: m.mnemonic, items: [for (final n in m.nodes) ...expand(n)]), diff --git a/lib/builtin/output/src/dock_status_item.dart b/lib/builtin/output/src/dock_status_item.dart index 242ea045..68c0c691 100644 --- a/lib/builtin/output/src/dock_status_item.dart +++ b/lib/builtin/output/src/dock_status_item.dart @@ -60,8 +60,8 @@ class _DockStatusItemState extends State { final (String badge, Color color) = errors > 0 ? ('✕ $errors', tokens.statusError) : warns > 0 - ? ('⚠ $warns', tokens.statusWarning) - : ('✓', tokens.statusSuccess); + ? ('⚠ $warns', tokens.statusWarning) + : ('✓', tokens.statusSuccess); return Semantics( button: true, label: 'toggle output dock', diff --git a/lib/builtin/output/src/extension.dart b/lib/builtin/output/src/extension.dart index 70060a76..0974235a 100644 --- a/lib/builtin/output/src/extension.dart +++ b/lib/builtin/output/src/extension.dart @@ -30,34 +30,34 @@ class OutputExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'output.panel', - slot: Slots.dock, - title: 'Output', - priority: -100, // sort before Problems in the dock tab bar - build: (ctx) => OutputView(ring: ClideKernel.of(ctx).logRing), - ), - StatusItemContribution( - id: 'output.dock-toggle', - priority: 100, // right group, replacing the old app-status item - build: (_) => const DockStatusItem(), - ), - CommandContribution( - id: 'dock.toggle', - command: 'dock.toggle', - title: 'Toggle output dock', - defaultBinding: 'ctrl+j', - run: (_) async { - final ctx = _ctx; - if (ctx == null) return IpcResponse.ok(id: '', data: const {}); - final a = ctx.arrangement; - final opening = !a.isVisible(Slots.dock); - a.setVisible(Slots.dock, opening); - if (opening && ctx.panels.activeTabIn(Slots.dock) == null) { - ctx.panels.activateTab(Slots.dock, 'output.panel'); - } - return IpcResponse.ok(id: '', data: {'dock': opening}); - }, - ), - ]; + TabContribution( + id: 'output.panel', + slot: Slots.dock, + title: 'Output', + priority: -100, // sort before Problems in the dock tab bar + build: (ctx) => OutputView(ring: ClideKernel.of(ctx).logRing), + ), + StatusItemContribution( + id: 'output.dock-toggle', + priority: 100, // right group, replacing the old app-status item + build: (_) => const DockStatusItem(), + ), + CommandContribution( + id: 'dock.toggle', + command: 'dock.toggle', + title: 'Toggle output dock', + defaultBinding: 'ctrl+j', + run: (_) async { + final ctx = _ctx; + if (ctx == null) return IpcResponse.ok(id: '', data: const {}); + final a = ctx.arrangement; + final opening = !a.isVisible(Slots.dock); + a.setVisible(Slots.dock, opening); + if (opening && ctx.panels.activeTabIn(Slots.dock) == null) { + ctx.panels.activateTab(Slots.dock, 'output.panel'); + } + return IpcResponse.ok(id: '', data: {'dock': opening}); + }, + ), + ]; } diff --git a/lib/builtin/output/src/output_view.dart b/lib/builtin/output/src/output_view.dart index abb33111..d0010510 100644 --- a/lib/builtin/output/src/output_view.dart +++ b/lib/builtin/output/src/output_view.dart @@ -84,10 +84,7 @@ class _OutputViewState extends State { child: rows.isEmpty ? Padding( padding: const EdgeInsets.all(12), - child: ClideText( - widget.ring.isEmpty ? 'No output yet.' : 'No output matches the filter.', - muted: true, - ), + child: ClideText(widget.ring.isEmpty ? 'No output yet.' : 'No output matches the filter.', muted: true), ) : Stack( children: [ @@ -100,12 +97,7 @@ class _OutputViewState extends State { children: [for (final r in rows) _LogRow(record: r)], ), ), - if (!_following) - Positioned( - right: 12, - bottom: 8, - child: _JumpPill(onTap: _jumpToLatest), - ), + if (!_following) Positioned(right: 12, bottom: 8, child: _JumpPill(onTap: _jumpToLatest)), ], ), ), @@ -128,15 +120,9 @@ class _OutputViewState extends State { child: ClideFilterBox(address: 'output.panel', hint: 'Filter…', onChanged: _c.setText), ), const SizedBox(width: 8), - _Chip( - label: 'Level: ${_c.minLevel.name}', - onTap: () => _c.setMinLevel(LogLevel.values[(_c.minLevel.index + 1) % LogLevel.values.length]), - ), + _Chip(label: 'Level: ${_c.minLevel.name}', onTap: () => _c.setMinLevel(LogLevel.values[(_c.minLevel.index + 1) % LogLevel.values.length])), const SizedBox(width: 6), - _Chip( - label: 'Source: ${_c.source ?? 'all'}', - onTap: _cycleSource, - ), + _Chip(label: 'Source: ${_c.source ?? 'all'}', onTap: _cycleSource), const SizedBox(width: 6), _Chip(label: 'Clear', onTap: _c.clear), ], @@ -235,8 +221,14 @@ class _LogRow extends StatelessWidget { const SizedBox(width: 8), SizedBox( width: 92, - child: ClideText(record.source, - fontSize: clideFontMono, color: tokens.globalTextMuted, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.clip), + child: ClideText( + record.source, + fontSize: clideFontMono, + color: tokens.globalTextMuted, + fontFamily: clideMonoFamily, + maxLines: 1, + overflow: TextOverflow.clip, + ), ), const SizedBox(width: 8), Expanded( @@ -248,9 +240,9 @@ class _LogRow extends StatelessWidget { } Color _levelColor(LogLevel level, SurfaceTokens tokens) => switch (level) { - LogLevel.error => tokens.statusError, - LogLevel.warn => tokens.statusWarning, - LogLevel.info => tokens.globalForeground, - LogLevel.debug || LogLevel.trace => tokens.globalTextMuted, - }; + LogLevel.error => tokens.statusError, + LogLevel.warn => tokens.statusWarning, + LogLevel.info => tokens.globalForeground, + LogLevel.debug || LogLevel.trace => tokens.globalTextMuted, + }; } diff --git a/lib/builtin/pql/src/backlinks_view.dart b/lib/builtin/pql/src/backlinks_view.dart index fb2bff44..4f2ead9a 100644 --- a/lib/builtin/pql/src/backlinks_view.dart +++ b/lib/builtin/pql/src/backlinks_view.dart @@ -42,13 +42,7 @@ class _BacklinksViewState extends State { builder: (context, _) { final tokens = ClideTheme.of(context).surface; if (c.activePath == null) { - return const Padding( - padding: EdgeInsets.all(12), - child: ClideText( - 'Open a file to see its links.', - muted: true, - ), - ); + return const Padding(padding: EdgeInsets.all(12), child: ClideText('Open a file to see its links.', muted: true)); } return Semantics( label: 'backlinks for ${c.activePath}', @@ -62,35 +56,16 @@ class _BacklinksViewState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: ClideText( - c.activePath!.split('/').last, - color: tokens.globalForeground, - ), + child: ClideText(c.activePath!.split('/').last, color: tokens.globalForeground), ), if (c.error != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: ClideText( - c.error!, - color: tokens.statusError, - fontSize: clideFontCaption, - ), + child: ClideText(c.error!, color: tokens.statusError, fontSize: clideFontCaption), ), - if (c.loading) - const Padding( - padding: EdgeInsets.all(12), - child: ClideText('Loading…', muted: true), - ), - _LinkGroup( - label: 'Backlinks', - links: c.backlinks, - pathKey: 'source', - ), - _LinkGroup( - label: 'Outlinks', - links: c.outlinks, - pathKey: 'target', - ), + if (c.loading) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)), + _LinkGroup(label: 'Backlinks', links: c.backlinks, pathKey: 'source'), + _LinkGroup(label: 'Outlinks', links: c.outlinks, pathKey: 'target'), ], ), ), @@ -101,11 +76,7 @@ class _BacklinksViewState extends State { } class _LinkGroup extends StatelessWidget { - const _LinkGroup({ - required this.label, - required this.links, - required this.pathKey, - }); + const _LinkGroup({required this.label, required this.links, required this.pathKey}); final String label; final List> links; @@ -119,11 +90,7 @@ class _LinkGroup extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 2), - child: ClideText( - '$label (${links.length})', - fontSize: clideFontCaption, - muted: true, - ), + child: ClideText('$label (${links.length})', fontSize: clideFontCaption, muted: true), ), if (links.isEmpty) const Padding( diff --git a/lib/builtin/pql/src/extension.dart b/lib/builtin/pql/src/extension.dart index bfeaf838..dd98e354 100644 --- a/lib/builtin/pql/src/extension.dart +++ b/lib/builtin/pql/src/extension.dart @@ -17,13 +17,13 @@ class PqlExtension extends ClideExtension { // tab (T-201); this extension keeps only the Backlinks context panel. @override List get contributions => [ - TabContribution( - id: 'pql.backlinks', - slot: Slots.contextPanel, - title: 'Links', - icon: PhosphorIcons.byName('link'), - priority: -80, - build: (_) => const BacklinksView(), - ), - ]; + TabContribution( + id: 'pql.backlinks', + slot: Slots.contextPanel, + title: 'Links', + icon: PhosphorIcons.byName('link'), + priority: -80, + build: (_) => const BacklinksView(), + ), + ]; } diff --git a/lib/builtin/pql/src/pql_controller.dart b/lib/builtin/pql/src/pql_controller.dart index a5163bab..bd9712ba 100644 --- a/lib/builtin/pql/src/pql_controller.dart +++ b/lib/builtin/pql/src/pql_controller.dart @@ -71,10 +71,7 @@ class PqlController extends ChangeNotifier { _error = null; notifyListeners(); - final r = await ipc.request('pql.search', args: { - 'terms': terms, - 'limit': 50, - }); + final r = await ipc.request('pql.search', args: {'terms': terms, 'limit': 50}); _loading = false; if (!r.ok) { @@ -91,10 +88,7 @@ class PqlController extends ChangeNotifier { _loading = true; notifyListeners(); - final r = await ipc.request('pql.files', args: { - 'glob': glob ?? '**/*.md', - 'limit': 200, - }); + final r = await ipc.request('pql.files', args: {'glob': glob ?? '**/*.md', 'limit': 200}); _loading = false; if (!r.ok) { @@ -113,10 +107,7 @@ class PqlController extends ChangeNotifier { _error = null; notifyListeners(); - final r = await ipc.request('pql.query', args: { - 'query': dsl, - 'limit': 200, - }); + final r = await ipc.request('pql.query', args: {'query': dsl, 'limit': 200}); _loading = false; if (!r.ok) { diff --git a/lib/builtin/pql/src/pql_search_body.dart b/lib/builtin/pql/src/pql_search_body.dart index 724fa0e8..075aaa3a 100644 --- a/lib/builtin/pql/src/pql_search_body.dart +++ b/lib/builtin/pql/src/pql_search_body.dart @@ -59,8 +59,8 @@ class _PqlSearchBodyState extends State { .on() .where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && (e.data['path'] as String? ?? '').endsWith('.md')) .listen((_) { - if (widget.controller.view == PqlView.markdown) unawaited(widget.controller.loadMarkdownFiles()); - }); + if (widget.controller.view == PqlView.markdown) unawaited(widget.controller.loadMarkdownFiles()); + }); } @override @@ -187,10 +187,7 @@ class _SearchResultRow extends StatelessWidget { }, builder: (context, hovered, _) => Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - decoration: BoxDecoration( - color: hovered ? tokens.sidebarItemHover : null, - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: hovered ? tokens.sidebarItemHover : null, borderRadius: BorderRadius.circular(4)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -259,13 +256,7 @@ class _FileRow extends StatelessWidget { borderRadius: BorderRadius.circular(4), border: focused ? Border.all(color: tokens.globalFocus, width: 1) : null, ), - child: ClideText( - path, - maxLines: 1, - overflow: TextOverflow.ellipsis, - fontSize: clideFontCaption, - color: tokens.sidebarForeground, - ), + child: ClideText(path, maxLines: 1, overflow: TextOverflow.ellipsis, fontSize: clideFontCaption, color: tokens.sidebarForeground), ), ), ); diff --git a/lib/builtin/problems/src/extension.dart b/lib/builtin/problems/src/extension.dart index cc69f0ab..be39dcd5 100644 --- a/lib/builtin/problems/src/extension.dart +++ b/lib/builtin/problems/src/extension.dart @@ -14,16 +14,16 @@ class ProblemsExtension extends ClideExtension { @override List get contributions => [ - // Moved out of the sidebar into the bottom dock (D-87): no duplication, - // and the dock's width fits `severity · file:line · message` rows. - TabContribution( - id: 'problems.panel', - slot: Slots.dock, - title: 'Problems', - titleKey: 'tab.title', - i18nNamespace: id, - priority: -50, - build: (_) => const ProblemsView(), - ), - ]; + // Moved out of the sidebar into the bottom dock (D-87): no duplication, + // and the dock's width fits `severity · file:line · message` rows. + TabContribution( + id: 'problems.panel', + slot: Slots.dock, + title: 'Problems', + titleKey: 'tab.title', + i18nNamespace: id, + priority: -50, + build: (_) => const ProblemsView(), + ), + ]; } diff --git a/lib/builtin/problems/src/problems_controller.dart b/lib/builtin/problems/src/problems_controller.dart index f241a2f9..790b2c4b 100644 --- a/lib/builtin/problems/src/problems_controller.dart +++ b/lib/builtin/problems/src/problems_controller.dart @@ -16,11 +16,7 @@ class Problem { final String message; final String? hint; - Map toJson() => { - 'source': source, - 'message': message, - if (hint != null) 'hint': hint, - }; + Map toJson() => {'source': source, 'message': message, if (hint != null) 'hint': hint}; } class ProblemsController extends ChangeNotifier { @@ -47,11 +43,7 @@ class ProblemsController extends ChangeNotifier { if (doctor.ok) { final db = (doctor.data['db'] as Map?)?.cast(); if (db != null && db['exists'] == false) { - found.add(const Problem( - source: 'pql', - message: 'pql index database not found', - hint: 'Run pql to build the index.', - )); + found.add(const Problem(source: 'pql', message: 'pql index database not found', hint: 'Run pql to build the index.')); } final skill = (doctor.data['skill'] as Map?)?.cast(); if (skill != null) { @@ -59,37 +51,21 @@ class ProblemsController extends ChangeNotifier { if (project != null) { final state = project['state'] as String?; if (state == 'stale') { - found.add(const Problem( - source: 'pql', - message: 'pql skill is stale — newer version available', - hint: 'Run: pql skill install', - )); + found.add(const Problem(source: 'pql', message: 'pql skill is stale — newer version available', hint: 'Run: pql skill install')); } else if (state == 'missing') { - found.add(const Problem( - source: 'pql', - message: 'pql skill not installed', - hint: 'Run: pql init --with-skill=yes', - )); + found.add(const Problem(source: 'pql', message: 'pql skill not installed', hint: 'Run: pql init --with-skill=yes')); } } } } else { - found.add(Problem( - source: 'pql', - message: 'pql doctor failed', - hint: doctor.error?.message, - )); + found.add(Problem(source: 'pql', message: 'pql doctor failed', hint: doctor.error?.message)); } final sync = await ipc.request('pql.decisions.sync'); if (sync.ok) { final broken = (sync.data['broken'] as num?)?.toInt() ?? 0; if (broken > 0) { - found.add(Problem( - source: 'decisions', - message: '$broken broken cross-reference(s) in governance/', - hint: 'Run: pql decisions validate', - )); + found.add(Problem(source: 'decisions', message: '$broken broken cross-reference(s) in governance/', hint: 'Run: pql decisions validate')); } } diff --git a/lib/builtin/problems/src/problems_view.dart b/lib/builtin/problems/src/problems_view.dart index 065d5112..f63f81a0 100644 --- a/lib/builtin/problems/src/problems_view.dart +++ b/lib/builtin/problems/src/problems_view.dart @@ -49,8 +49,9 @@ class _ProblemsViewState extends State { explicitChildNodes: true, child: () { final lf = _filter.toLowerCase(); - final filtered = - lf.isEmpty ? c.problems : c.problems.where((p) => p.message.toLowerCase().contains(lf) || p.source.toLowerCase().contains(lf)).toList(); + final filtered = lf.isEmpty + ? c.problems + : c.problems.where((p) => p.message.toLowerCase().contains(lf) || p.source.toLowerCase().contains(lf)).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -59,14 +60,18 @@ class _ProblemsViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), child: Row( children: [ - Expanded(child: ClideText('Problems (${filtered.length})', fontSize: clideFontCaption, color: tokens.sidebarForeground)), + Expanded( + child: ClideText('Problems (${filtered.length})', fontSize: clideFontCaption, color: tokens.sidebarForeground), + ), Semantics( button: true, label: 'refresh problems', child: GestureDetector( onTap: () => unawaited(c.refresh()), child: MouseRegion( - cursor: SystemMouseCursors.click, child: ClideText('Refresh', fontSize: clideFontCaption, color: tokens.sidebarForeground)), + cursor: SystemMouseCursors.click, + child: ClideText('Refresh', fontSize: clideFontCaption, color: tokens.sidebarForeground), + ), ), ), ], @@ -108,31 +113,15 @@ class _ProblemRow extends StatelessWidget { children: [ Row( children: [ - ClideText( - problem.source, - fontSize: clideFontMono, - color: tokens.statusWarning, - fontFamily: clideMonoFamily, - ), + ClideText(problem.source, fontSize: clideFontMono, color: tokens.statusWarning, fontFamily: clideMonoFamily), const SizedBox(width: 6), - Expanded( - child: ClideText( - problem.message, - color: tokens.sidebarForeground, - maxLines: 2, - ), - ), + Expanded(child: ClideText(problem.message, color: tokens.sidebarForeground, maxLines: 2)), ], ), if (problem.hint != null) Padding( padding: const EdgeInsets.only(left: 44, top: 2), - child: ClideText( - problem.hint!, - fontSize: clideFontMono, - muted: true, - fontFamily: clideMonoFamily, - ), + child: ClideText(problem.hint!, fontSize: clideFontMono, muted: true, fontFamily: clideMonoFamily), ), ], ), diff --git a/lib/builtin/search/src/extension.dart b/lib/builtin/search/src/extension.dart index 55a15ab0..6fbecd06 100644 --- a/lib/builtin/search/src/extension.dart +++ b/lib/builtin/search/src/extension.dart @@ -19,13 +19,13 @@ class SearchExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'search.findInFiles', - slot: Slots.sidebar, - title: 'Search', - icon: PhosphorIcons.byName('magnifying-glass'), - priority: -90, - build: (_) => const SearchPanelView(), - ), - ]; + TabContribution( + id: 'search.findInFiles', + slot: Slots.sidebar, + title: 'Search', + icon: PhosphorIcons.byName('magnifying-glass'), + priority: -90, + build: (_) => const SearchPanelView(), + ), + ]; } diff --git a/lib/builtin/search/src/find_in_files_controller.dart b/lib/builtin/search/src/find_in_files_controller.dart index 332ac7ee..7ebadf2c 100644 --- a/lib/builtin/search/src/find_in_files_controller.dart +++ b/lib/builtin/search/src/find_in_files_controller.dart @@ -86,15 +86,18 @@ class FindInFilesController extends ChangeNotifier { /// results. Returns the number of files changed and matches replaced. /// Callers must gate on [isWorkingTreeClean] + user confirmation first. Future<({int files, int count})> applyReplace() async { - final r = await ipc.request('search.replace', args: { - 'pattern': pattern, - 'regex': regex, - 'ignoreCase': ignoreCase, - 'include': _split(includeGlobs), - 'exclude': _split(excludeGlobs), - 'replacement': replacement, - 'apply': true, - }); + final r = await ipc.request( + 'search.replace', + args: { + 'pattern': pattern, + 'regex': regex, + 'ignoreCase': ignoreCase, + 'include': _split(includeGlobs), + 'exclude': _split(excludeGlobs), + 'replacement': replacement, + 'apply': true, + }, + ); final files = (r.data['filesChanged'] as num?)?.toInt() ?? 0; final count = (r.data['totalCount'] as num?)?.toInt() ?? 0; await run(pattern); // refresh the match list against the new content @@ -123,13 +126,10 @@ class FindInFilesController extends ChangeNotifier { _running = true; notifyListeners(); - final resp = await ipc.request('search.grep', args: { - 'pattern': pattern, - 'regex': regex, - 'ignoreCase': ignoreCase, - 'include': _split(includeGlobs), - 'exclude': _split(excludeGlobs), - }); + final resp = await ipc.request( + 'search.grep', + args: {'pattern': pattern, 'regex': regex, 'ignoreCase': ignoreCase, 'include': _split(includeGlobs), 'exclude': _split(excludeGlobs)}, + ); if (!resp.ok) { _error = resp.error?.message ?? 'search failed'; _running = false; diff --git a/lib/builtin/search/src/search_panel_view.dart b/lib/builtin/search/src/search_panel_view.dart index 6b6a3473..1e4cb608 100644 --- a/lib/builtin/search/src/search_panel_view.dart +++ b/lib/builtin/search/src/search_panel_view.dart @@ -50,17 +50,16 @@ class _SearchPanelViewState extends State { if (c.replacement.isEmpty || c.matchCount == 0) return; final dialog = ClideKernel.of(context).dialog; if (!await c.isWorkingTreeClean()) { - await dialog.show((ctx, dismiss) => _MessageDialog( - title: 'Working tree not clean', - body: 'Commit or stash your changes before replacing — git is the only undo.', - dismiss: dismiss, - )); + await dialog.show( + (ctx, dismiss) => + _MessageDialog(title: 'Working tree not clean', body: 'Commit or stash your changes before replacing — git is the only undo.', dismiss: dismiss), + ); return; } - final confirmed = await dialog.show((ctx, dismiss) => _ConfirmDialog( - body: 'Replace ${c.matchCount} match(es) across ${c.fileCount} file(s)? This cannot be undone in clide.', - dismiss: dismiss, - )); + final confirmed = await dialog.show( + (ctx, dismiss) => + _ConfirmDialog(body: 'Replace ${c.matchCount} match(es) across ${c.fileCount} file(s)? This cannot be undone in clide.', dismiss: dismiss), + ); if (confirmed != true) return; await c.applyReplace(); } @@ -138,26 +137,34 @@ class _SearchPanelViewState extends State { Row( children: [ Expanded( - child: ClideFilterBox( - address: 'search.findInFiles.replace', hint: 'Replace', icon: null, debounce: Duration.zero, onChanged: c.setReplacement)), - const SizedBox(width: 6), - _ReplaceAllButton( - enabled: c.replacement.isNotEmpty && c.matchCount > 0, - tokens: tokens, - onTap: _replaceAll, + child: ClideFilterBox( + address: 'search.findInFiles.replace', + hint: 'Replace', + icon: null, + debounce: Duration.zero, + onChanged: c.setReplacement, + ), ), + const SizedBox(width: 6), + _ReplaceAllButton(enabled: c.replacement.isNotEmpty && c.matchCount > 0, tokens: tokens, onTap: _replaceAll), ], ), const SizedBox(height: 6), ClideFilterBox( - address: 'search.findInFiles.include', - hint: 'files to include (e.g. *.dart)', - icon: null, - debounce: Duration.zero, - onChanged: (v) => c.include = v), + address: 'search.findInFiles.include', + hint: 'files to include (e.g. *.dart)', + icon: null, + debounce: Duration.zero, + onChanged: (v) => c.include = v, + ), const SizedBox(height: 4), ClideFilterBox( - address: 'search.findInFiles.exclude', hint: 'files to exclude', icon: null, debounce: Duration.zero, onChanged: (v) => c.exclude = v), + address: 'search.findInFiles.exclude', + hint: 'files to exclude', + icon: null, + debounce: Duration.zero, + onChanged: (v) => c.exclude = v, + ), ], ), ), @@ -171,14 +178,7 @@ class _SearchPanelViewState extends State { padding: EdgeInsets.zero, children: [ for (final entry in groups.entries) - _FileGroup( - path: entry.key, - matches: entry.value, - tokens: tokens, - onTap: c.openMatch, - query: c.query, - replacement: c.replacement, - ), + _FileGroup(path: entry.key, matches: entry.value, tokens: tokens, onTap: c.openMatch, query: c.query, replacement: c.replacement), ], ), ), @@ -196,18 +196,15 @@ class _ModeSwitcher extends StatelessWidget { final SurfaceTokens tokens; final ValueChanged onSelect; - static const _labels = { - SearchTabMode.find: 'Find', - SearchTabMode.vault: 'Vault', - SearchTabMode.query: 'Query', - SearchTabMode.markdown: 'Markdown', - }; + static const _labels = {SearchTabMode.find: 'Find', SearchTabMode.vault: 'Vault', SearchTabMode.query: 'Query', SearchTabMode.markdown: 'Markdown'}; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), - decoration: BoxDecoration(border: Border(bottom: BorderSide(color: tokens.panelBorder))), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: tokens.panelBorder)), + ), child: Row( children: [ for (final m in SearchTabMode.values) ...[ @@ -233,13 +230,7 @@ class _ModeSwitcher extends StatelessWidget { } class _Toggle extends StatelessWidget { - const _Toggle({ - required this.label, - required this.tooltip, - required this.active, - required this.tokens, - required this.onTap, - }); + const _Toggle({required this.label, required this.tooltip, required this.active, required this.tokens, required this.onTap}); final String label; final String tooltip; @@ -296,14 +287,7 @@ class _StatusText extends StatelessWidget { } class _FileGroup extends StatelessWidget { - const _FileGroup({ - required this.path, - required this.matches, - required this.tokens, - required this.onTap, - required this.query, - required this.replacement, - }); + const _FileGroup({required this.path, required this.matches, required this.tokens, required this.onTap, required this.query, required this.replacement}); final String path; final List matches; @@ -336,13 +320,7 @@ class _FileGroup extends StatelessWidget { } class _MatchRow extends StatelessWidget { - const _MatchRow({ - required this.match, - required this.tokens, - required this.onTap, - required this.query, - required this.replacement, - }); + const _MatchRow({required this.match, required this.tokens, required this.onTap, required this.query, required this.replacement}); final SearchMatch match; final SurfaceTokens tokens; @@ -385,11 +363,17 @@ class _MatchRow extends StatelessWidget { return RichText( maxLines: 1, overflow: TextOverflow.ellipsis, - text: TextSpan(style: _base, children: [ - TextSpan(text: line.substring(0, start)), - TextSpan(text: line.substring(start, end), style: _base.copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold)), - TextSpan(text: line.substring(end)), - ]), + text: TextSpan( + style: _base, + children: [ + TextSpan(text: line.substring(0, start)), + TextSpan( + text: line.substring(start, end), + style: _base.copyWith(color: tokens.globalFocus, fontWeight: FontWeight.bold), + ), + TextSpan(text: line.substring(end)), + ], + ), ); } @@ -403,12 +387,18 @@ class _MatchRow extends StatelessWidget { RichText( maxLines: 1, overflow: TextOverflow.ellipsis, - text: TextSpan(text: match.preview, style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted)), + text: TextSpan( + text: match.preview, + style: _base.copyWith(decoration: TextDecoration.lineThrough, color: tokens.globalTextMuted), + ), ), RichText( maxLines: 1, overflow: TextOverflow.ellipsis, - text: TextSpan(text: after, style: _base.copyWith(color: tokens.globalFocus)), + text: TextSpan( + text: after, + style: _base.copyWith(color: tokens.globalFocus), + ), ), ], ); @@ -437,11 +427,7 @@ class _ReplaceAllButton extends StatelessWidget { border: Border.all(color: tokens.buttonBorder), borderRadius: BorderRadius.circular(4), ), - child: ClideText( - 'Replace all', - fontSize: clideFontCaption, - color: enabled ? tokens.sidebarForeground : tokens.globalTextMuted, - ), + child: ClideText('Replace all', fontSize: clideFontCaption, color: enabled ? tokens.sidebarForeground : tokens.globalTextMuted), ), ), ); diff --git a/lib/builtin/shared/reader_chrome.dart b/lib/builtin/shared/reader_chrome.dart index d045360f..31cd48df 100644 --- a/lib/builtin/shared/reader_chrome.dart +++ b/lib/builtin/shared/reader_chrome.dart @@ -50,13 +50,7 @@ class ReaderActionBar extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - _ActionButton( - painter: PhosphorIcons.byName('caret-left'), - tooltip: 'Back', - enabled: canGoBack, - onTap: canGoBack ? onBack : null, - tokens: tokens, - ), + _ActionButton(painter: PhosphorIcons.byName('caret-left'), tooltip: 'Back', enabled: canGoBack, onTap: canGoBack ? onBack : null, tokens: tokens), const SizedBox(width: 2), _ActionButton( painter: PhosphorIcons.byName('caret-right'), @@ -67,23 +61,11 @@ class ReaderActionBar extends StatelessWidget { ), if (hasPinned) ...[ const SizedBox(width: 2), - _ActionButton( - painter: PhosphorIcons.byName('arrow-u-up-left'), - tooltip: 'Jump to pin', - enabled: true, - onTap: onJumpToPin, - tokens: tokens, - ), + _ActionButton(painter: PhosphorIcons.byName('arrow-u-up-left'), tooltip: 'Jump to pin', enabled: true, onTap: onJumpToPin, tokens: tokens), ], if (onEdit != null) ...[ const SizedBox(width: 4), - _ActionButton( - painter: PhosphorIcons.byName('pencil-simple'), - tooltip: 'Edit in editor', - enabled: true, - onTap: onEdit, - tokens: tokens, - ), + _ActionButton(painter: PhosphorIcons.byName('pencil-simple'), tooltip: 'Edit in editor', enabled: true, onTap: onEdit, tokens: tokens), ], ], ); @@ -118,14 +100,7 @@ class ReaderPinButton extends StatelessWidget { // --------------------------------------------------------------------------- class _ActionButton extends StatelessWidget { - const _ActionButton({ - required this.painter, - required this.tooltip, - required this.enabled, - required this.onTap, - required this.tokens, - this.active = false, - }); + const _ActionButton({required this.painter, required this.tooltip, required this.enabled, required this.onTap, required this.tokens, this.active = false}); final ClideIconPainter painter; final String tooltip; @@ -161,10 +136,7 @@ class _ActionButton extends StatelessWidget { width: 20, height: 20, alignment: Alignment.center, - decoration: BoxDecoration( - color: hovered && enabled ? tokens.sidebarItemHover : null, - borderRadius: BorderRadius.circular(3), - ), + decoration: BoxDecoration(color: hovered && enabled ? tokens.sidebarItemHover : null, borderRadius: BorderRadius.circular(3)), child: ClideIcon(painter, size: 11, color: color), ), ), diff --git a/lib/builtin/terminal/src/extension.dart b/lib/builtin/terminal/src/extension.dart index 6438efa6..b4c318d5 100644 --- a/lib/builtin/terminal/src/extension.dart +++ b/lib/builtin/terminal/src/extension.dart @@ -18,14 +18,14 @@ class TerminalExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'terminal.pane', - slot: Slots.workspace, - title: 'Terminal', - titleKey: 'tab.title', - i18nNamespace: id, - priority: 100, - build: (_) => const TerminalPane(), - ), - ]; + TabContribution( + id: 'terminal.pane', + slot: Slots.workspace, + title: 'Terminal', + titleKey: 'tab.title', + i18nNamespace: id, + priority: 100, + build: (_) => const TerminalPane(), + ), + ]; } diff --git a/lib/builtin/terminal/src/terminal_pane.dart b/lib/builtin/terminal/src/terminal_pane.dart index d3bcf29b..397dba5e 100644 --- a/lib/builtin/terminal/src/terminal_pane.dart +++ b/lib/builtin/terminal/src/terminal_pane.dart @@ -69,13 +69,16 @@ class _TerminalPaneState extends State { final shell = Platform.environment['SHELL'] ?? '/bin/bash'; final cwd = Directory.current.path; - final response = await ipc.request('pane.spawn', args: { - 'argv': [shell, '-l'], - 'kind': PaneKind.terminal.wire, - 'cwd': cwd, - 'cols': _terminal.viewWidth, - 'rows': _terminal.viewHeight, - }); + final response = await ipc.request( + 'pane.spawn', + args: { + 'argv': [shell, '-l'], + 'kind': PaneKind.terminal.wire, + 'cwd': cwd, + 'cols': _terminal.viewWidth, + 'rows': _terminal.viewHeight, + }, + ); if (!mounted) return; if (!response.ok) { setState(() => _error = response.error?.message ?? 'spawn failed'); @@ -120,11 +123,7 @@ class _TerminalPaneState extends State { void _onTerminalResize(int cols, int rows, int pixelWidth, int pixelHeight) { final id = _paneId; if (id == null) return; - _kernelIpc()?.request('pane.resize', args: { - 'id': id, - 'cols': cols, - 'rows': rows, - }); + _kernelIpc()?.request('pane.resize', args: {'id': id, 'cols': cols, 'rows': rows}); } DaemonClient? _kernelIpc() => _kernel()?.ipc; @@ -144,12 +143,7 @@ class _TerminalPaneState extends State { return ClidePaneChrome( title: 'terminal', subtitle: subtitle, - child: _error != null - ? _ErrorBody(message: _error!) - : ClidePtyView( - terminal: _terminal, - label: 'terminal — $subtitle', - ), + child: _error != null ? _ErrorBody(message: _error!) : ClidePtyView(terminal: _terminal, label: 'terminal — $subtitle'), ); } } @@ -165,11 +159,7 @@ class _ErrorBody extends StatelessWidget { alignment: Alignment.topLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ClideText('Terminal unavailable'), - const SizedBox(height: 4), - ClideText(message, muted: true), - ], + children: [const ClideText('Terminal unavailable'), const SizedBox(height: 4), ClideText(message, muted: true)], ), ), ); diff --git a/lib/builtin/theme_picker/src/extension.dart b/lib/builtin/theme_picker/src/extension.dart index 8a52b6a3..6bc0eb01 100644 --- a/lib/builtin/theme_picker/src/extension.dart +++ b/lib/builtin/theme_picker/src/extension.dart @@ -20,46 +20,25 @@ class ThemePickerExtension extends ClideExtension { @override List get contributions => [ - // Opens the settings modal (T-238). Command id kept as `theme.pick` - // (the welcome theme-link and other callers reference it); ⌘K opens - // Settings, whose only section today is the theme picker. - CommandContribution( - id: 'theme.pick', - command: 'theme.pick', - title: 'Settings…', - defaultBinding: 'ctrl+k', - run: _pick, - ), - // Always-visible switcher in the far-right status bar (T-234). - // priority >= 100 places it in the right group; registered after - // ipc-status so it sits to its right. - StatusItemContribution( - id: 'theme-picker.switcher', - priority: 110, - build: (_) => const ThemeSwitcherStatusItem(), - ), - ]; + // Opens the settings modal (T-238). Command id kept as `theme.pick` + // (the welcome theme-link and other callers reference it); ⌘K opens + // Settings, whose only section today is the theme picker. + CommandContribution(id: 'theme.pick', command: 'theme.pick', title: 'Settings…', defaultBinding: 'ctrl+k', run: _pick), + // Always-visible switcher in the far-right status bar (T-234). + // priority >= 100 places it in the right group; registered after + // ipc-status so it sits to its right. + StatusItemContribution(id: 'theme-picker.switcher', priority: 110, build: (_) => const ThemeSwitcherStatusItem()), + ]; Future _pick(List args) async { final ctx = _ctx; if (ctx == null) { return IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'theme-picker not activated', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'theme-picker not activated'), ); } - final selected = await ctx.dialog.show( - (context, dismiss) => SettingsView( - controller: ctx.theme, - onDismiss: dismiss, - ), - ); - return IpcResponse.ok(id: '', data: { - 'selected': selected ?? ctx.theme.currentName, - }); + final selected = await ctx.dialog.show((context, dismiss) => SettingsView(controller: ctx.theme, onDismiss: dismiss)); + return IpcResponse.ok(id: '', data: {'selected': selected ?? ctx.theme.currentName}); } } diff --git a/lib/builtin/theme_picker/src/settings_view.dart b/lib/builtin/theme_picker/src/settings_view.dart index b3f70d67..cbcec9c1 100644 --- a/lib/builtin/theme_picker/src/settings_view.dart +++ b/lib/builtin/theme_picker/src/settings_view.dart @@ -10,11 +10,7 @@ import 'package:flutter/widgets.dart'; /// (D-69); the shared `theme_families` helpers keep both surfaces in sync. /// Selecting a theme applies it live and dismisses; Cancel just closes. class SettingsView extends StatefulWidget { - const SettingsView({ - super.key, - required this.controller, - required this.onDismiss, - }); + const SettingsView({super.key, required this.controller, required this.onDismiss}); final ThemeController controller; final void Function([String? selected]) onDismiss; @@ -233,9 +229,7 @@ class _ThemeRow extends StatelessWidget { ) else const SizedBox(width: 20), - Expanded( - child: ClideText(displayName, color: fg), - ), + Expanded(child: ClideText(displayName, color: fg)), ClideText(name, color: tokens.globalTextMuted, fontSize: clideFontCaption), ], ), diff --git a/lib/builtin/theme_picker/src/theme_status_item.dart b/lib/builtin/theme_picker/src/theme_status_item.dart index a3302148..99e22d88 100644 --- a/lib/builtin/theme_picker/src/theme_status_item.dart +++ b/lib/builtin/theme_picker/src/theme_status_item.dart @@ -129,19 +129,9 @@ class _ThemePopoverState extends State<_ThemePopover> { maxWidth: 280, maxHeight: 360, entries: [ - ClideMenuItem( - label: 'High contrast', - active: _hc, - keepOpenOnSelect: true, - onSelect: _toggleHc, - ), + ClideMenuItem(label: 'High contrast', active: _hc, keepOpenOnSelect: true, onSelect: _toggleHc), const ClideMenuSeparator(), - for (final t in _themes) - ClideMenuItem( - label: t.displayName, - active: t.name == currentBase, - onSelect: () => _pick(t), - ), + for (final t in _themes) ClideMenuItem(label: t.displayName, active: t.name == currentBase, onSelect: () => _pick(t)), ], ), ); diff --git a/lib/builtin/tickets/src/extension.dart b/lib/builtin/tickets/src/extension.dart index eba7106c..7f5a2793 100644 --- a/lib/builtin/tickets/src/extension.dart +++ b/lib/builtin/tickets/src/extension.dart @@ -39,21 +39,21 @@ class TicketsExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'tickets.panel', - slot: Slots.sidebar, - title: 'Tickets', - titleKey: 'tab.title', - i18nNamespace: id, - icon: PhosphorIcons.byName('ticket'), - build: (_) => const TicketsView(), - ), - TabContribution( - id: 'tickets.detail', - slot: Slots.contextPanel, - title: 'Ticket', - icon: PhosphorIcons.byName('ticket'), - build: (_) => const TicketDetailView(), - ), - ]; + TabContribution( + id: 'tickets.panel', + slot: Slots.sidebar, + title: 'Tickets', + titleKey: 'tab.title', + i18nNamespace: id, + icon: PhosphorIcons.byName('ticket'), + build: (_) => const TicketsView(), + ), + TabContribution( + id: 'tickets.detail', + slot: Slots.contextPanel, + title: 'Ticket', + icon: PhosphorIcons.byName('ticket'), + build: (_) => const TicketDetailView(), + ), + ]; } diff --git a/lib/builtin/tickets/src/ticket_colors.dart b/lib/builtin/tickets/src/ticket_colors.dart index dd687075..e87d10e7 100644 --- a/lib/builtin/tickets/src/ticket_colors.dart +++ b/lib/builtin/tickets/src/ticket_colors.dart @@ -1,13 +1,7 @@ import 'dart:ui' show Color; class TicketTypeColors { - const TicketTypeColors({ - required this.initiative, - required this.epic, - required this.story, - required this.task, - required this.bug, - }); + const TicketTypeColors({required this.initiative, required this.epic, required this.story, required this.task, required this.bug}); final Color initiative; final Color epic; @@ -16,13 +10,13 @@ class TicketTypeColors { final Color bug; Color forType(String? type) => switch (type) { - 'initiative' => initiative, - 'epic' => epic, - 'story' => story, - 'task' => task, - 'bug' => bug, - _ => task, - }; + 'initiative' => initiative, + 'epic' => epic, + 'story' => story, + 'task' => task, + 'bug' => bug, + _ => task, + }; static const dark = TicketTypeColors( initiative: Color(0xFFC792EA), diff --git a/lib/builtin/tickets/src/ticket_detail_view.dart b/lib/builtin/tickets/src/ticket_detail_view.dart index 1d829972..ddb1ab6d 100644 --- a/lib/builtin/tickets/src/ticket_detail_view.dart +++ b/lib/builtin/tickets/src/ticket_detail_view.dart @@ -73,10 +73,7 @@ class _TicketDetailViewState extends State { return ClidePaneChrome( title: d.id, subtitle: d.title, - leading: ReaderPinButton( - pinned: _nav?.hasPinned ?? false, - onTap: _onPin, - ), + leading: ReaderPinButton(pinned: _nav?.hasPinned ?? false, onTap: _onPin), trailing: [ ReaderActionBar( canGoBack: _nav?.canGoBack ?? false, @@ -104,13 +101,7 @@ class _TicketDetailViewState extends State { const SizedBox(height: 16), _SectionLabel(label: 'PARENT TREE', tokens: tokens), const SizedBox(height: 6), - for (var i = 0; i < d.parents.length; i++) - _CompactCard( - data: d.parents[i], - tokens: tokens, - typeColors: typeColors, - indent: i, - ), + for (var i = 0; i < d.parents.length; i++) _CompactCard(data: d.parents[i], tokens: tokens, typeColors: typeColors, indent: i), ], if (d.decisions.isNotEmpty) ...[ const SizedBox(height: 16), @@ -150,7 +141,11 @@ class _TicketHeader extends StatelessWidget { children: [ ClideTooltip( message: detail.type ?? 'task', - child: Container(width: 10, height: 10, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle), + ), ), const SizedBox(width: 8), ClideText(detail.id, fontSize: clideFontSmall, color: typeColor, fontFamily: clideMonoFamily), @@ -188,10 +183,13 @@ class _StatusControls extends StatelessWidget { onTap: detail.status == s ? null : () async { - final resp = await controller.ipc.request('pql.tickets.status', args: { - 'ids': [detail.id], - 'status': s - }); + final resp = await controller.ipc.request( + 'pql.tickets.status', + args: { + 'ids': [detail.id], + 'status': s, + }, + ); if (resp.ok) { controller.messages.publish('builtin.tickets', 'changed', {'id': detail.id}); await controller.load(detail.id); @@ -220,13 +218,13 @@ class _StatusControls extends StatelessWidget { } static String _shortLabel(String s) => switch (s) { - 'backlog' => 'BACKLOG', - 'ready' => 'READY', - 'in_progress' => 'WIP', - 'review' => 'REVIEW', - 'done' => 'DONE', - _ => s.toUpperCase(), - }; + 'backlog' => 'BACKLOG', + 'ready' => 'READY', + 'in_progress' => 'WIP', + 'review' => 'REVIEW', + 'done' => 'DONE', + _ => s.toUpperCase(), + }; } class _SectionLabel extends StatelessWidget { @@ -266,7 +264,11 @@ class _CompactCard extends StatelessWidget { ), child: Row( children: [ - Container(width: 6, height: 6, decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle)), + Container( + width: 6, + height: 6, + decoration: BoxDecoration(color: typeColor, shape: BoxShape.circle), + ), const SizedBox(width: 6), ClideText(id, fontSize: clideFontSmall, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), const SizedBox(width: 8), @@ -312,7 +314,11 @@ class _DecisionRefCard extends StatelessWidget { children: [ Row( children: [ - Container(width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), + Container( + width: 6, + height: 6, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ), const SizedBox(width: 6), ClideText(id, fontSize: clideFontSmall, color: color, fontFamily: clideMonoFamily), const Spacer(), diff --git a/lib/builtin/tickets/src/tickets_view.dart b/lib/builtin/tickets/src/tickets_view.dart index 9795f38e..4e438b10 100644 --- a/lib/builtin/tickets/src/tickets_view.dart +++ b/lib/builtin/tickets/src/tickets_view.dart @@ -26,13 +26,7 @@ class _TicketsViewState extends State { /// ticket type; all on by default. An empty set never persists — toggling off /// the last one snaps all back on, so the list is never mysteriously blank. static const _allTypes = {'initiative', 'epic', 'story', 'task', 'bug'}; - static const _typeOrder = [ - ('initiative', 'Initiative'), - ('epic', 'Epic'), - ('story', 'Story'), - ('task', 'Task'), - ('bug', 'Bug'), - ]; + static const _typeOrder = [('initiative', 'Initiative'), ('epic', 'Epic'), ('story', 'Story'), ('task', 'Task'), ('bug', 'Bug')]; final Set _enabledTypes = {..._allTypes}; StreamSubscription? _focusSub; StreamSubscription? _schedulerSub; @@ -107,9 +101,11 @@ class _TicketsViewState extends State { _focusSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'focus').listen(_onFocus); _changedSub = kernel.messages.subscribe(publisher: 'builtin.tickets', channel: 'changed').listen((msg) { final id = msg.data['id'] as String?; - unawaited(_refresh().then((_) { - if (id != null && mounted) _scrollToFocused(id); - })); + unawaited( + _refresh().then((_) { + if (id != null && mounted) _scrollToFocused(id); + }), + ); }); _schedulerSub = kernel.events.on().where((e) => e.tier == SchedulerTier.oneMinute).listen((_) => _refresh()); // The first load can fire before the project's workspace is wired into @@ -208,7 +204,9 @@ class _TicketsViewState extends State { children: [ Row( children: [ - Expanded(child: ClideFilterBox(address: 'tickets.panel', hint: 'Filter tickets…', onChanged: (v) => setState(() => _filter = v))), + Expanded( + child: ClideFilterBox(address: 'tickets.panel', hint: 'Filter tickets…', onChanged: (v) => setState(() => _filter = v)), + ), Padding( padding: const EdgeInsets.only(right: 8), child: ClideTappable( @@ -277,14 +275,7 @@ class _TicketsViewState extends State { /// type; double-click isolates it (chart-legend solo). One [GestureDetector] /// owns both so Flutter disambiguates single vs double. class _TypeChip extends StatelessWidget { - const _TypeChip({ - required this.label, - required this.color, - required this.active, - required this.onToggle, - required this.onSolo, - required this.tokens, - }); + const _TypeChip({required this.label, required this.color, required this.active, required this.onToggle, required this.onSolo, required this.tokens}); final String label; final Color color; @@ -317,7 +308,11 @@ class _TypeChip extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container(width: 8, height: 8, decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle)), + Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle), + ), const SizedBox(width: 6), ClideText(label, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: active ? tokens.globalForeground : tokens.globalTextMuted), ], @@ -340,13 +335,13 @@ class _TicketEntry { final String? parentId; factory _TicketEntry.fromJson(Map json) => _TicketEntry( - id: json['id'] as String? ?? '', - title: json['title'] as String? ?? '', - type: json['type'] as String?, - status: json['status'] as String?, - priority: json['priority'] as String?, - parentId: json['parent_id'] as String?, - ); + id: json['id'] as String? ?? '', + title: json['title'] as String? ?? '', + type: json['type'] as String?, + status: json['status'] as String?, + priority: json['priority'] as String?, + parentId: json['parent_id'] as String?, + ); } class _TicketCard extends StatelessWidget { @@ -412,15 +407,17 @@ class _TicketCard extends StatelessWidget { ), const SizedBox(height: 4), ClideText(entry.title, fontSize: clideFontCaption), - if (statusLabel != null) ...[ - const SizedBox(height: 6), - _StatusBadge(label: statusLabel, tokens: tokens, status: entry.status), - ], + if (statusLabel != null) ...[const SizedBox(height: 6), _StatusBadge(label: statusLabel, tokens: tokens, status: entry.status)], ], ), // Hover affordance (T-327): hand the full ticket to the focused // Claude pane via the message bus. - if (hovered) Positioned(top: 0, right: 0, child: _PickUpAction(id: entry.id, tokens: tokens)), + if (hovered) + Positioned( + top: 0, + right: 0, + child: _PickUpAction(id: entry.id, tokens: tokens), + ), ], ), ), @@ -429,11 +426,11 @@ class _TicketCard extends StatelessWidget { } static String? _statusLabel(String? status) => switch (status) { - 'in_progress' => 'WIP', - 'review' => 'REVIEW', - 'cancelled' => 'CANCELLED', - _ => null, - }; + 'in_progress' => 'WIP', + 'review' => 'REVIEW', + 'cancelled' => 'CANCELLED', + _ => null, + }; } /// The hover "pick up" run-icon (T-327): fetches the full ticket and publishes @@ -482,10 +479,7 @@ class _StatusBadge extends StatelessWidget { }; return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: color.withAlpha(0x30), - borderRadius: BorderRadius.circular(3), - ), + decoration: BoxDecoration(color: color.withAlpha(0x30), borderRadius: BorderRadius.circular(3)), child: ClideText(label, fontSize: clideFontBadge, color: color, fontFamily: clideMonoFamily), ); } diff --git a/lib/builtin/view/src/extension.dart b/lib/builtin/view/src/extension.dart index 45c5fbac..83174a73 100644 --- a/lib/builtin/view/src/extension.dart +++ b/lib/builtin/view/src/extension.dart @@ -23,35 +23,35 @@ class ViewExtension extends ClideExtension { @override List get contributions => [ - // Keybindings live in `assets/keymaps/default.yaml` against the - // text.scale* intents — registering a `defaultBinding` here too - // would shadow them. Palette discovery is the only goal. - CommandContribution( - id: 'view.zoomIn', - command: 'view.zoomIn', - title: 'View: Zoom In', - run: (_) async { - textZoom.increase(); - return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); - }, - ), - CommandContribution( - id: 'view.zoomOut', - command: 'view.zoomOut', - title: 'View: Zoom Out', - run: (_) async { - textZoom.decrease(); - return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); - }, - ), - CommandContribution( - id: 'view.zoomReset', - command: 'view.zoomReset', - title: 'View: Reset Zoom', - run: (_) async { - textZoom.reset(); - return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); - }, - ), - ]; + // Keybindings live in `assets/keymaps/default.yaml` against the + // text.scale* intents — registering a `defaultBinding` here too + // would shadow them. Palette discovery is the only goal. + CommandContribution( + id: 'view.zoomIn', + command: 'view.zoomIn', + title: 'View: Zoom In', + run: (_) async { + textZoom.increase(); + return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); + }, + ), + CommandContribution( + id: 'view.zoomOut', + command: 'view.zoomOut', + title: 'View: Zoom Out', + run: (_) async { + textZoom.decrease(); + return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); + }, + ), + CommandContribution( + id: 'view.zoomReset', + command: 'view.zoomReset', + title: 'View: Reset Zoom', + run: (_) async { + textZoom.reset(); + return IpcResponse.ok(id: '', data: {'scale': textZoom.scale}); + }, + ), + ]; } diff --git a/lib/builtin/vim/src/extension.dart b/lib/builtin/vim/src/extension.dart index 7a22383b..06a441fd 100644 --- a/lib/builtin/vim/src/extension.dart +++ b/lib/builtin/vim/src/extension.dart @@ -61,19 +61,19 @@ class VimExtension extends ClideExtension { @override List get contributions => [ - _modeCommand('vim.mode.normal', 'Vim: Normal mode', () => _mode?.enterNormal()), - _modeCommand('vim.mode.insert', 'Vim: Insert mode', () => _mode?.enterInsert()), - _modeCommand('vim.mode.visual', 'Vim: Visual mode', () => _mode?.enterVisual()), - StatusItemContribution( - id: 'vim.mode', - priority: -50, // left group, near the other editor status items - listenable: _mode, - build: (_) { - final m = _mode; - return m == null ? const SizedBox.shrink() : VimModeIndicator(service: m); - }, - ), - ]; + _modeCommand('vim.mode.normal', 'Vim: Normal mode', () => _mode?.enterNormal()), + _modeCommand('vim.mode.insert', 'Vim: Insert mode', () => _mode?.enterInsert()), + _modeCommand('vim.mode.visual', 'Vim: Visual mode', () => _mode?.enterVisual()), + StatusItemContribution( + id: 'vim.mode', + priority: -50, // left group, near the other editor status items + listenable: _mode, + build: (_) { + final m = _mode; + return m == null ? const SizedBox.shrink() : VimModeIndicator(service: m); + }, + ), + ]; CommandContribution _modeCommand(String id, String title, void Function() apply) { return CommandContribution( diff --git a/lib/builtin/vim/src/vim_mode_indicator.dart b/lib/builtin/vim/src/vim_mode_indicator.dart index 8f443dbc..f8abfdc0 100644 --- a/lib/builtin/vim/src/vim_mode_indicator.dart +++ b/lib/builtin/vim/src/vim_mode_indicator.dart @@ -20,12 +20,7 @@ class VimModeIndicator extends StatelessWidget { final tokens = ClideTheme.of(context).surface; return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: ClideText( - '-- ${service.mode.label} --', - fontFamily: clideMonoFamily, - fontSize: clideFontCaption, - color: tokens.statusBarForeground, - ), + child: ClideText('-- ${service.mode.label} --', fontFamily: clideMonoFamily, fontSize: clideFontCaption, color: tokens.statusBarForeground), ); }, ); diff --git a/lib/builtin/welcome/src/extension.dart b/lib/builtin/welcome/src/extension.dart index 70ac287d..146b4e48 100644 --- a/lib/builtin/welcome/src/extension.dart +++ b/lib/builtin/welcome/src/extension.dart @@ -13,23 +13,20 @@ class WelcomeExtension extends ClideExtension { @override List get contributions => [ - TabContribution( - id: 'welcome.view', - slot: Slots.workspace, - title: 'Welcome', - titleKey: 'tab.title', - i18nNamespace: id, - priority: -100, // anchor at the far left of the workspace tabs - build: (_) => const WelcomeView(), - ), - CommandContribution( - id: 'workspace.open-project', - command: 'workspace.open-project', - title: 'Workspace: Open project…', - run: (_) async => IpcResponse.ok( - id: '', - data: const {'note': 'project picker lands in a later tier'}, - ), - ), - ]; + TabContribution( + id: 'welcome.view', + slot: Slots.workspace, + title: 'Welcome', + titleKey: 'tab.title', + i18nNamespace: id, + priority: -100, // anchor at the far left of the workspace tabs + build: (_) => const WelcomeView(), + ), + CommandContribution( + id: 'workspace.open-project', + command: 'workspace.open-project', + title: 'Workspace: Open project…', + run: (_) async => IpcResponse.ok(id: '', data: const {'note': 'project picker lands in a later tier'}), + ), + ]; } diff --git a/lib/builtin/welcome/src/welcome_view.dart b/lib/builtin/welcome/src/welcome_view.dart index 6152af3f..3941d993 100644 --- a/lib/builtin/welcome/src/welcome_view.dart +++ b/lib/builtin/welcome/src/welcome_view.dart @@ -40,15 +40,16 @@ class WelcomeView extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(child: _StartColumn(tokens: tokens, kernel: kernel)), + Expanded( + child: _StartColumn(tokens: tokens, kernel: kernel), + ), const SizedBox(width: 56), - Expanded(child: _RecentColumn(tokens: tokens, kernel: kernel)), + Expanded( + child: _RecentColumn(tokens: tokens, kernel: kernel), + ), ], ), - if (showTips) ...[ - const SizedBox(height: 48), - _TipsCard(tokens: tokens), - ], + if (showTips) ...[const SizedBox(height: 48), _TipsCard(tokens: tokens)], ], ), ), @@ -119,7 +120,9 @@ class _TipsCard extends StatelessWidget { Expanded( child: Row( children: [ - Expanded(child: ClideText(tips[i].$1, fontSize: clideFontMeta, color: tokens.globalTextMuted)), + Expanded( + child: ClideText(tips[i].$1, fontSize: clideFontMeta, color: tokens.globalTextMuted), + ), ClideText(tips[i].$2, fontSize: clideFontSmall, color: tokens.globalForeground, fontFamily: clideMonoFamily), ], ), @@ -166,27 +169,9 @@ class _StartColumn extends StatelessWidget { children: [ ClideText('START', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), const SizedBox(height: 20), - _ActionRow( - icon: PhosphorIcons.byName('folder'), - label: 'Open folder…', - shortcut: '⌘O', - tokens: tokens, - onTap: () => _openFolder(context), - ), - _ActionRow( - icon: PhosphorIcons.byName('git-branch'), - label: 'Clone from git…', - shortcut: '⌘G', - tokens: tokens, - onTap: () {}, - ), - _ActionRow( - icon: PhosphorIcons.byName('chat-circle'), - label: 'Start a Claude session', - shortcut: '⌘C', - tokens: tokens, - onTap: () {}, - ), + _ActionRow(icon: PhosphorIcons.byName('folder'), label: 'Open folder…', shortcut: '⌘O', tokens: tokens, onTap: () => _openFolder(context)), + _ActionRow(icon: PhosphorIcons.byName('git-branch'), label: 'Clone from git…', shortcut: '⌘G', tokens: tokens, onTap: () {}), + _ActionRow(icon: PhosphorIcons.byName('chat-circle'), label: 'Start a Claude session', shortcut: '⌘C', tokens: tokens, onTap: () {}), ], ); } @@ -199,10 +184,7 @@ class _StartColumn extends StatelessWidget { if (ok) { kernel.panels.activateTab(Slots.workspace, 'claude.primary'); } else { - kernel.dialog.show((ctx, dismiss) => _NotARepoDialog( - path: picked, - onDismiss: () => dismiss(), - )); + kernel.dialog.show((ctx, dismiss) => _NotARepoDialog(path: picked, onDismiss: () => dismiss())); } } return; @@ -239,15 +221,14 @@ class _ActionRow extends StatelessWidget { onTap: onTap, builder: (context, hovered, _) => Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: hovered ? tokens.listItemHoverBackground : null, - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: hovered ? tokens.listItemHoverBackground : null, borderRadius: BorderRadius.circular(4)), child: Row( children: [ ClideIcon(icon, size: 18, color: tokens.globalTextMuted), const SizedBox(width: 14), - Expanded(child: ClideText(label, fontSize: clideFontBody, color: tokens.globalForeground)), + Expanded( + child: ClideText(label, fontSize: clideFontBody, color: tokens.globalForeground), + ), if (shortcut != null) ClideText(shortcut!, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily), ], ), @@ -296,12 +277,7 @@ class _RecentColumn extends StatelessWidget { } class _RecentRow extends StatelessWidget { - const _RecentRow({ - required this.project, - required this.tokens, - required this.onTap, - required this.onToggleSticky, - }); + const _RecentRow({required this.project, required this.tokens, required this.onTap, required this.onToggleSticky}); final RecentProject project; final SurfaceTokens tokens; final VoidCallback onTap; @@ -313,10 +289,7 @@ class _RecentRow extends StatelessWidget { onTap: onTap, builder: (context, hovered, _) => Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: hovered ? tokens.listItemHoverBackground : null, - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: hovered ? tokens.listItemHoverBackground : null, borderRadius: BorderRadius.circular(4)), child: Row( children: [ Expanded( @@ -328,27 +301,36 @@ class _RecentRow extends StatelessWidget { Row( children: [ Flexible( - child: ClideText(project.relativePath, - muted: true, fontSize: clideFontMeta, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)), + child: ClideText( + project.relativePath, + muted: true, + fontSize: clideFontMeta, + fontFamily: clideMonoFamily, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), if (project.branch != null) ...[ ClideText(' · ', muted: true, fontSize: clideFontMeta), ClideIcon(PhosphorIcons.byName('git-branch'), size: 11, color: tokens.globalTextMuted), const SizedBox(width: 3), Flexible( - child: ClideText(project.branch!, - muted: true, fontSize: clideFontMeta, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)), + child: ClideText( + project.branch!, + muted: true, + fontSize: clideFontMeta, + fontFamily: clideMonoFamily, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), ], ], ), ], ), ), - _StickyToggle( - key: ValueKey('welcome.sticky.${project.path}'), - sticky: project.startupSticky, - tokens: tokens, - onTap: onToggleSticky, - ), + _StickyToggle(key: ValueKey('welcome.sticky.${project.path}'), sticky: project.startupSticky, tokens: tokens, onTap: onToggleSticky), const SizedBox(width: 12), ClideText(project.timeAgo, muted: true, fontSize: clideFontMeta), ], @@ -420,8 +402,12 @@ class _StatusLine extends StatelessWidget { else if (tc.allOk) ClideText('application ok', fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusSuccess) else - ClideText(tc.missing.map((t) => '$t not found').join(' · '), - fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusWarning), + ClideText( + tc.missing.map((t) => '$t not found').join(' · '), + fontSize: clideFontSmall, + fontFamily: clideMonoFamily, + color: tokens.statusWarning, + ), ClideText(' · ', muted: true, fontSize: clideFontSmall), _ThemeLink(tokens: tokens, kernel: kernel, themeName: themeName), ], @@ -526,16 +512,17 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> { controller: _controller, focusNode: _focus, style: TextStyle( - color: tokens.globalForeground, fontSize: clideFontCaption, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback), + color: tokens.globalForeground, + fontSize: clideFontCaption, + fontFamily: clideMonoFamily, + fontFamilyFallback: clideMonoFamilyFallback, + ), cursorColor: tokens.globalForeground, backgroundCursorColor: tokens.globalTextMuted, onSubmitted: (_) => unawaited(_submit()), ), ), - if (_error != null) ...[ - const SizedBox(height: 8), - ClideText(_error!, color: tokens.statusError, fontSize: clideFontSmall), - ], + if (_error != null) ...[const SizedBox(height: 8), ClideText(_error!, color: tokens.statusError, fontSize: clideFontSmall)], const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -575,17 +562,11 @@ class _NotARepoDialog extends StatelessWidget { const SizedBox(height: 8), ClideText(path, muted: true, fontSize: clideFontMeta), const SizedBox(height: 8), - const ClideText( - 'A clide project root requires a git repository.', - muted: true, - fontSize: clideFontMeta, - ), + const ClideText('A clide project root requires a git repository.', muted: true, fontSize: clideFontMeta), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, - children: [ - ClideButton(label: 'OK', onPressed: () => onDismiss()), - ], + children: [ClideButton(label: 'OK', onPressed: () => onDismiss())], ), ], ), diff --git a/lib/extension/src/contribution.dart b/lib/extension/src/contribution.dart index 98afd7c9..ad7dd7c2 100644 --- a/lib/extension/src/contribution.dart +++ b/lib/extension/src/contribution.dart @@ -56,13 +56,7 @@ class TabContribution extends ContributionPoint { /// A status-bar item. Order is determined by [priority] within each /// alignment group; negative priorities float left, positive right. class StatusItemContribution extends ContributionPoint { - const StatusItemContribution({ - required super.id, - required this.build, - this.priority = 0, - this.listenable, - this.flex = 0, - }); + const StatusItemContribution({required super.id, required this.build, this.priority = 0, this.listenable, this.flex = 0}); @override SlotId get slot => Slots.statusbar; @@ -80,14 +74,7 @@ class StatusItemContribution extends ContributionPoint { /// A button in the main toolbar. class ToolbarButtonContribution extends ContributionPoint { - const ToolbarButtonContribution({ - required super.id, - required this.label, - required this.onPressed, - this.icon, - this.tooltip, - this.priority = 0, - }); + const ToolbarButtonContribution({required super.id, required this.label, required this.onPressed, this.icon, this.tooltip, this.priority = 0}); @override SlotId get slot => Slots.toolbar; @@ -101,14 +88,7 @@ class ToolbarButtonContribution extends ContributionPoint { /// A command extensions register with [CommandRegistry]. Surfaced by the /// command palette, the keybinding resolver, and `clide` CLI subcommands. class CommandContribution extends ContributionPoint { - const CommandContribution({ - required super.id, - required this.command, - required this.run, - this.title, - this.defaultBinding, - this.bindingWhen, - }); + const CommandContribution({required super.id, required this.command, required this.run, this.title, this.defaultBinding, this.bindingWhen}); final String command; // e.g. "git.commit" final String? title; // "Git: Commit staged" @@ -124,12 +104,7 @@ class CommandContribution extends ContributionPoint { /// Registers an item in the OS tray / menu-bar. class TrayItemContribution extends ContributionPoint { - const TrayItemContribution({ - required super.id, - required this.label, - required this.onSelected, - this.priority = 0, - }); + const TrayItemContribution({required super.id, required this.label, required this.onSelected, this.priority = 0}); @override SlotId get slot => Slots.tray; @@ -141,11 +116,7 @@ class TrayItemContribution extends ContributionPoint { /// A named layout arrangement. One "classic" preset ships with /// `builtin.default-layout`; other presets can be contributed. class LayoutPresetContribution extends ContributionPoint { - const LayoutPresetContribution({ - required super.id, - required this.displayName, - required this.slots, - }); + const LayoutPresetContribution({required super.id, required this.displayName, required this.slots}); final String displayName; final List slots; @@ -154,14 +125,7 @@ class LayoutPresetContribution extends ContributionPoint { /// One slot in a [LayoutPresetContribution]. Describes where the slot /// appears and its initial size/visibility. class LayoutSlot { - const LayoutSlot({ - required this.slot, - required this.position, - this.defaultSize, - this.minSize, - this.maxSize, - this.visible = true, - }); + const LayoutSlot({required this.slot, required this.position, this.defaultSize, this.minSize, this.maxSize, this.visible = true}); final SlotId slot; final SlotPosition position; diff --git a/lib/extension/src/extension.dart b/lib/extension/src/extension.dart index 1e6b1d51..9639a85c 100644 --- a/lib/extension/src/extension.dart +++ b/lib/extension/src/extension.dart @@ -98,15 +98,6 @@ extension ClideExtensionContextI18n on ClideExtensionContext { String t(String key, {String? placeholder}) => i18n.string(key, namespace: id, placeholder: placeholder); /// [t] with interpolation replacers. - String tr( - String key, { - String? placeholder, - List replacers = const [], - }) => - i18n.interpolated( - key, - namespace: id, - placeholder: placeholder, - replacers: replacers, - ); + String tr(String key, {String? placeholder, List replacers = const []}) => + i18n.interpolated(key, namespace: id, placeholder: placeholder, replacers: replacers); } diff --git a/lib/extension/src/manifest.dart b/lib/extension/src/manifest.dart index c9e24de6..20e7fb5c 100644 --- a/lib/extension/src/manifest.dart +++ b/lib/extension/src/manifest.dart @@ -45,14 +45,7 @@ class ExtensionManifest { if (d is String) deps.add(d); } } - return ExtensionManifest( - id: id, - title: title, - version: version, - dependsOn: deps, - entry: entry, - schemaVersion: schemaVersion, - ); + return ExtensionManifest(id: id, title: title, version: version, dependsOn: deps, entry: entry, schemaVersion: schemaVersion); } static Future fromFile(File f) async => ExtensionManifest.fromYamlString(await f.readAsString()); diff --git a/lib/kernel/src/cli_install.dart b/lib/kernel/src/cli_install.dart index 708fedef..991cf536 100644 --- a/lib/kernel/src/cli_install.dart +++ b/lib/kernel/src/cli_install.dart @@ -54,13 +54,7 @@ class CliInstallStatus { /// Result of [CliInstaller.install]. class CliInstallResult { - const CliInstallResult({ - required this.ok, - required this.message, - this.installedPath, - this.onPath = true, - this.fromDevTree = false, - }); + const CliInstallResult({required this.ok, required this.message, this.installedPath, this.onPath = true, this.fromDevTree = false}); final bool ok; final String message; @@ -80,14 +74,10 @@ class CliInstallResult { /// environment, candidate client locations, the target dir) is injectable so /// the logic is unit-testable without a real install. class CliInstaller { - CliInstaller({ - required this.resolvedExecutable, - Map? env, - List? bundledClientCandidates, - String? installDir, - }) : env = env ?? Platform.environment, - bundledClientCandidates = bundledClientCandidates ?? _defaultBundledCandidates(resolvedExecutable, env ?? Platform.environment), - installDir = installDir ?? _defaultInstallDir(env ?? Platform.environment); + CliInstaller({required this.resolvedExecutable, Map? env, List? bundledClientCandidates, String? installDir}) + : env = env ?? Platform.environment, + bundledClientCandidates = bundledClientCandidates ?? _defaultBundledCandidates(resolvedExecutable, env ?? Platform.environment), + installDir = installDir ?? _defaultInstallDir(env ?? Platform.environment); /// Path to the running Flutter GUI executable /// (`Platform.resolvedExecutable`). @@ -132,7 +122,8 @@ class CliInstaller { if (src == null) { return const CliInstallResult( ok: false, - message: 'No bundled clide client found to install. Build with ' + message: + 'No bundled clide client found to install. Build with ' '`make build` so the C client ships inside the app bundle.', ); } @@ -203,11 +194,7 @@ class CliInstaller { return null; } - String _expandedPath() => expandedPath( - env['PATH'] ?? '', - macOS: Platform.isMacOS, - home: env['HOME'] ?? '', - ); + String _expandedPath() => expandedPath(env['PATH'] ?? '', macOS: Platform.isMacOS, home: env['HOME'] ?? ''); static String _defaultInstallDir(Map env) => '${env['HOME'] ?? ''}/.local/bin'; @@ -217,10 +204,7 @@ class CliInstaller { /// `Contents/MacOS/` on macOS). static List _defaultBundledCandidates(String resolvedExecutable, Map env) { final exeDir = File(resolvedExecutable).parent.path; - return [ - if ((env['CLIDE_CLI_BIN'] ?? '').isNotEmpty) env['CLIDE_CLI_BIN']!, - '$exeDir/clide-cli', - ]; + return [if ((env['CLIDE_CLI_BIN'] ?? '').isNotEmpty) env['CLIDE_CLI_BIN']!, '$exeDir/clide-cli']; } } @@ -239,12 +223,7 @@ bool isDevTreeClient(String path) => _devTreeClient.hasMatch(path); /// platform-parameterized function so both branches are testable off-platform. String expandedPath(String base, {required bool macOS, String home = ''}) { if (!macOS) return base; - final extras = [ - if (home.isNotEmpty) '$home/.local/bin', - '/opt/homebrew/bin', - '/opt/homebrew/sbin', - '/usr/local/bin', - ]; + final extras = [if (home.isNotEmpty) '$home/.local/bin', '/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin']; final existing = base.split(':').toSet(); final missing = extras.where((p) => !existing.contains(p)); if (missing.isEmpty) return base; diff --git a/lib/kernel/src/clipboard.dart b/lib/kernel/src/clipboard.dart index 13b1531f..d4c46c20 100644 --- a/lib/kernel/src/clipboard.dart +++ b/lib/kernel/src/clipboard.dart @@ -14,10 +14,7 @@ class ClideClipboard { final int historyLimit; final Map> _history = {}; - Future write( - T value, { - String Function(T)? toPlain, - }) async { + Future write(T value, {String Function(T)? toPlain}) async { final bucket = _history.putIfAbsent(T, () => []); bucket.insert(0, value); if (bucket.length > historyLimit) bucket.removeLast(); diff --git a/lib/kernel/src/commands/keybindings.dart b/lib/kernel/src/commands/keybindings.dart index 56dd0fab..e4a33d75 100644 --- a/lib/kernel/src/commands/keybindings.dart +++ b/lib/kernel/src/commands/keybindings.dart @@ -5,9 +5,7 @@ import 'package:flutter/services.dart'; /// (modifiers sorted, lowercased) so equality works for lookup keys. @immutable class Keybinding { - Keybinding({required Set modifiers, required String key}) - : modifiers = _canonModifiers(modifiers), - key = key.toLowerCase(); + Keybinding({required Set modifiers, required String key}) : modifiers = _canonModifiers(modifiers), key = key.toLowerCase(); final List modifiers; final String key; diff --git a/lib/kernel/src/commands/registry.dart b/lib/kernel/src/commands/registry.dart index f0425eb1..f62e9927 100644 --- a/lib/kernel/src/commands/registry.dart +++ b/lib/kernel/src/commands/registry.dart @@ -17,19 +17,12 @@ class CommandRegistry extends ChangeNotifier { Iterable get all => _byCommand.values; CommandContribution? get(String command) => _byCommand[command]; - Future execute( - String command, { - List args = const [], - }) async { + Future execute(String command, {List args = const []}) async { final c = _byCommand[command]; if (c == null) { return IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: 'no such command: $command', - ), + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'no such command: $command'), ); } return c.run(args); diff --git a/lib/kernel/src/dialog.dart b/lib/kernel/src/dialog.dart index a2701fc7..f54c379c 100644 --- a/lib/kernel/src/dialog.dart +++ b/lib/kernel/src/dialog.dart @@ -2,10 +2,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; -typedef DialogBuilder = Widget Function( - BuildContext context, - void Function([T? result]) dismiss, -); +typedef DialogBuilder = Widget Function(BuildContext context, void Function([T? result]) dismiss); /// Single-at-a-time modal router. /// @@ -70,12 +67,7 @@ class _Queued { /// Hosts the current dialog from [DialogRouter]. Place high in the tree /// (inside the WidgetsApp) so dialogs overlay every other surface. class DialogHost extends StatelessWidget { - const DialogHost({ - super.key, - required this.router, - required this.child, - this.backdropColor = const Color(0xC0000000), - }); + const DialogHost({super.key, required this.router, required this.child, this.backdropColor = const Color(0xC0000000)}); final DialogRouter router; final Widget child; @@ -98,10 +90,7 @@ class DialogHost extends StatelessWidget { child: ColoredBox( color: backdropColor, child: Center( - child: GestureDetector( - onTap: () {}, - child: b(ctx, router.dismiss), - ), + child: GestureDetector(onTap: () {}, child: b(ctx, router.dismiss)), ), ), ), diff --git a/lib/kernel/src/events/message_bus.dart b/lib/kernel/src/events/message_bus.dart index 7c7b2c13..fb148453 100644 --- a/lib/kernel/src/events/message_bus.dart +++ b/lib/kernel/src/events/message_bus.dart @@ -1,11 +1,7 @@ import 'dart:async'; class Message { - Message({ - required this.publisher, - required this.channel, - required this.data, - }) : timestamp = DateTime.now(); + Message({required this.publisher, required this.channel, required this.data}) : timestamp = DateTime.now(); final String publisher; final String channel; diff --git a/lib/kernel/src/events/types.dart b/lib/kernel/src/events/types.dart index 196be613..842eb9ff 100644 --- a/lib/kernel/src/events/types.dart +++ b/lib/kernel/src/events/types.dart @@ -13,13 +13,7 @@ class ClideEventEnvelope { final ClideEvent event; final DateTime timestamp; - Map toJson() => { - 'v': 1, - 'subsystem': event.subsystem, - 'kind': event.kind, - 'ts': timestamp.toIso8601String(), - 'data': event.payload(), - }; + Map toJson() => {'v': 1, 'subsystem': event.subsystem, 'kind': event.kind, 'ts': timestamp.toIso8601String(), 'data': event.payload()}; } class DaemonConnectionChanged extends ClideEvent { @@ -89,12 +83,7 @@ class ExtensionDeactivated extends ClideEvent { /// narrow by subsystem+kind, or register a converter that emits a typed /// `ClideEvent` subclass into the bus. class DaemonEvent extends ClideEvent { - const DaemonEvent({ - required this.subsystem, - required this.kind, - required this.data, - required this.ts, - }); + const DaemonEvent({required this.subsystem, required this.kind, required this.data, required this.ts}); @override final String subsystem; @@ -145,26 +134,22 @@ class TeamMemberJoined extends ClideEvent { String get kind => 'member-joined'; @override Map payload() => { - 'team': team, - 'agentId': agentId, - 'name': name, - 'agentType': agentType, - 'paneId': paneId, - if (model != null) 'model': model, - if (color != null) 'color': color, - if (cwd != null) 'cwd': cwd, - if (transcriptPath != null) 'transcriptPath': transcriptPath, - }; + 'team': team, + 'agentId': agentId, + 'name': name, + 'agentType': agentType, + 'paneId': paneId, + if (model != null) 'model': model, + if (color != null) 'color': color, + if (cwd != null) 'cwd': cwd, + if (transcriptPath != null) 'transcriptPath': transcriptPath, + }; } /// A Claude Code tmux teammate's pane went away (it exited or the team /// dissolved) — T-139. class TeamMemberLeft extends ClideEvent { - const TeamMemberLeft({ - required this.team, - required this.agentId, - required this.paneId, - }); + const TeamMemberLeft({required this.team, required this.agentId, required this.paneId}); final String team; final String agentId; diff --git a/lib/kernel/src/facade.dart b/lib/kernel/src/facade.dart index bd359b3c..34279562 100644 --- a/lib/kernel/src/facade.dart +++ b/lib/kernel/src/facade.dart @@ -147,13 +147,7 @@ class KernelServices { final settings = SettingsStore(appDir: appDir); await settings.load(); - final i18n = I18n( - loader: i18nLoader, - log: log, - defaultLocale: defaultLocale, - initialLocale: initialLocale, - availableLocales: availableLocales, - ); + final i18n = I18n(loader: i18nLoader, log: log, defaultLocale: defaultLocale, initialLocale: initialLocale, availableLocales: availableLocales); for (final ns in preloadNamespaces) { await i18n.ensureNamespaceLoaded(ns); } @@ -193,7 +187,8 @@ class KernelServices { onProjectOpen: onProjectOpen, onValidateProject: onValidateProject, ); - final ipc = isolateClient ?? + final ipc = + isolateClient ?? (daemonClientFactory != null ? daemonClientFactory(log, events, arrangement, panels) : DaemonClient( @@ -309,11 +304,7 @@ class KernelServices { } class ClideKernel extends InheritedWidget { - const ClideKernel({ - super.key, - required this.services, - required super.child, - }); + const ClideKernel({super.key, required this.services, required super.child}); final KernelServices services; diff --git a/lib/kernel/src/files.dart b/lib/kernel/src/files.dart index e1b26629..299f261e 100644 --- a/lib/kernel/src/files.dart +++ b/lib/kernel/src/files.dart @@ -16,10 +16,7 @@ class FilesDropped extends ClideEvent { @override String get kind => 'dropped'; @override - Map payload() => { - 'paths': paths, - 'slot': slot.value, - }; + Map payload() => {'paths': paths, 'slot': slot.value}; } /// Tier-0 stub for file pickers and drop targets. @@ -33,17 +30,11 @@ class FileServices { FileServices(this._events); final DaemonBus _events; - Future> pickOpen({ - List extensions = const [], - bool multiple = false, - }) async { + Future> pickOpen({List extensions = const [], bool multiple = false}) async { throw UnimplementedError('pickOpen — wired in a later tier'); } - Future pickSave({ - String? defaultName, - List extensions = const [], - }) async { + Future pickSave({String? defaultName, List extensions = const []}) async { throw UnimplementedError('pickSave — wired in a later tier'); } diff --git a/lib/kernel/src/focus.dart b/lib/kernel/src/focus.dart index 935f72f8..a2236c68 100644 --- a/lib/kernel/src/focus.dart +++ b/lib/kernel/src/focus.dart @@ -18,11 +18,7 @@ class FocusTracker extends ChangeNotifier { /// of the three-column layout (sidebar → workspace → context); the /// statusbar / toolbar aren't included because they don't host /// keyboard-active content. - static const List traversalOrder = [ - Slots.sidebar, - Slots.workspace, - Slots.contextPanel, - ]; + static const List traversalOrder = [Slots.sidebar, Slots.workspace, Slots.contextPanel]; SlotId? get activeSlot => _slot; String? get activeContributionId => _contributionId; diff --git a/lib/kernel/src/i18n/fallback_chain.dart b/lib/kernel/src/i18n/fallback_chain.dart index 714ed997..c3bcae5b 100644 --- a/lib/kernel/src/i18n/fallback_chain.dart +++ b/lib/kernel/src/i18n/fallback_chain.dart @@ -14,22 +14,14 @@ import 'package:flutter/foundation.dart'; /// canonicalized by omitting it (not empty string) so equality works. @immutable class FallbackChain { - const FallbackChain({ - required this.current, - required this.defaultLocale, - }); + const FallbackChain({required this.current, required this.defaultLocale}); final Locale current; final Locale defaultLocale; List resolve() { final out = []; - for (final l in [ - current, - Locale(current.languageCode), - defaultLocale, - Locale(defaultLocale.languageCode), - ]) { + for (final l in [current, Locale(current.languageCode), defaultLocale, Locale(defaultLocale.languageCode)]) { final canon = _canon(l); if (!out.any((e) => _canon(e) == canon)) { out.add(l); diff --git a/lib/kernel/src/i18n/i18n.dart b/lib/kernel/src/i18n/i18n.dart index b124383a..d189f3a2 100644 --- a/lib/kernel/src/i18n/i18n.dart +++ b/lib/kernel/src/i18n/i18n.dart @@ -26,9 +26,9 @@ class I18n extends ChangeNotifier { required Locale defaultLocale, Locale? initialLocale, List availableLocales = const [Locale('en', 'US')], - }) : _defaultLocale = defaultLocale, - _current = initialLocale ?? defaultLocale, - _available = List.unmodifiable(availableLocales); + }) : _defaultLocale = defaultLocale, + _current = initialLocale ?? defaultLocale, + _available = List.unmodifiable(availableLocales); final CatalogLoader loader; final Logger log; @@ -50,11 +50,7 @@ class I18n extends ChangeNotifier { /// Register a catalog that was loaded outside of [loader] — e.g. by the /// ExtensionManager when a third-party extension activates. - void registerCatalog( - String namespace, - Locale locale, - Map catalog, - ) { + void registerCatalog(String namespace, Locale locale, Map catalog) { _cache.putIfAbsent(namespace, () => >{})[locale] = catalog; notifyListeners(); } @@ -85,14 +81,8 @@ class I18n extends ChangeNotifier { } Future _ensureLoaded(String namespace) async { - final byLocale = _cache.putIfAbsent( - namespace, - () => >{}, - ); - final chain = FallbackChain( - current: _current, - defaultLocale: _defaultLocale, - ).resolve(); + final byLocale = _cache.putIfAbsent(namespace, () => >{}); + final chain = FallbackChain(current: _current, defaultLocale: _defaultLocale).resolve(); for (final l in chain) { if (byLocale.containsKey(l)) continue; byLocale[l] = await loader.load(namespace, l); @@ -102,24 +92,14 @@ class I18n extends ChangeNotifier { /// Look up a key, walking the locale fallback chain. Returns the /// placeholder if nothing hits; returns the key itself when placeholder /// is null (developer fallback — keys are more useful than blanks). - String string( - String key, { - required String namespace, - String? placeholder, - }) { + String string(String key, {required String namespace, String? placeholder}) { final byLocale = _cache[namespace]; if (byLocale == null) { - _warnOnce( - '$namespace::MISSING_NAMESPACE::$key', - 'i18n: namespace not registered: $namespace (key: $key)', - ); + _warnOnce('$namespace::MISSING_NAMESPACE::$key', 'i18n: namespace not registered: $namespace (key: $key)'); return placeholder ?? key; } - final chain = FallbackChain( - current: _current, - defaultLocale: _defaultLocale, - ).resolve(); + final chain = FallbackChain(current: _current, defaultLocale: _defaultLocale).resolve(); for (final locale in chain) { final catalog = byLocale[locale]; @@ -128,22 +108,14 @@ class I18n extends ChangeNotifier { if (hit != null) return hit; } - _warnOnce( - '$namespace::${_current.languageCode}::$key', - 'i18n: missing key "$key" in namespace "$namespace" (locale ${_current.toString()})', - ); + _warnOnce('$namespace::${_current.languageCode}::$key', 'i18n: missing key "$key" in namespace "$namespace" (locale ${_current.toString()})'); return placeholder ?? key; } /// [string] + naive `replaceAll` interpolation per replacer. /// Matches fframe: replacers whose [I18nReplacer.from] isn't present /// are silent no-ops. - String interpolated( - String key, { - required String namespace, - String? placeholder, - List replacers = const [], - }) { + String interpolated(String key, {required String namespace, String? placeholder, List replacers = const []}) { var out = string(key, namespace: namespace, placeholder: placeholder); for (final r in replacers) { out = out.replaceAll(r.from, r.replace); diff --git a/lib/kernel/src/ipc/client.dart b/lib/kernel/src/ipc/client.dart index fe147893..f278cd4e 100644 --- a/lib/kernel/src/ipc/client.dart +++ b/lib/kernel/src/ipc/client.dart @@ -10,13 +10,7 @@ import 'package:clide/kernel/src/log.dart'; import 'package:flutter/foundation.dart'; class DaemonClient extends ChangeNotifier { - DaemonClient({ - required String socketPath, - required Logger log, - required DaemonBus events, - }) : _socketPath = socketPath, - _log = log, - _events = events; + DaemonClient({required String socketPath, required Logger log, required DaemonBus events}) : _socketPath = socketPath, _log = log, _events = events; String _socketPath; String get socketPath => _socketPath; @@ -85,10 +79,7 @@ class DaemonClient extends ChangeNotifier { await _connect(); } - Future request( - String cmd, { - Map args = const {}, - }) async { + Future request(String cmd, {Map args = const {}}) async { if (!_connected || _socket == null) { // A connection attempt is in flight (startup or reconnect) — wait // for it rather than failing instantly, so queries issued during @@ -100,11 +91,7 @@ class DaemonClient extends ChangeNotifier { if (!_connected || _socket == null) { return IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'daemon not connected', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'daemon not connected'), ); } } @@ -150,15 +137,19 @@ class DaemonClient extends ChangeNotifier { _backoff = const Duration(milliseconds: 200); _setConnected(true); _log.info('ipc', 'connected to $_socketPath'); - socket.cast>().transform(utf8.decoder).transform(const LineSplitter()).listen( - _handleLine, - onDone: _handleDisconnect, - onError: (Object e) { - _log.warn('ipc', 'socket error', error: e); - _handleDisconnect(); - }, - cancelOnError: true, - ); + socket + .cast>() + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + _handleLine, + onDone: _handleDisconnect, + onError: (Object e) { + _log.warn('ipc', 'socket error', error: e); + _handleDisconnect(); + }, + cancelOnError: true, + ); } catch (e) { _log.debug('ipc', 'connect failed ($e); retry in ${_backoff.inMilliseconds}ms'); _scheduleReconnect(); @@ -174,12 +165,7 @@ class DaemonClient extends ChangeNotifier { final c = _pending.remove(r.id); if (c != null && !c.isCompleted) c.complete(r); case IpcEvent e: - _events.emit(DaemonEvent( - subsystem: e.subsystem, - kind: e.kind, - data: e.data, - ts: e.timestamp, - )); + _events.emit(DaemonEvent(subsystem: e.subsystem, kind: e.kind, data: e.data, ts: e.timestamp)); case IpcRequest _: _log.warn('ipc', 'daemon sent a request — unexpected'); } @@ -196,11 +182,7 @@ class DaemonClient extends ChangeNotifier { } void _failPending(String reason) { - final err = IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: reason, - ); + final err = IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: reason); for (final entry in _pending.entries) { if (!entry.value.isCompleted) { entry.value.complete(IpcResponse.err(id: entry.key, error: err)); @@ -213,9 +195,7 @@ class DaemonClient extends ChangeNotifier { if (_disposed) return; _reconnectTimer?.cancel(); _reconnectTimer = Timer(_backoff, _connect); - _backoff = Duration( - milliseconds: math.min(_backoff.inMilliseconds * 2, 5000), - ); + _backoff = Duration(milliseconds: math.min(_backoff.inMilliseconds * 2, 5000)); } void _setConnected(bool v) { diff --git a/lib/kernel/src/keymap/key_chord.dart b/lib/kernel/src/keymap/key_chord.dart index 9d320402..d48a423a 100644 --- a/lib/kernel/src/keymap/key_chord.dart +++ b/lib/kernel/src/keymap/key_chord.dart @@ -23,11 +23,11 @@ enum KeyModifier { /// Display string used in palette + tooltip hints. String get display => switch (this) { - KeyModifier.ctrl => 'Ctrl', - KeyModifier.alt => 'Alt', - KeyModifier.shift => 'Shift', - KeyModifier.meta => 'Cmd', - }; + KeyModifier.ctrl => 'Ctrl', + KeyModifier.alt => 'Alt', + KeyModifier.shift => 'Shift', + KeyModifier.meta => 'Cmd', + }; } /// A modifier-set + a single key, identified by layout-independent diff --git a/lib/kernel/src/keymap/keymap.dart b/lib/kernel/src/keymap/keymap.dart index 7e06f9e9..ef3ca7db 100644 --- a/lib/kernel/src/keymap/keymap.dart +++ b/lib/kernel/src/keymap/keymap.dart @@ -23,11 +23,7 @@ import 'when_clause.dart'; /// to fire when the sequence matches and the when-clause is true. @immutable class KeymapBinding { - const KeymapBinding({ - required this.sequence, - required this.intent, - this.when, - }); + const KeymapBinding({required this.sequence, required this.intent, this.when}); /// Convenience constructor for a single-chord binding. KeymapBinding.chord(KeyChord chord, {required this.intent, this.when}) : sequence = [chord]; @@ -204,9 +200,7 @@ class Keymap { /// must come first. We don't dedupe — a no-op match in a later layer /// just earns the first slot. static List _flatten(List layers) { - return [ - for (final l in layers.reversed) ...l.bindings, - ]; + return [for (final l in layers.reversed) ...l.bindings]; } @override diff --git a/lib/kernel/src/keymap/keymap_service.dart b/lib/kernel/src/keymap/keymap_service.dart index ac1f1a32..66d1e9e4 100644 --- a/lib/kernel/src/keymap/keymap_service.dart +++ b/lib/kernel/src/keymap/keymap_service.dart @@ -41,13 +41,10 @@ const String kKeymapOverridesSetting = 'app.keymap.overrides'; const String kKeymapUserFile = 'keybindings.yaml'; class KeymapService extends ChangeNotifier { - KeymapService({ - required SettingsStore settings, - required Directory appDir, - AssetBundle? bundle, - }) : _settings = settings, - _appDir = appDir, - _bundle = bundle ?? rootBundle; + KeymapService({required SettingsStore settings, required Directory appDir, AssetBundle? bundle}) + : _settings = settings, + _appDir = appDir, + _bundle = bundle ?? rootBundle; final SettingsStore _settings; final Directory _appDir; @@ -127,11 +124,7 @@ class KeymapService extends ChangeNotifier { /// defaults, the user can override either via the user file or /// settings overlay. void registerCommandBinding(String chordSpec, String commandId, {String? when}) { - _contributions.add(KeymapBinding( - sequence: KeyChord.parseSequence(chordSpec), - intent: InvokeCommandIntent(commandId), - when: WhenExpr.tryParse(when), - )); + _contributions.add(KeymapBinding(sequence: KeyChord.parseSequence(chordSpec), intent: InvokeCommandIntent(commandId), when: WhenExpr.tryParse(when))); _rebuildActive(); } @@ -149,12 +142,7 @@ class KeymapService extends ChangeNotifier { } void _rebuildActive() { - final layers = [ - if (_preset != null) _preset!, - KeymapLayer(name: 'contributions', bindings: List.unmodifiable(_contributions)), - if (_userFile != null) _userFile!, - if (_settingsOverlay != null) _settingsOverlay!, - ]; + final layers = [?_preset, KeymapLayer(name: 'contributions', bindings: List.unmodifiable(_contributions)), ?_userFile, ?_settingsOverlay]; _active = Keymap(layers); notifyListeners(); } diff --git a/lib/kernel/src/keymap/sequence_matcher.dart b/lib/kernel/src/keymap/sequence_matcher.dart index db5de36e..b052bb53 100644 --- a/lib/kernel/src/keymap/sequence_matcher.dart +++ b/lib/kernel/src/keymap/sequence_matcher.dart @@ -33,18 +33,9 @@ enum SeqOutcome { @immutable class SeqResult { - const SeqResult.fired(Intent this.intent, this.count) - : outcome = SeqOutcome.fired, - passKey = null; - const SeqResult.pending() - : outcome = SeqOutcome.pending, - intent = null, - count = 1, - passKey = null; - const SeqResult.unmatched(this.passKey) - : outcome = SeqOutcome.unmatched, - intent = null, - count = 1; + const SeqResult.fired(Intent this.intent, this.count) : outcome = SeqOutcome.fired, passKey = null; + const SeqResult.pending() : outcome = SeqOutcome.pending, intent = null, count = 1, passKey = null; + const SeqResult.unmatched(this.passKey) : outcome = SeqOutcome.unmatched, intent = null, count = 1; final SeqOutcome outcome; final Intent? intent; @@ -53,12 +44,9 @@ class SeqResult { } class SequenceMatcher { - SequenceMatcher({ - required Keymap Function() keymap, - required Map Function() context, - this.captureCounts = true, - }) : _keymap = keymap, - _context = context; + SequenceMatcher({required Keymap Function() keymap, required Map Function() context, this.captureCounts = true}) + : _keymap = keymap, + _context = context; final Keymap Function() _keymap; final Map Function() _context; diff --git a/lib/kernel/src/log.dart b/lib/kernel/src/log.dart index 0c9ba1a2..54674ca1 100644 --- a/lib/kernel/src/log.dart +++ b/lib/kernel/src/log.dart @@ -4,14 +4,7 @@ import 'dart:io'; enum LogLevel { trace, debug, info, warn, error } class LogRecord { - LogRecord({ - required this.level, - required this.source, - required this.message, - required this.timestamp, - this.error, - this.stackTrace, - }); + LogRecord({required this.level, required this.source, required this.message, required this.timestamp, this.error, this.stackTrace}); final LogLevel level; final String source; @@ -51,14 +44,7 @@ class Logger { void _emit(LogLevel level, String source, String message, {Object? error, StackTrace? stackTrace}) { if (level.index < minLevel.index) return; - final rec = LogRecord( - level: level, - source: source, - message: message, - timestamp: DateTime.now().toUtc(), - error: error, - stackTrace: stackTrace, - ); + final rec = LogRecord(level: level, source: source, message: message, timestamp: DateTime.now().toUtc(), error: error, stackTrace: stackTrace); for (final sink in _sinks) { try { sink(rec); diff --git a/lib/kernel/src/notify.dart b/lib/kernel/src/notify.dart index d4d4916f..a4228cb8 100644 --- a/lib/kernel/src/notify.dart +++ b/lib/kernel/src/notify.dart @@ -6,13 +6,8 @@ enum NotificationLevel { info, warning, error, success } @immutable class ClideNotification { - ClideNotification({ - required this.id, - required this.level, - required this.message, - this.title, - this.duration = const Duration(seconds: 4), - }) : createdAt = DateTime.now().toUtc(); + ClideNotification({required this.id, required this.level, required this.message, this.title, this.duration = const Duration(seconds: 4)}) + : createdAt = DateTime.now().toUtc(); final String id; final NotificationLevel level; @@ -41,20 +36,9 @@ class Notifications extends ChangeNotifier { if (_active.length != before) notifyListeners(); } - void _push( - NotificationLevel level, - String message, { - String? title, - Duration? duration, - }) { + void _push(NotificationLevel level, String message, {String? title, Duration? duration}) { final id = 'n${_seq++}'; - final n = ClideNotification( - id: id, - level: level, - message: message, - title: title, - duration: duration ?? const Duration(seconds: 4), - ); + final n = ClideNotification(id: id, level: level, message: message, title: title, duration: duration ?? const Duration(seconds: 4)); _active.add(n); _timers[id] = Timer(n.duration, () => dismiss(id)); notifyListeners(); diff --git a/lib/kernel/src/os.dart b/lib/kernel/src/os.dart index adf9b5a4..e3e2eabd 100644 --- a/lib/kernel/src/os.dart +++ b/lib/kernel/src/os.dart @@ -16,9 +16,7 @@ class OsLifecycleEvent extends ClideEvent { } class OsBridge { - OsBridge({required Logger log, required DaemonBus events}) - : _log = log, - _events = events; + OsBridge({required Logger log, required DaemonBus events}) : _log = log, _events = events; final Logger _log; final DaemonBus _events; diff --git a/lib/kernel/src/panels/arrangement.dart b/lib/kernel/src/panels/arrangement.dart index 43eaa105..2bb78228 100644 --- a/lib/kernel/src/panels/arrangement.dart +++ b/lib/kernel/src/panels/arrangement.dart @@ -19,13 +19,7 @@ class LayoutArrangement extends ChangeNotifier { _focusModeSnapshot = null; _focusModeSlot = null; for (final slot in preset.slots) { - _state[slot.slot] = _SlotState( - position: slot.position, - size: slot.defaultSize, - minSize: slot.minSize, - maxSize: slot.maxSize, - visible: slot.visible, - ); + _state[slot.slot] = _SlotState(position: slot.position, size: slot.defaultSize, minSize: slot.minSize, maxSize: slot.maxSize, visible: slot.visible); } notifyListeners(); } @@ -131,26 +125,15 @@ class LayoutArrangement extends ChangeNotifier { void registerSlotsInto(PanelRegistry registry, LayoutPresetContribution preset) { for (final slot in preset.slots) { - registry.registerSlot(SlotDefinition( - id: slot.slot, - position: slot.position, - defaultSize: slot.defaultSize, - minSize: slot.minSize, - maxSize: slot.maxSize, - )); + registry.registerSlot( + SlotDefinition(id: slot.slot, position: slot.position, defaultSize: slot.defaultSize, minSize: slot.minSize, maxSize: slot.maxSize), + ); } } } class _SlotState { - const _SlotState({ - required this.position, - this.size, - this.minSize, - this.maxSize, - this.visible = true, - this.collapsed = false, - }); + const _SlotState({required this.position, this.size, this.minSize, this.maxSize, this.visible = true, this.collapsed = false}); final SlotPosition position; final double? size; @@ -159,14 +142,7 @@ class _SlotState { final bool visible; final bool collapsed; - _SlotState copyWith({ - SlotPosition? position, - double? size, - double? minSize, - double? maxSize, - bool? visible, - bool? collapsed, - }) { + _SlotState copyWith({SlotPosition? position, double? size, double? minSize, double? maxSize, bool? visible, bool? collapsed}) { return _SlotState( position: position ?? this.position, size: size ?? this.size, diff --git a/lib/kernel/src/panels/drag_resize.dart b/lib/kernel/src/panels/drag_resize.dart index c1cdf688..a83d6b5a 100644 --- a/lib/kernel/src/panels/drag_resize.dart +++ b/lib/kernel/src/panels/drag_resize.dart @@ -12,13 +12,7 @@ import 'package:flutter/widgets.dart'; /// coarse step). Exposes a `slider` Semantics node so screen readers /// announce the current width. class DragResizeHandle extends StatefulWidget { - const DragResizeHandle({ - super.key, - required this.arrangement, - required this.slot, - required this.axis, - this.thickness = DragResizeHandle.defaultThickness, - }); + const DragResizeHandle({super.key, required this.arrangement, required this.slot, required this.axis, this.thickness = DragResizeHandle.defaultThickness}); final LayoutArrangement arrangement; final SlotId slot; @@ -155,11 +149,7 @@ class _BumpIntent extends Intent { /// delta = right/down. Context-panel sits on the right edge of the /// app, so we flip the sign there — right-arrow should *shrink* it, /// matching how dragging the left-edge handle rightward works. -double bumpedSlotSize({ - required SlotId slot, - required double current, - required double rawDelta, -}) { +double bumpedSlotSize({required SlotId slot, required double current, required double rawDelta}) { final delta = slot == Slots.contextPanel ? -rawDelta : rawDelta; return current + delta; } diff --git a/lib/kernel/src/panels/layout_preset.dart b/lib/kernel/src/panels/layout_preset.dart index 5ca650e3..fffae898 100644 --- a/lib/kernel/src/panels/layout_preset.dart +++ b/lib/kernel/src/panels/layout_preset.dart @@ -11,41 +11,13 @@ import 'package:clide/kernel/src/panels/slot_id.dart'; /// context 420 (drag 220–420) /// statusbar 26 (fixed height strip) LayoutPresetContribution classicPreset() => const LayoutPresetContribution( - id: 'builtin.default-layout.classic', - displayName: 'Classic', - slots: [ - LayoutSlot( - slot: Slots.sidebar, - position: SlotPosition.left, - defaultSize: 400, - minSize: 180, - maxSize: 400, - ), - LayoutSlot( - slot: Slots.workspace, - position: SlotPosition.center, - ), - LayoutSlot( - slot: Slots.contextPanel, - position: SlotPosition.right, - defaultSize: 420, - minSize: 220, - maxSize: 1000, - ), - LayoutSlot( - slot: Slots.dock, - position: SlotPosition.bottom, - defaultSize: 200, - minSize: 100, - maxSize: 600, - visible: false, - ), - LayoutSlot( - slot: Slots.statusbar, - position: SlotPosition.bottom, - defaultSize: 26, - minSize: 26, - maxSize: 26, - ), - ], - ); + id: 'builtin.default-layout.classic', + displayName: 'Classic', + slots: [ + LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 400, minSize: 180, maxSize: 400), + LayoutSlot(slot: Slots.workspace, position: SlotPosition.center), + LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 420, minSize: 220, maxSize: 1000), + LayoutSlot(slot: Slots.dock, position: SlotPosition.bottom, defaultSize: 200, minSize: 100, maxSize: 600, visible: false), + LayoutSlot(slot: Slots.statusbar, position: SlotPosition.bottom, defaultSize: 26, minSize: 26, maxSize: 26), + ], +); diff --git a/lib/kernel/src/panels/registry.dart b/lib/kernel/src/panels/registry.dart index 61f1b58c..5ad2be53 100644 --- a/lib/kernel/src/panels/registry.dart +++ b/lib/kernel/src/panels/registry.dart @@ -4,13 +4,7 @@ import 'package:flutter/foundation.dart'; @immutable class SlotDefinition { - const SlotDefinition({ - required this.id, - required this.position, - this.defaultSize, - this.minSize, - this.maxSize, - }); + const SlotDefinition({required this.id, required this.position, this.defaultSize, this.minSize, this.maxSize}); final SlotId id; final SlotPosition position; diff --git a/lib/kernel/src/panels/view_pane_snapshot.dart b/lib/kernel/src/panels/view_pane_snapshot.dart index 2cf3b896..7ba2b11d 100644 --- a/lib/kernel/src/panels/view_pane_snapshot.dart +++ b/lib/kernel/src/panels/view_pane_snapshot.dart @@ -20,13 +20,7 @@ List snapshotViewPanes(PanelRegistry panels, LayoutArrangement arrange final activeId = panels.activeTabIn(slot.id); final visible = arrangement.isVisible(slot.id); for (final tab in panels.tabsFor(slot.id)) { - out.add(ViewPane( - id: tab.id, - slot: slot.id.value, - title: tab.title, - active: tab.id == activeId, - visible: visible, - )); + out.add(ViewPane(id: tab.id, slot: slot.id.value, title: tab.title, active: tab.id == activeId, visible: visible)); } } return out; diff --git a/lib/kernel/src/project.dart b/lib/kernel/src/project.dart index 39974855..bfab2320 100644 --- a/lib/kernel/src/project.dart +++ b/lib/kernel/src/project.dart @@ -9,13 +9,7 @@ import 'package:clide/kernel/src/toolchain.dart'; import 'package:flutter/foundation.dart'; class RecentProject { - const RecentProject({ - required this.path, - required this.name, - this.branch, - required this.lastOpened, - this.startupSticky = false, - }); + const RecentProject({required this.path, required this.name, this.branch, required this.lastOpened, this.startupSticky = false}); final String path; final String name; @@ -28,28 +22,28 @@ class RecentProject { final bool startupSticky; RecentProject copyWith({bool? startupSticky, DateTime? lastOpened, String? branch}) => RecentProject( - path: path, - name: name, - branch: branch ?? this.branch, - lastOpened: lastOpened ?? this.lastOpened, - startupSticky: startupSticky ?? this.startupSticky, - ); + path: path, + name: name, + branch: branch ?? this.branch, + lastOpened: lastOpened ?? this.lastOpened, + startupSticky: startupSticky ?? this.startupSticky, + ); Map toJson() => { - 'path': path, - 'name': name, - 'branch': branch, - 'lastOpened': lastOpened.toIso8601String(), - if (startupSticky) 'startupSticky': true, - }; + 'path': path, + 'name': name, + 'branch': branch, + 'lastOpened': lastOpened.toIso8601String(), + if (startupSticky) 'startupSticky': true, + }; factory RecentProject.fromJson(Map json) => RecentProject( - path: json['path'] as String? ?? '', - name: json['name'] as String? ?? '', - branch: json['branch'] as String?, - lastOpened: DateTime.tryParse(json['lastOpened'] as String? ?? '') ?? DateTime.now(), - startupSticky: json['startupSticky'] as bool? ?? false, - ); + path: json['path'] as String? ?? '', + name: json['name'] as String? ?? '', + branch: json['branch'] as String?, + lastOpened: DateTime.tryParse(json['lastOpened'] as String? ?? '') ?? DateTime.now(), + startupSticky: json['startupSticky'] as bool? ?? false, + ); String get relativePath { final home = Platform.environment['HOME'] ?? ''; @@ -77,12 +71,12 @@ class ProjectManager extends ChangeNotifier { required Toolchain toolchain, Future Function(String path)? onProjectOpen, Future Function(String path)? onValidateProject, - }) : _log = log, - _events = events, - _settings = settings, - _toolchain = toolchain, - _onProjectOpen = onProjectOpen, - _onValidateProject = onValidateProject; + }) : _log = log, + _events = events, + _settings = settings, + _toolchain = toolchain, + _onProjectOpen = onProjectOpen, + _onValidateProject = onValidateProject; final Logger _log; final DaemonBus _events; @@ -137,10 +131,7 @@ class ProjectManager extends ChangeNotifier { // project (T-115). final wasSticky = isStickyStartup(root); _recents.removeWhere((r) => r.path == root); - _recents.insert( - 0, - RecentProject(path: root, name: name, branch: branch, lastOpened: DateTime.now(), startupSticky: wasSticky), - ); + _recents.insert(0, RecentProject(path: root, name: name, branch: branch, lastOpened: DateTime.now(), startupSticky: wasSticky)); if (_recents.length > 10) _recents = _recents.sublist(0, 10); await _settings.set('app.recentProjects', jsonEncode(_recents.map((r) => r.toJson()).toList())); diff --git a/lib/kernel/src/reader_nav.dart b/lib/kernel/src/reader_nav.dart index beca9352..04ea3659 100644 --- a/lib/kernel/src/reader_nav.dart +++ b/lib/kernel/src/reader_nav.dart @@ -17,11 +17,7 @@ import 'package:clide/kernel/src/events/message_bus.dart'; import 'package:flutter/foundation.dart'; class ReaderNav extends ChangeNotifier { - ReaderNav({ - required MessageBus messages, - required this.publisherId, - required this.dataKey, - }) : _messages = messages { + ReaderNav({required MessageBus messages, required this.publisherId, required this.dataKey}) : _messages = messages { // Retained recorder: every selection for this reader is captured // here whether or not the reader widget is mounted. _selectionSub = _messages.subscribe(publisher: publisherId, channel: 'selection').listen((m) { @@ -132,17 +128,14 @@ class ReaderNavRegistry { /// `clide status` to surface what the user is reading (T-221, D-6 parity) — /// viewer files aren't editor buffers, so they don't live in EditorRegistry. Map get currentByReader => { - for (final e in _navs.entries) - if (e.value.current != null) e.key: e.value.current!, - }; + for (final e in _navs.entries) + if (e.value.current != null) e.key: e.value.current!, + }; /// The retained [ReaderNav] for [publisherId], created on first use. /// [dataKey] is the bus-payload key for this reader's entry. ReaderNav navFor(String publisherId, {required String dataKey}) { - return _navs.putIfAbsent( - publisherId, - () => ReaderNav(messages: _messages, publisherId: publisherId, dataKey: dataKey), - ); + return _navs.putIfAbsent(publisherId, () => ReaderNav(messages: _messages, publisherId: publisherId, dataKey: dataKey)); } void dispose() { diff --git a/lib/kernel/src/secrets.dart b/lib/kernel/src/secrets.dart index b3537cd2..7251cfac 100644 --- a/lib/kernel/src/secrets.dart +++ b/lib/kernel/src/secrets.dart @@ -6,25 +6,15 @@ class SecretsVault { final Map _memory = {}; - Future write({ - required String extensionId, - required String key, - required String value, - }) async { + Future write({required String extensionId, required String key, required String value}) async { _memory['$extensionId/$key'] = value; } - Future read({ - required String extensionId, - required String key, - }) async { + Future read({required String extensionId, required String key}) async { return _memory['$extensionId/$key']; } - Future delete({ - required String extensionId, - required String key, - }) async { + Future delete({required String extensionId, required String key}) async { _memory.remove('$extensionId/$key'); } diff --git a/lib/kernel/src/syntax/tree_sitter_ffi.dart b/lib/kernel/src/syntax/tree_sitter_ffi.dart index 4f43d6d0..8c4dff5f 100644 --- a/lib/kernel/src/syntax/tree_sitter_ffi.dart +++ b/lib/kernel/src/syntax/tree_sitter_ffi.dart @@ -124,28 +124,28 @@ typedef DWasmEngineDelete = void Function(Pointer); class TreeSitterLib { TreeSitterLib._(DynamicLibrary lib) - : parserNew = lib.lookupFunction<_TsParserNew, DTsParserNew>('ts_parser_new'), - parserDelete = lib.lookupFunction<_TsParserDelete, DTsParserDelete>('ts_parser_delete'), - parserSetLanguage = lib.lookupFunction<_TsParserSetLanguage, DTsParserSetLanguage>('ts_parser_set_language'), - parserSetWasmStore = lib.lookupFunction<_TsParserSetWasmStore, DTsParserSetWasmStore>('ts_parser_set_wasm_store'), - parserParseString = lib.lookupFunction<_TsParserParseString, DTsParserParseString>('ts_parser_parse_string'), - treeDelete = lib.lookupFunction<_TsTreeDelete, DTsTreeDelete>('ts_tree_delete'), - treeRootNode = lib.lookupFunction<_TsTreeRootNode, DTsTreeRootNode>('ts_tree_root_node'), - nodeStartByte = lib.lookupFunction<_TsNodeStartByte, DTsNodeStartByte>('ts_node_start_byte'), - nodeEndByte = lib.lookupFunction<_TsNodeEndByte, DTsNodeEndByte>('ts_node_end_byte'), - queryNew = lib.lookupFunction<_TsQueryNew, DTsQueryNew>('ts_query_new'), - queryDelete = lib.lookupFunction<_TsQueryDelete, DTsQueryDelete>('ts_query_delete'), - queryCaptureCount = lib.lookupFunction<_TsQueryCaptureCount, DTsQueryCaptureCount>('ts_query_capture_count'), - queryCaptureNameForId = lib.lookupFunction<_TsQueryCaptureNameForId, DTsQueryCaptureNameForId>('ts_query_capture_name_for_id'), - queryCursorNew = lib.lookupFunction<_TsQueryCursorNew, DTsQueryCursorNew>('ts_query_cursor_new'), - queryCursorDelete = lib.lookupFunction<_TsQueryCursorDelete, DTsQueryCursorDelete>('ts_query_cursor_delete'), - queryCursorExec = lib.lookupFunction<_TsQueryCursorExec, DTsQueryCursorExec>('ts_query_cursor_exec'), - queryCursorNextMatch = lib.lookupFunction<_TsQueryCursorNextMatch, DTsQueryCursorNextMatch>('ts_query_cursor_next_match'), - wasmStoreNew = lib.lookupFunction<_TsWasmStoreNew, DTsWasmStoreNew>('ts_wasm_store_new'), - wasmStoreDelete = lib.lookupFunction<_TsWasmStoreDelete, DTsWasmStoreDelete>('ts_wasm_store_delete'), - wasmStoreLoadLanguage = lib.lookupFunction<_TsWasmStoreLoadLanguage, DTsWasmStoreLoadLanguage>('ts_wasm_store_load_language'), - wasmEngineNew = lib.lookupFunction<_WasmEngineNew, DWasmEngineNew>('wasm_engine_new'), - wasmEngineDelete = lib.lookupFunction<_WasmEngineDelete, DWasmEngineDelete>('wasm_engine_delete'); + : parserNew = lib.lookupFunction<_TsParserNew, DTsParserNew>('ts_parser_new'), + parserDelete = lib.lookupFunction<_TsParserDelete, DTsParserDelete>('ts_parser_delete'), + parserSetLanguage = lib.lookupFunction<_TsParserSetLanguage, DTsParserSetLanguage>('ts_parser_set_language'), + parserSetWasmStore = lib.lookupFunction<_TsParserSetWasmStore, DTsParserSetWasmStore>('ts_parser_set_wasm_store'), + parserParseString = lib.lookupFunction<_TsParserParseString, DTsParserParseString>('ts_parser_parse_string'), + treeDelete = lib.lookupFunction<_TsTreeDelete, DTsTreeDelete>('ts_tree_delete'), + treeRootNode = lib.lookupFunction<_TsTreeRootNode, DTsTreeRootNode>('ts_tree_root_node'), + nodeStartByte = lib.lookupFunction<_TsNodeStartByte, DTsNodeStartByte>('ts_node_start_byte'), + nodeEndByte = lib.lookupFunction<_TsNodeEndByte, DTsNodeEndByte>('ts_node_end_byte'), + queryNew = lib.lookupFunction<_TsQueryNew, DTsQueryNew>('ts_query_new'), + queryDelete = lib.lookupFunction<_TsQueryDelete, DTsQueryDelete>('ts_query_delete'), + queryCaptureCount = lib.lookupFunction<_TsQueryCaptureCount, DTsQueryCaptureCount>('ts_query_capture_count'), + queryCaptureNameForId = lib.lookupFunction<_TsQueryCaptureNameForId, DTsQueryCaptureNameForId>('ts_query_capture_name_for_id'), + queryCursorNew = lib.lookupFunction<_TsQueryCursorNew, DTsQueryCursorNew>('ts_query_cursor_new'), + queryCursorDelete = lib.lookupFunction<_TsQueryCursorDelete, DTsQueryCursorDelete>('ts_query_cursor_delete'), + queryCursorExec = lib.lookupFunction<_TsQueryCursorExec, DTsQueryCursorExec>('ts_query_cursor_exec'), + queryCursorNextMatch = lib.lookupFunction<_TsQueryCursorNextMatch, DTsQueryCursorNextMatch>('ts_query_cursor_next_match'), + wasmStoreNew = lib.lookupFunction<_TsWasmStoreNew, DTsWasmStoreNew>('ts_wasm_store_new'), + wasmStoreDelete = lib.lookupFunction<_TsWasmStoreDelete, DTsWasmStoreDelete>('ts_wasm_store_delete'), + wasmStoreLoadLanguage = lib.lookupFunction<_TsWasmStoreLoadLanguage, DTsWasmStoreLoadLanguage>('ts_wasm_store_load_language'), + wasmEngineNew = lib.lookupFunction<_WasmEngineNew, DWasmEngineNew>('wasm_engine_new'), + wasmEngineDelete = lib.lookupFunction<_WasmEngineDelete, DWasmEngineDelete>('wasm_engine_delete'); /// Constructs a [TreeSitterLib] from caller-supplied Dart closures. Used by /// tests to substitute the FFI surface without dlopen'ing the real library; @@ -176,30 +176,30 @@ class TreeSitterLib { DTsWasmStoreLoadLanguage? wasmStoreLoadLanguage, DWasmEngineNew? wasmEngineNew, DWasmEngineDelete? wasmEngineDelete, - }) : parserNew = parserNew ?? (() => nullptr), - parserDelete = parserDelete ?? ((_) {}), - parserSetLanguage = parserSetLanguage ?? ((_, __) => false), - parserSetWasmStore = parserSetWasmStore ?? ((_, __) {}), - parserParseString = parserParseString ?? ((_, __, ___, ____) => nullptr), - treeDelete = treeDelete ?? ((_) {}), - // Leaks a zeroed TSNode allocation — only hit when the test supplies - // a non-null parserParseString without also supplying treeRootNode. - treeRootNode = treeRootNode ?? ((_) => calloc().ref), - nodeStartByte = nodeStartByte ?? ((_) => 0), - nodeEndByte = nodeEndByte ?? ((_) => 0), - queryNew = queryNew ?? ((_, __, ___, ____, _____) => nullptr), - queryDelete = queryDelete ?? ((_) {}), - queryCaptureCount = queryCaptureCount ?? ((_) => 0), - queryCaptureNameForId = queryCaptureNameForId ?? ((_, __, ___) => nullptr), - queryCursorNew = queryCursorNew ?? (() => nullptr), - queryCursorDelete = queryCursorDelete ?? ((_) {}), - queryCursorExec = queryCursorExec ?? ((_, __, ___) {}), - queryCursorNextMatch = queryCursorNextMatch ?? ((_, __) => false), - wasmStoreNew = wasmStoreNew ?? ((_, __) => nullptr), - wasmStoreDelete = wasmStoreDelete ?? ((_) {}), - wasmStoreLoadLanguage = wasmStoreLoadLanguage ?? ((_, __, ___, ____, _____) => nullptr), - wasmEngineNew = wasmEngineNew ?? (() => nullptr), - wasmEngineDelete = wasmEngineDelete ?? ((_) {}); + }) : parserNew = parserNew ?? (() => nullptr), + parserDelete = parserDelete ?? ((_) {}), + parserSetLanguage = parserSetLanguage ?? ((_, _) => false), + parserSetWasmStore = parserSetWasmStore ?? ((_, _) {}), + parserParseString = parserParseString ?? ((_, _, _, _) => nullptr), + treeDelete = treeDelete ?? ((_) {}), + // Leaks a zeroed TSNode allocation — only hit when the test supplies + // a non-null parserParseString without also supplying treeRootNode. + treeRootNode = treeRootNode ?? ((_) => calloc().ref), + nodeStartByte = nodeStartByte ?? ((_) => 0), + nodeEndByte = nodeEndByte ?? ((_) => 0), + queryNew = queryNew ?? ((_, _, _, _, _) => nullptr), + queryDelete = queryDelete ?? ((_) {}), + queryCaptureCount = queryCaptureCount ?? ((_) => 0), + queryCaptureNameForId = queryCaptureNameForId ?? ((_, _, _) => nullptr), + queryCursorNew = queryCursorNew ?? (() => nullptr), + queryCursorDelete = queryCursorDelete ?? ((_) {}), + queryCursorExec = queryCursorExec ?? ((_, _, _) {}), + queryCursorNextMatch = queryCursorNextMatch ?? ((_, _) => false), + wasmStoreNew = wasmStoreNew ?? ((_, _) => nullptr), + wasmStoreDelete = wasmStoreDelete ?? ((_) {}), + wasmStoreLoadLanguage = wasmStoreLoadLanguage ?? ((_, _, _, _, _) => nullptr), + wasmEngineNew = wasmEngineNew ?? (() => nullptr), + wasmEngineDelete = wasmEngineDelete ?? ((_) {}); final DTsParserNew parserNew; final DTsParserDelete parserDelete; @@ -256,10 +256,10 @@ class TreeSitterLib { final libName = Platform.isLinux ? 'libtree-sitter.so' : Platform.isMacOS - ? 'libtree-sitter.dylib' - : Platform.isWindows - ? 'tree-sitter.dll' - : null; + ? 'libtree-sitter.dylib' + : Platform.isWindows + ? 'tree-sitter.dll' + : null; if (libName == null) { lastOpenError = 'unsupported platform ${Platform.operatingSystem}'; lastOpenErrorPath = null; diff --git a/lib/kernel/src/syntax/tree_sitter_service.dart b/lib/kernel/src/syntax/tree_sitter_service.dart index a9a28157..2a7180df 100644 --- a/lib/kernel/src/syntax/tree_sitter_service.dart +++ b/lib/kernel/src/syntax/tree_sitter_service.dart @@ -20,11 +20,7 @@ typedef GrammarBytesLoader = Future Function(String language); typedef GrammarQueryLoader = Future Function(String language); class SyntaxSpan { - const SyntaxSpan({ - required this.start, - required this.end, - required this.role, - }); + const SyntaxSpan({required this.start, required this.end, required this.role}); final int start; final int end; @@ -39,11 +35,7 @@ class SyntaxResult { } class _LoadedGrammar { - _LoadedGrammar({ - required this.language, - required this.query, - required this.captureNames, - }); + _LoadedGrammar({required this.language, required this.query, required this.captureNames}); final Pointer language; final Pointer query; @@ -56,13 +48,10 @@ class TreeSitterService { /// Production constructor: uses the dlopen'd [TreeSitterLib.instance] and /// the Flutter [rootBundle]. Tests pass [lib] / [grammarBytes] / /// [grammarQuery] to substitute a fake FFI surface and in-memory assets. - TreeSitterService({ - TreeSitterLib? lib, - GrammarBytesLoader? grammarBytes, - GrammarQueryLoader? grammarQuery, - }) : _injectedLib = lib, - _grammarBytes = grammarBytes ?? _defaultGrammarBytes, - _grammarQuery = grammarQuery ?? _defaultGrammarQuery; + TreeSitterService({TreeSitterLib? lib, GrammarBytesLoader? grammarBytes, GrammarQueryLoader? grammarQuery}) + : _injectedLib = lib, + _grammarBytes = grammarBytes ?? _defaultGrammarBytes, + _grammarQuery = grammarQuery ?? _defaultGrammarQuery; final TreeSitterLib? _injectedLib; final GrammarBytesLoader _grammarBytes; @@ -142,13 +131,7 @@ class TreeSitterService { wasmNative.asTypedList(wasmBytes.length).setAll(0, wasmBytes); final error = calloc(); - final lang = lib.wasmStoreLoadLanguage( - _store!, - nameNative.cast(), - wasmNative, - wasmBytes.length, - error, - ); + final lang = lib.wasmStoreLoadLanguage(_store!, nameNative.cast(), wasmNative, wasmBytes.length, error); calloc.free(wasmNative); calloc.free(nameNative); @@ -174,13 +157,7 @@ class TreeSitterService { final errorOffset = calloc(); final errorType = calloc(); - query = lib.queryNew( - lang, - queryNative.cast(), - queryLen, - errorOffset, - errorType, - ); + query = lib.queryNew(lang, queryNative.cast(), queryLen, errorOffset, errorType); calloc.free(queryNative); calloc.free(errorOffset); @@ -198,11 +175,7 @@ class TreeSitterService { } } - final grammar = _LoadedGrammar( - language: lang, - query: query, - captureNames: captureNames, - ); + final grammar = _LoadedGrammar(language: lang, query: query, captureNames: captureNames); _grammars[language] = grammar; return grammar; } catch (_) { @@ -244,12 +217,7 @@ class TreeSitterService { // Parse source. final sourceNative = source.toNativeUtf8(); final sourceLen = utf8.encode(source).length; - final tree = lib.parserParseString( - parser, - nullptr, - sourceNative.cast(), - sourceLen, - ); + final tree = lib.parserParseString(parser, nullptr, sourceNative.cast(), sourceLen); if (tree == nullptr) { calloc.free(sourceNative); @@ -270,11 +238,7 @@ class TreeSitterService { final cap = m.captures[i]; final captureIndex = cap.index; if (captureIndex < grammar.captureNames.length) { - spans.add(SyntaxSpan( - start: lib.nodeStartByte(cap.node), - end: lib.nodeEndByte(cap.node), - role: grammar.captureNames[captureIndex], - )); + spans.add(SyntaxSpan(start: lib.nodeStartByte(cap.node), end: lib.nodeEndByte(cap.node), role: grammar.captureNames[captureIndex])); } } } diff --git a/lib/kernel/src/theme/contrast.dart b/lib/kernel/src/theme/contrast.dart index 5280ed98..0a4c2490 100644 --- a/lib/kernel/src/theme/contrast.dart +++ b/lib/kernel/src/theme/contrast.dart @@ -7,12 +7,7 @@ import 'package:flutter/foundation.dart'; /// A foreground/background token pair the a11y contrast suite walks. @immutable class ContrastPair { - const ContrastPair({ - required this.name, - required this.foreground, - required this.background, - this.largeText = false, - }); + const ContrastPair({required this.name, required this.foreground, required this.background, this.largeText = false}); final String name; final Color foreground; @@ -49,62 +44,18 @@ double minimumRatio(ContrastPair pair) => pair.largeText ? 3.0 : 4.5; /// muted text, status chips, syntax tokens, and the focus border lives /// in [extendedPairs], which only `-hc`/`-cb` variants must pass. List canonicalPairs(SurfaceTokens s) => [ - ContrastPair( - name: 'global.text_on_background', - foreground: s.globalForeground, - background: s.globalBackground, - ), - ContrastPair( - name: 'panel.header_foreground_on_panel', - foreground: s.panelHeaderForeground, - background: s.panelHeader, - ), - ContrastPair( - name: 'sidebar.foreground_on_sidebar', - foreground: s.sidebarForeground, - background: s.sidebarBackground, - ), - ContrastPair( - name: 'statusbar.foreground_on_statusbar', - foreground: s.statusBarForeground, - background: s.statusBarBackground, - ), - ContrastPair( - name: 'tab.active_text_on_active_bg', - foreground: s.tabActiveForeground, - background: s.tabActive, - ), - ContrastPair( - name: 'tab.inactive_text_on_inactive_bg', - foreground: s.tabInactiveForeground, - background: s.tabInactive, - ), - ContrastPair( - name: 'button.text_on_button', - foreground: s.buttonForeground, - background: s.buttonBackground, - ), - ContrastPair( - name: 'listItem.selected_text_on_selected_bg', - foreground: s.listItemSelectedForeground, - background: s.listItemSelectedBackground, - ), - ContrastPair( - name: 'listItem.text_on_list', - foreground: s.listItemForeground, - background: s.listItemBackground, - ), - ContrastPair( - name: 'tooltip.text_on_tooltip', - foreground: s.tooltipForeground, - background: s.tooltipBackground, - ), - ContrastPair( - name: 'dropdown.text_on_dropdown', - foreground: s.dropdownForeground, - background: s.dropdownBackground, - ), - ]; + ContrastPair(name: 'global.text_on_background', foreground: s.globalForeground, background: s.globalBackground), + ContrastPair(name: 'panel.header_foreground_on_panel', foreground: s.panelHeaderForeground, background: s.panelHeader), + ContrastPair(name: 'sidebar.foreground_on_sidebar', foreground: s.sidebarForeground, background: s.sidebarBackground), + ContrastPair(name: 'statusbar.foreground_on_statusbar', foreground: s.statusBarForeground, background: s.statusBarBackground), + ContrastPair(name: 'tab.active_text_on_active_bg', foreground: s.tabActiveForeground, background: s.tabActive), + ContrastPair(name: 'tab.inactive_text_on_inactive_bg', foreground: s.tabInactiveForeground, background: s.tabInactive), + ContrastPair(name: 'button.text_on_button', foreground: s.buttonForeground, background: s.buttonBackground), + ContrastPair(name: 'listItem.selected_text_on_selected_bg', foreground: s.listItemSelectedForeground, background: s.listItemSelectedBackground), + ContrastPair(name: 'listItem.text_on_list', foreground: s.listItemForeground, background: s.listItemBackground), + ContrastPair(name: 'tooltip.text_on_tooltip', foreground: s.tooltipForeground, background: s.tooltipBackground), + ContrastPair(name: 'dropdown.text_on_dropdown', foreground: s.dropdownForeground, background: s.dropdownBackground), +]; /// Stricter pair set — only the high-contrast (`-hc`) and colour-blind /// (`-cb`) theme variants must clear it. See D-69. These are the @@ -112,94 +63,37 @@ List canonicalPairs(SurfaceTokens s) => [ /// text, status chip foregrounds, syntax tokens on the code-block /// surface, and the focus-indicating panel border. List extendedPairs(SurfaceTokens s) => [ - ContrastPair( - name: 'global.text_muted_on_background', - foreground: s.globalTextMuted, - background: s.globalBackground, - ), - ContrastPair( - name: 'global.text_muted_on_panel', - foreground: s.globalTextMuted, - background: s.panelBackground, - ), - ContrastPair( - name: 'status.success_on_statusbar', - foreground: s.statusSuccess, - background: s.statusBarBackground, - ), - ContrastPair( - name: 'status.warning_on_statusbar', - foreground: s.statusWarning, - background: s.statusBarBackground, - ), - ContrastPair( - name: 'status.error_on_statusbar', - foreground: s.statusError, - background: s.statusBarBackground, - ), - ContrastPair( - name: 'status.info_on_statusbar', - foreground: s.statusInfo, - background: s.statusBarBackground, - ), - ContrastPair( - name: 'syntax.keyword_on_panel', - foreground: s.syntaxKeyword, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.type_on_panel', - foreground: s.syntaxType, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.string_on_panel', - foreground: s.syntaxString, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.number_on_panel', - foreground: s.syntaxNumber, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.comment_on_panel', - foreground: s.syntaxComment, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.method_on_panel', - foreground: s.syntaxMethod, - background: s.panelBackground, - ), - ContrastPair( - name: 'syntax.punct_on_panel', - foreground: s.syntaxPunct, - background: s.panelBackground, - ), - // WCAG 1.4.11 wants 3:1 for non-text UI components like a focus - // border against the adjacent surface. - ContrastPair( - name: 'panel.active_border_on_background', - foreground: s.panelActiveBorder, - background: s.globalBackground, - largeText: true, - ), - // selection.foreground_on_selection is intentionally omitted here. - // - // The `selectionBackground` token defaults to `globalFocus.withAlpha(0x66)` - // — a semi-transparent tint composited onto the real content background at - // runtime. The WCAG compositor in contrastRatio() blends onto neutral grey - // (0x808080) rather than the actual dark panel background, which - // systematically understates the readable contrast for all current bundled - // themes. Adding the pair here would require retuning palettes, which D-69 - // forbids for user-contract themes. - // - // Enforcement is deferred to a follow-up ticket: -hc/-cb variants will - // declare an explicit `surface.selectionBackground` override that is - // opaque enough to clear 3:1 against the grey compositor, at which point - // the pair can be added to extendedPairs. - ]; + ContrastPair(name: 'global.text_muted_on_background', foreground: s.globalTextMuted, background: s.globalBackground), + ContrastPair(name: 'global.text_muted_on_panel', foreground: s.globalTextMuted, background: s.panelBackground), + ContrastPair(name: 'status.success_on_statusbar', foreground: s.statusSuccess, background: s.statusBarBackground), + ContrastPair(name: 'status.warning_on_statusbar', foreground: s.statusWarning, background: s.statusBarBackground), + ContrastPair(name: 'status.error_on_statusbar', foreground: s.statusError, background: s.statusBarBackground), + ContrastPair(name: 'status.info_on_statusbar', foreground: s.statusInfo, background: s.statusBarBackground), + ContrastPair(name: 'syntax.keyword_on_panel', foreground: s.syntaxKeyword, background: s.panelBackground), + ContrastPair(name: 'syntax.type_on_panel', foreground: s.syntaxType, background: s.panelBackground), + ContrastPair(name: 'syntax.string_on_panel', foreground: s.syntaxString, background: s.panelBackground), + ContrastPair(name: 'syntax.number_on_panel', foreground: s.syntaxNumber, background: s.panelBackground), + ContrastPair(name: 'syntax.comment_on_panel', foreground: s.syntaxComment, background: s.panelBackground), + ContrastPair(name: 'syntax.method_on_panel', foreground: s.syntaxMethod, background: s.panelBackground), + ContrastPair(name: 'syntax.punct_on_panel', foreground: s.syntaxPunct, background: s.panelBackground), + // WCAG 1.4.11 wants 3:1 for non-text UI components like a focus + // border against the adjacent surface. + ContrastPair(name: 'panel.active_border_on_background', foreground: s.panelActiveBorder, background: s.globalBackground, largeText: true), + // selection.foreground_on_selection is intentionally omitted here. + // + // The `selectionBackground` token defaults to `globalFocus.withAlpha(0x66)` + // — a semi-transparent tint composited onto the real content background at + // runtime. The WCAG compositor in contrastRatio() blends onto neutral grey + // (0x808080) rather than the actual dark panel background, which + // systematically understates the readable contrast for all current bundled + // themes. Adding the pair here would require retuning palettes, which D-69 + // forbids for user-contract themes. + // + // Enforcement is deferred to a follow-up ticket: -hc/-cb variants will + // declare an explicit `surface.selectionBackground` override that is + // opaque enough to clear 3:1 against the grey compositor, at which point + // the pair can be added to extendedPairs. +]; /// Convenience for tests: returns the list of [canonicalPairs] that /// fail WCAG AA. @@ -223,18 +117,15 @@ List _failures(List pairs) { @immutable class ContrastFailure { - const ContrastFailure({ - required this.pair, - required this.ratio, - required this.minimum, - }); + const ContrastFailure({required this.pair, required this.ratio, required this.minimum}); final ContrastPair pair; final double ratio; final double minimum; @override - String toString() => 'contrast ${pair.name}: ${ratio.toStringAsFixed(2)} < ' + String toString() => + 'contrast ${pair.name}: ${ratio.toStringAsFixed(2)} < ' '${minimum.toStringAsFixed(1)}'; } @@ -244,12 +135,7 @@ Color _composite(Color src, Color dst) { final a = src.a; if (a >= 0.999) return src; double mix(double s, double d) => s * a + d * (1 - a); - return Color.from( - alpha: 1.0, - red: mix(src.r, dst.r), - green: mix(src.g, dst.g), - blue: mix(src.b, dst.b), - ); + return Color.from(alpha: 1.0, red: mix(src.r, dst.r), green: mix(src.g, dst.g), blue: mix(src.b, dst.b)); } double _relativeLuminance(Color c) { diff --git a/lib/kernel/src/theme/controller.dart b/lib/kernel/src/theme/controller.dart index bbd397df..94aac132 100644 --- a/lib/kernel/src/theme/controller.dart +++ b/lib/kernel/src/theme/controller.dart @@ -5,12 +5,7 @@ import 'package:flutter/widgets.dart'; @immutable class ClideThemeData { - const ClideThemeData({ - required this.name, - required this.displayName, - required this.dark, - required this.surface, - }); + const ClideThemeData({required this.name, required this.displayName, required this.dark, required this.surface}); final String name; final String displayName; @@ -19,12 +14,9 @@ class ClideThemeData { } class ThemeController extends ChangeNotifier { - ThemeController({ - required List bundled, - ThemeResolver resolver = const ThemeResolver(), - String? initialName, - }) : _resolver = resolver, - _defs = Map.fromEntries(bundled.map((d) => MapEntry(d.name, d))) { + ThemeController({required List bundled, ThemeResolver resolver = const ThemeResolver(), String? initialName}) + : _resolver = resolver, + _defs = Map.fromEntries(bundled.map((d) => MapEntry(d.name, d))) { final first = initialName != null && _defs.containsKey(initialName) ? initialName : bundled.first.name; _currentName = first; _current = _build(first); @@ -68,21 +60,12 @@ class ThemeController extends ChangeNotifier { surfaceOverride: def.surfaceOverride, extensionOverride: def.extensionOverride, ); - return ClideThemeData( - name: def.name, - displayName: def.displayName, - dark: def.dark, - surface: tokens, - ); + return ClideThemeData(name: def.name, displayName: def.displayName, dark: def.dark, surface: tokens); } } class ClideTheme extends InheritedNotifier { - const ClideTheme({ - super.key, - required ThemeController controller, - required super.child, - }) : super(notifier: controller); + const ClideTheme({super.key, required ThemeController controller, required super.child}) : super(notifier: controller); static ClideThemeData of(BuildContext context) { final w = context.dependOnInheritedWidgetOfExactType(); diff --git a/lib/kernel/src/theme/loader.dart b/lib/kernel/src/theme/loader.dart index 1bb4c642..f8215c58 100644 --- a/lib/kernel/src/theme/loader.dart +++ b/lib/kernel/src/theme/loader.dart @@ -70,10 +70,7 @@ class ThemeLoader { final surface = doc['surface']; final extension = doc['extension']; - final mergedSurface = { - ...syntaxSurface, - if (surface is Map) ..._parseRefMap(surface), - }; + final mergedSurface = {...syntaxSurface, if (surface is Map) ..._parseRefMap(surface)}; return ThemeDefinition( name: name, diff --git a/lib/kernel/src/theme/resolver.dart b/lib/kernel/src/theme/resolver.dart index a9441b53..ab45d91e 100644 --- a/lib/kernel/src/theme/resolver.dart +++ b/lib/kernel/src/theme/resolver.dart @@ -28,12 +28,7 @@ class ThemeResolver { final semantic = _buildSemantic(palette, semanticOverride); final surface = {}; for (final key in TokenKeys.all) { - surface[key] = _resolveSurface( - key: key, - palette: palette, - semantic: semantic, - surfaceOverride: surfaceOverride, - ); + surface[key] = _resolveSurface(key: key, palette: palette, semantic: semantic, surfaceOverride: surfaceOverride); } // selectionBackground defaults to globalFocus at ~40 % opacity (0x66 alpha) @@ -149,12 +144,7 @@ class ThemeResolver { return SemanticRoles(roles); } - Color _resolveSurface({ - required String key, - required Palette palette, - required SemanticRoles semantic, - Map? surfaceOverride, - }) { + Color _resolveSurface({required String key, required Palette palette, required SemanticRoles semantic, Map? surfaceOverride}) { final override = surfaceOverride?[key]; if (override != null) { final resolved = _resolveRef(override, palette, semantic); diff --git a/lib/kernel/src/theme/semantic.dart b/lib/kernel/src/theme/semantic.dart index b04c857b..fbbfd20e 100644 --- a/lib/kernel/src/theme/semantic.dart +++ b/lib/kernel/src/theme/semantic.dart @@ -24,17 +24,5 @@ abstract class SemanticKeys { static const error = 'error'; static const info = 'info'; - static const all = [ - mainchrome, - calltoaction, - focus, - background, - surface, - text, - textMuted, - success, - warning, - error, - info, - ]; + static const all = [mainchrome, calltoaction, focus, background, surface, text, textMuted, success, warning, error, info]; } diff --git a/lib/kernel/src/toast.dart b/lib/kernel/src/toast.dart index 6a34ef72..41817e45 100644 --- a/lib/kernel/src/toast.dart +++ b/lib/kernel/src/toast.dart @@ -13,27 +13,17 @@ enum ToastSeverity { success, warning, error, info } const String toastChannel = 'toast'; ToastSeverity _severityFromName(Object? name) => switch (name) { - 'success' => ToastSeverity.success, - 'warning' => ToastSeverity.warning, - 'error' => ToastSeverity.error, - _ => ToastSeverity.info, - }; + 'success' => ToastSeverity.success, + 'warning' => ToastSeverity.warning, + 'error' => ToastSeverity.error, + _ => ToastSeverity.info, +}; /// Raise a toast by publishing to the MessageBus — the decoupled path: /// emitters depend only on the bus, never on the [ToastService]. [publisher] /// is the emitter id (e.g. `builtin.git`), kept for provenance/filtering. -void publishToast( - MessageBus messages, - String publisher, - String message, { - ToastSeverity severity = ToastSeverity.info, - Duration? duration, -}) { - messages.publish(publisher, toastChannel, { - 'message': message, - 'severity': severity.name, - if (duration != null) 'durationMs': duration.inMilliseconds, - }); +void publishToast(MessageBus messages, String publisher, String message, {ToastSeverity severity = ToastSeverity.info, Duration? duration}) { + messages.publish(publisher, toastChannel, {'message': message, 'severity': severity.name, if (duration != null) 'durationMs': duration.inMilliseconds}); } /// One live toast. Immutable; the [ToastService] owns the list. diff --git a/lib/kernel/src/tool_check.dart b/lib/kernel/src/tool_check.dart index dd5f2a95..04e869ff 100644 --- a/lib/kernel/src/tool_check.dart +++ b/lib/kernel/src/tool_check.dart @@ -12,11 +12,7 @@ class ToolCheck extends ChangeNotifier { bool get allOk => pqlOk && tmuxOk && gitOk; - List get errors => [ - if (!pqlOk) 'pql not found', - if (!tmuxOk) 'tmux not found', - if (!gitOk) 'git not found', - ]; + List get errors => [if (!pqlOk) 'pql not found', if (!tmuxOk) 'tmux not found', if (!gitOk) 'git not found']; /// Workspace root, set by the app at boot. Falls back to cwd. static String? workspaceRoot; diff --git a/lib/kernel/src/toolchain.dart b/lib/kernel/src/toolchain.dart index ea531ec5..6927137c 100644 --- a/lib/kernel/src/toolchain.dart +++ b/lib/kernel/src/toolchain.dart @@ -44,11 +44,7 @@ class Toolchain extends ChangeNotifier implements ToolchainView { bool get allOk => _resolved && missing.isEmpty; @override - List get missing => [ - if (_git == null) 'git', - if (_pql == null) 'pql', - if (_tmux == null) 'tmux', - ]; + List get missing => [if (_git == null) 'git', if (_pql == null) 'pql', if (_tmux == null) 'tmux']; /// Returns a Future that completes when resolution finishes. Future waitForResolution() { diff --git a/lib/kernel/src/toolchain_paths.dart b/lib/kernel/src/toolchain_paths.dart index 9d5ca735..8f951e16 100644 --- a/lib/kernel/src/toolchain_paths.dart +++ b/lib/kernel/src/toolchain_paths.dart @@ -12,13 +12,7 @@ import 'dart:io'; /// Serializable result of tool resolution (crosses isolate boundary). class ResolvedPaths { - const ResolvedPaths({ - this.git, - this.pql, - this.tmux, - this.shell, - this.gitEnv, - }); + const ResolvedPaths({this.git, this.pql, this.tmux, this.shell, this.gitEnv}); final String? git; final String? pql; @@ -66,11 +60,7 @@ class _StaticToolchain implements ToolchainView { @override bool get allOk => missing.isEmpty; @override - List get missing => [ - if (_paths.git == null) 'git', - if (_paths.pql == null) 'pql', - if (_paths.tmux == null) 'tmux', - ]; + List get missing => [if (_paths.git == null) 'git', if (_paths.pql == null) 'pql', if (_paths.tmux == null) 'tmux']; } /// Top-level function for compute/isolate use. Returns a plain-data @@ -88,10 +78,7 @@ ResolvedPaths resolveToolchainPaths() { if (dugiteGit != null) { git = dugiteGit; final dugiteRoot = File(dugiteGit).parent.parent.path; - gitEnv = { - 'GIT_EXEC_PATH': '$dugiteRoot/libexec/git-core', - 'GIT_TEMPLATE_DIR': '$dugiteRoot/share/git-core/templates', - }; + gitEnv = {'GIT_EXEC_PATH': '$dugiteRoot/libexec/git-core', 'GIT_TEMPLATE_DIR': '$dugiteRoot/share/git-core/templates'}; } else { git = _findOnPath('git'); } @@ -145,12 +132,8 @@ String? _firstExisting(List candidates) { } /// Build expanded PATH inline — must be self-contained for isolate use. -String _expandedPath() => expandToolPath( - Platform.environment['PATH'] ?? '', - isMac: Platform.isMacOS, - isLinux: Platform.isLinux, - home: Platform.environment['HOME'], - ); +String _expandedPath() => + expandToolPath(Platform.environment['PATH'] ?? '', isMac: Platform.isMacOS, isLinux: Platform.isLinux, home: Platform.environment['HOME']); /// Pure PATH-expansion logic, extracted so it's testable without touching the /// process environment. @@ -164,12 +147,7 @@ String _expandedPath() => expandToolPath( String expandToolPath(String base, {required bool isMac, required bool isLinux, String? home}) { if (!isMac && !isLinux) return base; final h = home ?? ''; - final extras = [ - if (h.isNotEmpty) '$h/.local/bin', - if (isMac) '/opt/homebrew/bin', - if (isMac) '/opt/homebrew/sbin', - '/usr/local/bin', - ]; + final extras = [if (h.isNotEmpty) '$h/.local/bin', if (isMac) '/opt/homebrew/bin', if (isMac) '/opt/homebrew/sbin', '/usr/local/bin']; final existing = base.split(':').toSet(); final missing = extras.where((p) => !existing.contains(p)); if (missing.isEmpty) return base; diff --git a/lib/lua/src/adapter.dart b/lib/lua/src/adapter.dart index 5622f3ee..70399bf0 100644 --- a/lib/lua/src/adapter.dart +++ b/lib/lua/src/adapter.dart @@ -4,12 +4,7 @@ import 'package:clide/extension/extension.dart'; /// and a handle to the Lua state; contributions are proxied to /// callbacks registered by `clide.contribute(...)` from the Lua side. class LuaExtension extends ClideExtension { - LuaExtension({ - required this.id, - required this.title, - required this.version, - this.dependsOn = const [], - }); + LuaExtension({required this.id, required this.title, required this.version, this.dependsOn = const []}); @override final String id; diff --git a/lib/main.dart b/lib/main.dart index da24b6e4..063aab08 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,12 +147,7 @@ Future main() async { } catch (e) { ipcLog.warn('mcp', 'stop failed during swap: $e'); } - final server = IpcServer( - dispatcher: dispatcher, - workspaceRoot: workRoot.path, - log: ipcLog, - events: daemonBus, - ); + final server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot.path, log: ipcLog, events: daemonBus); ipcServer = server; try { await server.start(); @@ -193,24 +188,14 @@ Future main() async { return next; } - DaemonDispatcher buildDispatcher( - DaemonBus events, - Toolchain tc, - Directory workRoot, - LayoutArrangement arrangement, - PanelRegistry panels, - ) { + DaemonDispatcher buildDispatcher(DaemonBus events, Toolchain tc, Directory workRoot, LayoutArrangement arrangement, PanelRegistry panels) { final dispatcher = DaemonDispatcher(); final eventSink = _BusEventSink(events); final paneRegistry = PaneRegistry(events: eventSink); // D-6 parity (T-219, D-83): make the tabs the user sees in the GUI // visible to `pane list` by snapshotting the kernel PanelRegistry + // LayoutArrangement at request time — no mirrored state to drift. - registerPaneCommands( - dispatcher, - paneRegistry, - viewPanes: () => snapshotViewPanes(panels, arrangement), - ); + registerPaneCommands(dispatcher, paneRegistry, viewPanes: () => snapshotViewPanes(panels, arrangement)); // Trusted read-only roots beyond the workspace: the global Claude // config dir (~/.claude), so the reader can open user-scope skill / // agent / command markdown the Config tab surfaces (D-80, T-195). @@ -225,11 +210,7 @@ Future main() async { registerFilesCommands(dispatcher, filesService); // Search reuses the files service's resolved ignore set so the // grep honours the same ignore_files: layering (D-4 / D-79). - final searchService = SearchService( - root: workRoot, - ignore: filesService.ignore, - events: eventSink, - ); + final searchService = SearchService(root: workRoot, ignore: filesService.ignore, events: eventSink); registerSearchCommands(dispatcher, searchService); final editorRegistry = EditorRegistry(events: eventSink, workspaceRoot: workRoot); registerEditorCommands(dispatcher, editorRegistry); @@ -241,11 +222,7 @@ Future main() async { // `clide ui open ` — drive the GUI readers from the CLI // (T-231, drive-half of D-6). Publishes a 'selection' to the kernel // MessageBus, captured post-boot; null in headless contexts. - registerUiCommands( - dispatcher, - () => kernelMessages?.publish, - filterValue: (address) => kernelFilterStates?.get(address), - ); + registerUiCommands(dispatcher, () => kernelMessages?.publish, filterValue: (address) => kernelFilterStates?.get(address)); // `clide image show ` — drive an image card into the Claude // conversation log (T-249, drive-half of D-6). Resolves the path // (workspace-relative → absolute, must exist) here where workRoot is in @@ -273,12 +250,7 @@ Future main() async { 'behind': git.behind, 'clean': git.isClean, 'hasConflicts': git.hasConflicts, - 'counts': { - 'staged': git.staged.length, - 'unstaged': git.unstaged.length, - 'untracked': git.untracked.length, - 'conflicted': git.conflicted.length, - }, + 'counts': {'staged': git.staged.length, 'unstaged': git.unstaged.length, 'untracked': git.untracked.length, 'conflicted': git.conflicted.length}, }; } catch (_) { gitJson = null; // never sink the snapshot on a git hiccup @@ -289,12 +261,7 @@ Future main() async { 'git': gitJson, 'editor': editorActive == null ? null - : { - 'id': editorActive.id, - 'path': editorActive.path, - 'selection': editorActive.selection.toJson(), - 'dirty': editorActive.dirty, - }, + : {'id': editorActive.id, 'path': editorActive.path, 'selection': editorActive.selection.toJson(), 'dirty': editorActive.dirty}, 'readers': kernelReaderNav?.currentByReader ?? const {}, 'focusedFile': editorActive?.path, 'panes': [for (final v in snapshotViewPanes(panels, arrangement)) v.toJson()], @@ -337,11 +304,7 @@ Future main() async { // client will then auto-connect to via its reconnect // loop. autoStartDaemonClient:false means we own the // lifecycle here. - final client = DaemonClient( - socketPath: workspaceSocketPath(workRoot.path), - log: log, - events: events, - ); + final client = DaemonClient(socketPath: workspaceSocketPath(workRoot.path), log: log, events: events); ipcClient = client; // start() synchronously marks the client "connecting" (so // requests issued during the startup window park for the @@ -436,12 +399,7 @@ class _BusEventSink implements DaemonEventSink { @override void emit(IpcEvent event) { - _bus.emit(DaemonEvent( - subsystem: event.subsystem, - kind: event.kind, - data: event.data, - ts: DateTime.now(), - )); + _bus.emit(DaemonEvent(subsystem: event.subsystem, kind: event.kind, data: event.data, ts: DateTime.now())); } } diff --git a/lib/src/cli/argv_dispatch.dart b/lib/src/cli/argv_dispatch.dart index e8a2a38d..48a346e4 100644 --- a/lib/src/cli/argv_dispatch.dart +++ b/lib/src/cli/argv_dispatch.dart @@ -29,14 +29,12 @@ const String argvSentinelCmd = '_argv'; ArgvParseResult unwrapArgvRequest(IpcRequest outer) { final raw = outer.args['argv']; if (raw is! List) { - return ArgvError(IpcResponse.err( - id: outer.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: '_argv requires args.argv to be a JSON array', + return ArgvError( + IpcResponse.err( + id: outer.id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: '_argv requires args.argv to be a JSON array'), ), - )); + ); } return parseArgv(raw.cast(), requestId: outer.id); } diff --git a/lib/src/cli/argv_to_request.dart b/lib/src/cli/argv_to_request.dart index 225cd0f6..cf388269 100644 --- a/lib/src/cli/argv_to_request.dart +++ b/lib/src/cli/argv_to_request.dart @@ -73,11 +73,7 @@ ArgvParseResult parseArgv(List argv, {required String requestId}) { if (parsed is _TailError) { return ArgvError(_err(requestId, parsed.message)); } - return ArgvParsed(IpcRequest( - id: requestId, - cmd: first, - args: (parsed as _TailOk).toArgs(), - )); + return ArgvParsed(IpcRequest(id: requestId, cmd: first, args: (parsed as _TailOk).toArgs())); } // Subsystem.verb form: need at least two tokens. @@ -97,11 +93,7 @@ ArgvParseResult parseArgv(List argv, {required String requestId}) { if (parsed is _TailError) { return ArgvError(_err(requestId, parsed.message)); } - return ArgvParsed(IpcRequest( - id: requestId, - cmd: '$subsystem.$verb', - args: (parsed as _TailOk).toArgs(), - )); + return ArgvParsed(IpcRequest(id: requestId, cmd: '$subsystem.$verb', args: (parsed as _TailOk).toArgs())); } /// Route a `clide://` deep link to the `deeplink.invoke` command (T-56). A @@ -109,9 +101,15 @@ ArgvParseResult parseArgv(List argv, {required String requestId}) { /// it is NOT translated into a command here. The raw URL is handed to the /// deeplink handler, which validates it against a paranoid (default-deny) /// allowlist and prompts the user before doing anything (D-90). -ArgvParseResult _deepLinkToRequest(String url, String requestId) => ArgvParsed(IpcRequest(id: requestId, cmd: 'deeplink.invoke', args: { - 'positional': [url] - })); +ArgvParseResult _deepLinkToRequest(String url, String requestId) => ArgvParsed( + IpcRequest( + id: requestId, + cmd: 'deeplink.invoke', + args: { + 'positional': [url], + }, + ), +); // -- internals -------------------------------------------------------------- @@ -126,10 +124,10 @@ class _TailOk extends _TailParseResult { final List passthrough; Map toArgs() => { - if (positional.isNotEmpty) 'positional': positional, - if (flags.isNotEmpty) 'flags': flags, - if (passthrough.isNotEmpty) 'passthrough': passthrough, - }; + if (positional.isNotEmpty) 'positional': positional, + if (flags.isNotEmpty) 'flags': flags, + if (passthrough.isNotEmpty) 'passthrough': passthrough, + }; } class _TailError extends _TailParseResult { @@ -221,10 +219,6 @@ bool _isValidIdentifier(String s) { bool _isValidFlagName(String s) => _isValidIdentifier(s); IpcResponse _err(String id, String message) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: message, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message), +); diff --git a/lib/src/daemon/dispatcher.dart b/lib/src/daemon/dispatcher.dart index 577706ff..107746b6 100644 --- a/lib/src/daemon/dispatcher.dart +++ b/lib/src/daemon/dispatcher.dart @@ -77,19 +77,10 @@ class DaemonDispatcher { return h(IpcRequest(id: req.id, cmd: req.cmd, args: result.values!)); } - Future _ping(IpcRequest req) async => IpcResponse.ok( - id: req.id, - data: { - 'pong': true, - 'ts': DateTime.now().toUtc().toIso8601String(), - 'version': clideVersion, - }, - ); + Future _ping(IpcRequest req) async => + IpcResponse.ok(id: req.id, data: {'pong': true, 'ts': DateTime.now().toUtc().toIso8601String(), 'version': clideVersion}); - Future _version(IpcRequest req) async => IpcResponse.ok( - id: req.id, - data: {'version': clideVersion}, - ); + Future _version(IpcRequest req) async => IpcResponse.ok(id: req.id, data: {'version': clideVersion}); /// Reflects the live command registry so the surface is discoverable, not /// just present (T-248). Every registered verb is listed — split into @@ -139,11 +130,7 @@ class DaemonDispatcher { tools.add({ 'name': '$prefix$cmd', 'description': subsystem.isEmpty ? verb : '$subsystem: $verb', - 'inputSchema': { - 'type': 'object', - 'properties': props, - if (required.isNotEmpty) 'required': required, - }, + 'inputSchema': {'type': 'object', 'properties': props, if (required.isNotEmpty) 'required': required}, }); } return tools; @@ -153,17 +140,9 @@ class DaemonDispatcher { static Map _argJsonSchema(ArgSpec s) { switch (s.type) { case ArgType.string: - return { - 'type': 'string', - if (s.allowed != null) 'enum': (s.allowed!.toList()..sort()), - if (s.pattern != null) 'pattern': s.pattern!.pattern, - }; + return {'type': 'string', if (s.allowed != null) 'enum': (s.allowed!.toList()..sort()), if (s.pattern != null) 'pattern': s.pattern!.pattern}; case ArgType.number: - return { - 'type': 'number', - if (s.min != null) 'minimum': s.min, - if (s.max != null) 'maximum': s.max, - }; + return {'type': 'number', if (s.min != null) 'minimum': s.min, if (s.max != null) 'maximum': s.max}; case ArgType.boolean: return {'type': 'boolean'}; case ArgType.stringList: @@ -176,13 +155,13 @@ class DaemonDispatcher { } static Map _argSpecJson(ArgSpec s) => { - 'type': s.type.name, - if (s.required) 'required': true, - if (s.allowed != null) 'allowed': (s.allowed!.toList()..sort()), - if (s.pattern != null) 'pattern': s.pattern!.pattern, - if (s.min != null) 'min': s.min, - if (s.max != null) 'max': s.max, - if (s.maxItems != null) 'maxItems': s.maxItems, - if (s.rejectLeadingDash) 'rejectLeadingDash': true, - }; + 'type': s.type.name, + if (s.required) 'required': true, + if (s.allowed != null) 'allowed': (s.allowed!.toList()..sort()), + if (s.pattern != null) 'pattern': s.pattern!.pattern, + if (s.min != null) 'min': s.min, + if (s.max != null) 'max': s.max, + if (s.maxItems != null) 'maxItems': s.maxItems, + if (s.rejectLeadingDash) 'rejectLeadingDash': true, + }; } diff --git a/lib/src/daemon/editor_commands.dart b/lib/src/daemon/editor_commands.dart index 6810c3b8..f107f38c 100644 --- a/lib/src/daemon/editor_commands.dart +++ b/lib/src/daemon/editor_commands.dart @@ -28,8 +28,17 @@ export '../editor/buffer.dart' show Selection; const _idArg = CommandSchema(positional: ['id'], args: {'id': ArgSpec()}); void registerEditorCommands(DaemonDispatcher d, EditorRegistry registry) { - d.register('editor.open', (req) => _open(req, registry), - schema: const CommandSchema(positional: ['path', 'line'], args: {'path': ArgSpec(), 'line': ArgSpec(type: ArgType.number)})); + d.register( + 'editor.open', + (req) => _open(req, registry), + schema: const CommandSchema( + positional: ['path', 'line'], + args: { + 'path': ArgSpec(), + 'line': ArgSpec(type: ArgType.number), + }, + ), + ); d.register('editor.active', (req) => _active(req, registry)); d.register('editor.activate', (req) => _activate(req, registry), schema: _idArg); d.register('editor.list', (req) => _list(req, registry)); @@ -43,23 +52,14 @@ void registerEditorCommands(DaemonDispatcher d, EditorRegistry registry) { } IpcResponse _userErr(String id, String msg, {String? hint}) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: msg, - hint: hint, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: msg, hint: hint), +); IpcResponse _notFound(String id, String msg) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: msg, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: msg), +); String? _resolveId(IpcRequest req, EditorRegistry r) { final id = req.args['id'] as String?; @@ -94,20 +94,12 @@ Future _open(IpcRequest req, EditorRegistry r) async { } return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'editor.open failed: ${e.message}', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'editor.open failed: ${e.message}'), ); } catch (e) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'editor.open failed: $e', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'editor.open failed: $e'), ); } } @@ -145,7 +137,7 @@ Future _list(IpcRequest req, EditorRegistry r) async { return IpcResponse.ok( id: req.id, data: { - 'buffers': [for (final b in r.buffers) b.toJson()] + 'buffers': [for (final b in r.buffers) b.toJson()], }, ); } diff --git a/lib/src/daemon/files_commands.dart b/lib/src/daemon/files_commands.dart index 4c25a1f9..b20bb742 100644 --- a/lib/src/daemon/files_commands.dart +++ b/lib/src/daemon/files_commands.dart @@ -28,12 +28,7 @@ const int _filesReadMaxBytes = 10 * 1024 * 1024; /// Daemon-side state for the `files` subsystem. Holds one /// [FileWatcher] rooted at the workspace and a resolved [IgnoreSet]. class FilesService { - FilesService({ - required this.root, - required this.events, - IgnoreSet? ignore, - this.extraReadRoots = const [], - }) : ignore = ignore ?? _defaultIgnore(root); + FilesService({required this.root, required this.events, IgnoreSet? ignore, this.extraReadRoots = const []}) : ignore = ignore ?? _defaultIgnore(root); /// Build from the current working directory, walking up to the git /// root if present. Falls back to CWD otherwise. @@ -58,12 +53,7 @@ class FilesService { _watcher = w; await w.start(); w.stream.listen((change) { - events.emit(IpcEvent( - subsystem: 'files', - kind: 'files.changed', - timestamp: DateTime.now().toUtc(), - data: change.toJson(), - )); + events.emit(IpcEvent(subsystem: 'files', kind: 'files.changed', timestamp: DateTime.now().toUtc(), data: change.toJson())); }); } @@ -74,20 +64,15 @@ class FilesService { } void registerFilesCommands(DaemonDispatcher d, FilesService files) { - d.register( - 'files.root', - (req) async => IpcResponse.ok( - id: req.id, - data: { - 'path': files.root.absolute.path, - 'ignorePatterns': files.ignore.length, - }, - )); + d.register('files.root', (req) async => IpcResponse.ok(id: req.id, data: {'path': files.root.absolute.path, 'ignorePatterns': files.ignore.length})); d.register('files.read', (req) async { final path = req.args['path'] as String?; if (path == null || path.isEmpty) { - return IpcResponse.err(id: req.id, error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'files.read requires a path')); + return IpcResponse.err( + id: req.id, + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'files.read requires a path'), + ); } final String absPath; try { @@ -96,11 +81,17 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) { // the trusted extra read roots (Claude config dirs, D-80). absPath = resolveUnderRootsFollowingSymlinks(files.root, files.extraReadRoots, path); } on PathOutsideRoot { - return IpcResponse.err(id: req.id, error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'path outside workspace: $path')); + return IpcResponse.err( + id: req.id, + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'path outside workspace: $path'), + ); } final file = File(absPath); if (!file.existsSync()) { - return IpcResponse.err(id: req.id, error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'file not found: $path')); + return IpcResponse.err( + id: req.id, + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'file not found: $path'), + ); } // Cap response size so a single IPC call can't OOM the UI on a // multi-gigabyte log file. Caller can paginate / stream via a @@ -109,11 +100,7 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) { if (length > _filesReadMaxBytes) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'file too large: $path ($length bytes; cap $_filesReadMaxBytes)', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'file too large: $path ($length bytes; cap $_filesReadMaxBytes)'), ); } final content = file.readAsStringSync(); @@ -126,14 +113,13 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) { try { resolveUnderRootFollowingSymlinks(files.root, dir); } on PathOutsideRoot { - return IpcResponse.err(id: req.id, error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'path outside workspace: $dir')); + return IpcResponse.err( + id: req.id, + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'path outside workspace: $dir'), + ); } } - final entries = await listDir( - root: files.root, - dir: dir, - ignore: files.ignore, - ); + final entries = await listDir(root: files.root, dir: dir, ignore: files.ignore); return IpcResponse.ok( id: req.id, data: { @@ -156,10 +142,7 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) { d.register('files.watch', (req) async { await files.startWatching(); - return IpcResponse.ok( - id: req.id, - data: const {'subscribed': true}, - ); + return IpcResponse.ok(id: req.id, data: const {'subscribed': true}); }); } @@ -192,8 +175,5 @@ IgnoreSet _defaultIgnore(Directory root) { // Merge: built-in patterns first, user patterns last. "Last match // wins" semantics give the user the ability to un-ignore via `!` // in a future extension of the matcher. - return IgnoreSet([ - ...IgnoreSet.builtin().patterns, - ...user.patterns, - ]); + return IgnoreSet([...IgnoreSet.builtin().patterns, ...user.patterns]); } diff --git a/lib/src/daemon/git_commands.dart b/lib/src/daemon/git_commands.dart index 59653b1c..d545f8eb 100644 --- a/lib/src/daemon/git_commands.dart +++ b/lib/src/daemon/git_commands.dart @@ -28,12 +28,7 @@ const int _gitPathsMaxCount = 256; /// handler; the leading-dash guard stops argv injection at the /// dispatcher (mirrors [validateGitRef], kept below as /// defense-in-depth since the git client is also UI-reachable). -const CommandSchema _checkoutSchema = CommandSchema( - positional: ['branch'], - args: { - 'branch': ArgSpec(required: true, rejectLeadingDash: true), - }, -); +const CommandSchema _checkoutSchema = CommandSchema(positional: ['branch'], args: {'branch': ArgSpec(required: true, rejectLeadingDash: true)}); /// Schema for `git.push` (D-74). `clide git push `. /// Both refs optional (bare `git.push` is valid); leading-dash @@ -47,11 +42,7 @@ const CommandSchema _pushSchema = CommandSchema( }, ); -void registerGitCommands( - DaemonDispatcher d, - GitClient git, - DaemonEventSink events, -) { +void registerGitCommands(DaemonDispatcher d, GitClient git, DaemonEventSink events) { d.register('git.status', (req) async { try { final status = await git.status(); @@ -68,10 +59,13 @@ void registerGitCommands( final tooMany = _tooManyPaths(req.id, paths); if (tooMany != null) return tooMany; final diffs = await git.diff(staged: staged, paths: paths); - return IpcResponse.ok(id: req.id, data: { - 'staged': staged, - 'diffs': [for (final d in diffs) d.toJson()], - }); + return IpcResponse.ok( + id: req.id, + data: { + 'staged': staged, + 'diffs': [for (final d in diffs) d.toJson()], + }, + ); } on GitException catch (e) { return _gitError(req.id, e); } @@ -82,12 +76,7 @@ void registerGitCommands( if (paths.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.stage requires paths', - hint: 'pass {paths: ["file.txt"]}', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.stage requires paths', hint: 'pass {paths: ["file.txt"]}'), ); } final tooMany = _tooManyPaths(req.id, paths); @@ -127,11 +116,7 @@ void registerGitCommands( if (patch == null || patch.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.stage-hunk requires a patch', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.stage-hunk requires a patch'), ); } try { @@ -148,11 +133,7 @@ void registerGitCommands( if (patch == null || patch.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.unstage-hunk requires a patch', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.unstage-hunk requires a patch'), ); } try { @@ -169,11 +150,7 @@ void registerGitCommands( if (paths.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.discard requires paths', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.discard requires paths'), ); } try { @@ -190,11 +167,7 @@ void registerGitCommands( if (message == null || message.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.commit requires a message', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.commit requires a message'), ); } try { @@ -233,18 +206,17 @@ void registerGitCommands( if (count > _gitLogMaxCount) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.log count $count exceeds cap $_gitLogMaxCount', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.log count $count exceeds cap $_gitLogMaxCount'), ); } try { final entries = await git.log(count: count); - return IpcResponse.ok(id: req.id, data: { - 'entries': [for (final e in entries) e.toJson()], - }); + return IpcResponse.ok( + id: req.id, + data: { + 'entries': [for (final e in entries) e.toJson()], + }, + ); } on GitException catch (e) { return _gitError(req.id, e); } @@ -275,11 +247,14 @@ void registerGitCommands( d.register('git.branches', (req) async { try { final b = await git.branches(); - return IpcResponse.ok(id: req.id, data: { - 'branches': [ - for (final e in b) {'name': e.name, 'current': e.current} - ], - }); + return IpcResponse.ok( + id: req.id, + data: { + 'branches': [ + for (final e in b) {'name': e.name, 'current': e.current}, + ], + }, + ); } on GitException catch (e) { return _gitError(req.id, e); } @@ -290,11 +265,7 @@ void registerGitCommands( if (branch == null || branch.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'git.checkout requires a branch', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'git.checkout requires a branch'), ); } try { @@ -317,31 +288,17 @@ IpcResponse? _tooManyPaths(String id, List paths) { if (paths.length <= _gitPathsMaxCount) return null; return IpcResponse.err( id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'paths length ${paths.length} exceeds cap $_gitPathsMaxCount', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'paths length ${paths.length} exceeds cap $_gitPathsMaxCount'), ); } void _emitChanged(DaemonEventSink events) { - events.emit(IpcEvent( - subsystem: 'git', - kind: 'git.changed', - timestamp: DateTime.now().toUtc(), - data: const {}, - )); + events.emit(IpcEvent(subsystem: 'git', kind: 'git.changed', timestamp: DateTime.now().toUtc(), data: const {})); } IpcResponse _gitError(String id, GitException e) { return IpcResponse.err( id: id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: e.message, - hint: e.stderr.isNotEmpty ? e.stderr : null, - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: e.message, hint: e.stderr.isNotEmpty ? e.stderr : null), ); } diff --git a/lib/src/daemon/image_commands.dart b/lib/src/daemon/image_commands.dart index b9028708..40de1195 100644 --- a/lib/src/daemon/image_commands.dart +++ b/lib/src/daemon/image_commands.dart @@ -35,11 +35,7 @@ typedef ImagePathResolver = String? Function(String path); /// publisher so both ends point at one name. const imageShowChannel = 'image'; -void registerImageCommands( - DaemonDispatcher d, - MessagePublisher? Function() publisher, { - ImagePathResolver? resolve, -}) { +void registerImageCommands(DaemonDispatcher d, MessagePublisher? Function() publisher, {ImagePathResolver? resolve}) { d.register( 'image.show', (req) async => _show(req, publisher, resolve), @@ -55,15 +51,11 @@ void registerImageCommands( } IpcResponse _userErr(String id, String message, {String? hint}) => IpcResponse.err( - id: id, - error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), +); -Future _show( - IpcRequest req, - MessagePublisher? Function() publisherSource, - ImagePathResolver? resolve, -) async { +Future _show(IpcRequest req, MessagePublisher? Function() publisherSource, ImagePathResolver? resolve) async { final path = req.args['path'] as String?; if (path == null || path.trim().isEmpty) { return _userErr(req.id, 'an image path is required (e.g. `image show docs/diagram.png`)'); @@ -71,11 +63,7 @@ Future _show( final ext = _extensionOf(path); if (!imageShowExtensions.contains(ext)) { - return _userErr( - req.id, - 'unsupported image format${ext.isEmpty ? '' : ' ".$ext"'}', - hint: 'one of: ${(imageShowExtensions.toList()..sort()).join(', ')}', - ); + return _userErr(req.id, 'unsupported image format${ext.isEmpty ? '' : ' ".$ext"'}', hint: 'one of: ${(imageShowExtensions.toList()..sort()).join(', ')}'); } // Resolve to a concrete file before publishing, so the CLI fails honestly on @@ -114,7 +102,7 @@ Future _show( if (caption != null && caption.trim().isNotEmpty) 'caption': caption.trim(), if (fullscreen) 'fullscreen': true, }); - return IpcResponse.ok(id: req.id, data: {'path': resolved, if (caption != null) 'caption': caption, 'fullscreen': fullscreen, 'shown': true}); + return IpcResponse.ok(id: req.id, data: {'path': resolved, 'caption': ?caption, 'fullscreen': fullscreen, 'shown': true}); } /// Lower-cased extension (without the dot) of [path], or '' if none. diff --git a/lib/src/daemon/pane_commands.dart b/lib/src/daemon/pane_commands.dart index d613e670..88ccc210 100644 --- a/lib/src/daemon/pane_commands.dart +++ b/lib/src/daemon/pane_commands.dart @@ -36,10 +36,23 @@ void registerPaneCommands(DaemonDispatcher d, PaneRegistry registry, {ViewPaneSo d.register('pane.spawn', (req) => _spawn(req, registry)); d.register('pane.list', (req) => _list(req, registry, viewPanes)); d.register('pane.close', (req) => _close(req, registry), schema: idArg); - d.register('pane.write', (req) => _write(req, registry), schema: const CommandSchema(positional: ['id', 'text'], args: {'id': ArgSpec(), 'text': ArgSpec()})); - d.register('pane.resize', (req) => _resize(req, registry), - schema: const CommandSchema( - positional: ['id', 'cols', 'rows'], args: {'id': ArgSpec(), 'cols': ArgSpec(type: ArgType.number), 'rows': ArgSpec(type: ArgType.number)})); + d.register( + 'pane.write', + (req) => _write(req, registry), + schema: const CommandSchema(positional: ['id', 'text'], args: {'id': ArgSpec(), 'text': ArgSpec()}), + ); + d.register( + 'pane.resize', + (req) => _resize(req, registry), + schema: const CommandSchema( + positional: ['id', 'cols', 'rows'], + args: { + 'id': ArgSpec(), + 'cols': ArgSpec(type: ArgType.number), + 'rows': ArgSpec(type: ArgType.number), + }, + ), + ); d.register('pane.focus', (req) => _focus(req, registry), schema: idArg); // pane.tail is a streaming/no-op verb (events arrive via the tail stream), // a poor request/response MCP tool — keep it off the MCP surface (D-86). @@ -47,23 +60,14 @@ void registerPaneCommands(DaemonDispatcher d, PaneRegistry registry, {ViewPaneSo } IpcResponse _userErr(String id, String message, {String? hint}) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: message, - hint: hint, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), +); IpcResponse _notFound(String id, String message) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: message, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: message), +); Future _spawn(IpcRequest req, PaneRegistry registry) async { final args = req.args; @@ -87,9 +91,7 @@ Future _spawn(IpcRequest req, PaneRegistry registry) async { final envArg = args['env']; Map? env; if (envArg is Map) { - env = { - for (final e in envArg.entries) '${e.key}': '${e.value}', - }; + env = {for (final e in envArg.entries) '${e.key}': '${e.value}'}; } try { @@ -108,29 +110,17 @@ Future _spawn(IpcRequest req, PaneRegistry registry) async { if (errno != null) { return IpcResponse.err( id: req.id, - error: errnoToIpcError( - errno: errno, - op: 'pane.spawn', - target: argv.isNotEmpty ? argv.first : null, - ), + error: errnoToIpcError(errno: errno, op: 'pane.spawn', target: argv.isNotEmpty ? argv.first : null), ); } return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'pane.spawn failed: ${e.message}', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'pane.spawn failed: ${e.message}'), ); } catch (e) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'pane.spawn failed: $e', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'pane.spawn failed: $e'), ); } } @@ -143,7 +133,7 @@ Future _list(IpcRequest req, PaneRegistry registry, ViewPaneSource? for (final p in registry.panes) p.toJson(), if (viewPanes != null) for (final v in viewPanes()) v.toJson(), - ] + ], }, ); } @@ -198,12 +188,7 @@ Future _focus(IpcRequest req, PaneRegistry registry) async { if (registry.get(id) == null) return _notFound(req.id, 'no such pane: $id'); // Focus is advisory on the daemon side — UIs track their own focus // state. We just emit the event so subscribers know what changed. - registry.events.emit(IpcEvent( - subsystem: 'pane', - kind: 'pane.focused', - timestamp: DateTime.now().toUtc(), - data: {'id': id}, - )); + registry.events.emit(IpcEvent(subsystem: 'pane', kind: 'pane.focused', timestamp: DateTime.now().toUtc(), data: {'id': id})); return IpcResponse.ok(id: req.id, data: {'id': id}); } diff --git a/lib/src/daemon/panel_commands.dart b/lib/src/daemon/panel_commands.dart index 72871f5e..9665bb4a 100644 --- a/lib/src/daemon/panel_commands.dart +++ b/lib/src/daemon/panel_commands.dart @@ -86,37 +86,22 @@ Future _resize(IpcRequest req, PanelResizer r) async { } else { r.bumpEditorRatio(value); } - return IpcResponse.ok(id: req.id, data: { - 'slot': slot, - 'ratio': r.currentEditorRatio, - }); + return IpcResponse.ok(id: req.id, data: {'slot': slot, 'ratio': r.currentEditorRatio}); } final ok = hasTo ? r.setSlotSize(slot, value) : r.bumpSlotSize(slot, value); if (!ok) { return _notFound(req.id, 'no such slot: $slot'); } - return IpcResponse.ok(id: req.id, data: { - 'slot': slot, - 'size': r.currentSlotSize(slot), - }); + return IpcResponse.ok(id: req.id, data: {'slot': slot, 'size': r.currentSlotSize(slot)}); } IpcResponse _userErr(String id, String message, {String? hint}) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: message, - hint: hint, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), +); IpcResponse _notFound(String id, String message) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: message, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: message), +); diff --git a/lib/src/daemon/pql_commands.dart b/lib/src/daemon/pql_commands.dart index cd3fb02d..1cc7d17b 100644 --- a/lib/src/daemon/pql_commands.dart +++ b/lib/src/daemon/pql_commands.dart @@ -128,11 +128,7 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { d.register('pql.decisions.list', (req) async { try { - final results = await pql.decisionList( - type: req.args['type'] as String?, - domain: req.args['domain'] as String?, - status: req.args['status'] as String?, - ); + final results = await pql.decisionList(type: req.args['type'] as String?, domain: req.args['domain'] as String?, status: req.args['status'] as String?); return IpcResponse.ok(id: req.id, data: {'decisions': results}); } on PqlException catch (e) { return _pqlError(req.id, e); @@ -158,11 +154,7 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { return _userError(req.id, 'pql.decisions.show requires an id'); } try { - final result = await pql.decisionShow( - id, - withRefs: req.args['withRefs'] as bool? ?? false, - withTickets: req.args['withTickets'] as bool? ?? false, - ); + final result = await pql.decisionShow(id, withRefs: req.args['withRefs'] as bool? ?? false, withTickets: req.args['withTickets'] as bool? ?? false); return IpcResponse.ok(id: req.id, data: result); } on PqlException catch (e) { return _pqlError(req.id, e); @@ -189,11 +181,7 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { return _userError(req.id, 'pql.tickets.show requires an id'); } try { - final result = await pql.ticketShow( - id, - withContext: req.args['withContext'] as bool? ?? false, - withBlockers: req.args['withBlockers'] as bool? ?? false, - ); + final result = await pql.ticketShow(id, withContext: req.args['withContext'] as bool? ?? false, withBlockers: req.args['withBlockers'] as bool? ?? false); return IpcResponse.ok(id: req.id, data: result); } on PqlException catch (e) { return _pqlError(req.id, e); @@ -205,8 +193,8 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { final ids = rawIds is List ? rawIds.cast() : rawIds is String - ? [rawIds] - : []; + ? [rawIds] + : []; final status = req.args['status'] as String?; if (ids.isEmpty || status == null || status.isEmpty) { return _userError(req.id, 'pql.tickets.status requires ids and status'); @@ -221,9 +209,7 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { d.register('pql.tickets.board', (req) async { try { - final board = await pql.ticketBoard( - team: req.args['team'] as String?, - ); + final board = await pql.ticketBoard(team: req.args['team'] as String?); return IpcResponse.ok(id: req.id, data: {'columns': board}); } on PqlException catch (e) { return _pqlError(req.id, e); @@ -243,22 +229,13 @@ void registerPqlCommands(DaemonDispatcher d, PqlClient pql) { IpcResponse _userError(String id, String message) { return IpcResponse.err( id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: message, - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message), ); } IpcResponse _pqlError(String id, PqlException e) { return IpcResponse.err( id: id, - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: e.message, - hint: e.stderr.isNotEmpty ? e.stderr : null, - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: e.message, hint: e.stderr.isNotEmpty ? e.stderr : null), ); } diff --git a/lib/src/daemon/search_commands.dart b/lib/src/daemon/search_commands.dart index 6d45f391..746de189 100644 --- a/lib/src/daemon/search_commands.dart +++ b/lib/src/daemon/search_commands.dart @@ -25,12 +25,7 @@ import 'dispatcher.dart'; /// sink. One instance per workspace, constructed alongside the other /// daemon services. class SearchService { - SearchService({ - required this.root, - required this.ignore, - required this.events, - this.useIsolates = true, - }); + SearchService({required this.root, required this.ignore, required this.events, this.useIsolates = true}); final Directory root; final IgnoreSet ignore; @@ -64,12 +59,7 @@ class SearchService { /// path-safety guard. The clean-git-tree safety gate is enforced by /// the caller (the UI checks `git.status` before requesting apply). Future> replace(SearchQuery query, String replacement, {required bool apply}) async { - final files = await computeReplacements( - root: root, - ignore: ignore, - query: query, - replacement: replacement, - ); + final files = await computeReplacements(root: root, ignore: ignore, query: query, replacement: replacement); if (!apply) { return { 'apply': false, @@ -103,13 +93,7 @@ class SearchService { Future _run(String id, SearchQuery query, CancelToken cancel) async { try { - await for (final batch in grepWorkspace( - root: root, - ignore: ignore, - query: query, - cancel: cancel, - useIsolates: useIsolates, - )) { + await for (final batch in grepWorkspace(root: root, ignore: ignore, query: query, cancel: cancel, useIsolates: useIsolates)) { if (cancel.isCancelled) break; _emit('search.match', { 'searchId': id, @@ -127,12 +111,7 @@ class SearchService { } void _emit(String kind, Map data) { - events.emit(IpcEvent( - subsystem: 'search', - kind: kind, - timestamp: DateTime.now().toUtc(), - data: data, - )); + events.emit(IpcEvent(subsystem: 'search', kind: kind, timestamp: DateTime.now().toUtc(), data: data)); } } @@ -166,11 +145,7 @@ void registerSearchCommands(DaemonDispatcher d, SearchService search) { if (query.pattern.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'search.grep requires a non-empty pattern', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'search.grep requires a non-empty pattern'), ); } final id = search.start(query); @@ -184,11 +159,7 @@ void registerSearchCommands(DaemonDispatcher d, SearchService search) { if (query.pattern.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'search.replace requires a non-empty pattern', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'search.replace requires a non-empty pattern'), ); } try { @@ -197,11 +168,7 @@ void registerSearchCommands(DaemonDispatcher d, SearchService search) { } on FormatException catch (e) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'invalid regex: ${e.message}', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'invalid regex: ${e.message}'), ); } }, schema: _replaceSchema); @@ -211,11 +178,7 @@ void registerSearchCommands(DaemonDispatcher d, SearchService search) { if (id == null || id.isEmpty) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'search.cancel requires a searchId', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'search.cancel requires a searchId'), ); } search.cancel(id); diff --git a/lib/src/daemon/ui_command.dart b/lib/src/daemon/ui_command.dart index f44d19a2..16b76fb6 100644 --- a/lib/src/daemon/ui_command.dart +++ b/lib/src/daemon/ui_command.dart @@ -47,27 +47,20 @@ const Set _toastSeverities = {'success', 'warning', 'error', 'info'}; /// nothing has reported a value for that address (or there is no live UI). typedef FilterValueSource = String? Function(String address); -void registerUiCommands( - DaemonDispatcher d, - MessagePublisher? Function() publisher, { - FilterValueSource? filterValue, -}) { +void registerUiCommands(DaemonDispatcher d, MessagePublisher? Function() publisher, {FilterValueSource? filterValue}) { d.register('ui.open', (req) async => _open(req, publisher)); d.register('ui.toast', (req) async => _toast(req, publisher)); d.register( 'ui.filter', (req) async => _filter(req, publisher, filterValue), - schema: const CommandSchema( - positional: ['address', 'query'], - args: {'address': ArgSpec(required: true), 'query': ArgSpec()}, - ), + schema: const CommandSchema(positional: ['address', 'query'], args: {'address': ArgSpec(required: true), 'query': ArgSpec()}), ); } IpcResponse _userErr(String id, String message, {String? hint}) => IpcResponse.err( - id: id, - error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message, hint: hint), +); Future _open(IpcRequest req, MessagePublisher? Function() publisherSource) async { // Accept CLI positionals (`ui open `) or named args. @@ -134,11 +127,7 @@ Future _toast(IpcRequest req, MessagePublisher? Function() publishe ); } // Channel literal must match ToastService's `toastChannel`. - publish('cli', 'toast', { - 'message': message, - 'severity': severity, - if (durationMs != null) 'durationMs': durationMs, - }); + publish('cli', 'toast', {'message': message, 'severity': severity, 'durationMs': ?durationMs}); return IpcResponse.ok(id: req.id, data: {'message': message, 'severity': severity, 'shown': true}); } @@ -154,11 +143,7 @@ Future _toast(IpcRequest req, MessagePublisher? Function() publishe /// `address` is a pane/box id from `clide pane list`. With a `query` arg the /// verb *drives* — publishes a `filter.set` the box consumes. Without one it /// *observes* — reads the box's last reported value from the FilterStateCache. -Future _filter( - IpcRequest req, - MessagePublisher? Function() publisherSource, - FilterValueSource? filterValue, -) async { +Future _filter(IpcRequest req, MessagePublisher? Function() publisherSource, FilterValueSource? filterValue) async { final address = req.args['address'] as String?; if (address == null || address.isEmpty) { return _userErr(req.id, 'an address is required', hint: 'a pane/box id from `clide pane list` (e.g. decisions.panel)'); diff --git a/lib/src/editor/buffer.dart b/lib/src/editor/buffer.dart index 1c7fd725..5b532c58 100644 --- a/lib/src/editor/buffer.dart +++ b/lib/src/editor/buffer.dart @@ -11,9 +11,7 @@ import 'editor_settings.dart'; class Selection { const Selection({required this.start, required this.end}); - const Selection.collapsed(int offset) - : start = offset, - end = offset; + const Selection.collapsed(int offset) : start = offset, end = offset; final int start; final int end; @@ -23,10 +21,7 @@ class Selection { Map toJson() => {'start': start, 'end': end}; - factory Selection.fromJson(Map j) => Selection( - start: (j['start'] as num).toInt(), - end: (j['end'] as num).toInt(), - ); + factory Selection.fromJson(Map j) => Selection(start: (j['start'] as num).toInt(), end: (j['end'] as num).toInt()); @override bool operator ==(Object other) => other is Selection && other.start == start && other.end == end; @@ -39,14 +34,8 @@ class Selection { } class EditorBuffer { - EditorBuffer({ - required this.id, - required this.path, - required this.content, - Selection? selection, - this.dirty = false, - this.settings = EditorSettings.empty, - }) : selection = selection ?? const Selection.collapsed(0); + EditorBuffer({required this.id, required this.path, required this.content, Selection? selection, this.dirty = false, this.settings = EditorSettings.empty}) + : selection = selection ?? const Selection.collapsed(0); /// Stable daemon-local id (`b_1`, `b_2`, …). final String id; @@ -77,18 +66,15 @@ class EditorBuffer { EditorSettings settings; Map toJson() => { - 'id': id, - 'path': path, - 'length': content.length, - 'selection': selection.toJson(), - 'dirty': dirty, - 'editorSettings': settings.toJson(), - }; + 'id': id, + 'path': path, + 'length': content.length, + 'selection': selection.toJson(), + 'dirty': dirty, + 'editorSettings': settings.toJson(), + }; /// Full snapshot including [content] — for `editor.read` / tests / /// anything that needs the text explicitly. - Map toFullJson() => { - ...toJson(), - 'content': content, - }; + Map toFullJson() => {...toJson(), 'content': content}; } diff --git a/lib/src/editor/editor_settings.dart b/lib/src/editor/editor_settings.dart index a7a5b223..7f3ba2cc 100644 --- a/lib/src/editor/editor_settings.dart +++ b/lib/src/editor/editor_settings.dart @@ -54,11 +54,11 @@ class EditorSettings { /// The line terminator [endOfLine] names, or null when unset. String? get eolString => switch (endOfLine) { - 'lf' => '\n', - 'crlf' => '\r\n', - 'cr' => '\r', - _ => null, - }; + 'lf' => '\n', + 'crlf' => '\r\n', + 'cr' => '\r', + _ => null, + }; /// The text one Tab press inserts, or null to keep the editor's default /// (Flutter's focus traversal) — the editor only takes over Tab when a source @@ -75,25 +75,25 @@ class EditorSettings { /// first) lives in the resolver — a higher-precedence source (settings panel, /// clide settings file) merges over a lower one (.editorconfig). EditorSettings merge(EditorSettings other) => EditorSettings( - indentStyle: other.indentStyle ?? indentStyle, - indentSize: other.indentSize ?? indentSize, - tabWidth: other.tabWidth ?? tabWidth, - endOfLine: other.endOfLine ?? endOfLine, - maxLineLength: other.maxLineLength ?? maxLineLength, - trimTrailingWhitespace: other.trimTrailingWhitespace ?? trimTrailingWhitespace, - insertFinalNewline: other.insertFinalNewline ?? insertFinalNewline, - ); + indentStyle: other.indentStyle ?? indentStyle, + indentSize: other.indentSize ?? indentSize, + tabWidth: other.tabWidth ?? tabWidth, + endOfLine: other.endOfLine ?? endOfLine, + maxLineLength: other.maxLineLength ?? maxLineLength, + trimTrailingWhitespace: other.trimTrailingWhitespace ?? trimTrailingWhitespace, + insertFinalNewline: other.insertFinalNewline ?? insertFinalNewline, + ); /// Only the set keys, for the IPC payload. Empty map when [isEmpty]. Map toJson() => { - if (indentStyle != null) 'indent_style': indentStyle, - if (indentSize != null) 'indent_size': indentSize, - if (tabWidth != null) 'tab_width': tabWidth, - if (endOfLine != null) 'end_of_line': endOfLine, - if (maxLineLength != null) 'max_line_length': maxLineLength, - if (trimTrailingWhitespace != null) 'trim_trailing_whitespace': trimTrailingWhitespace, - if (insertFinalNewline != null) 'insert_final_newline': insertFinalNewline, - }; + if (indentStyle != null) 'indent_style': indentStyle, + if (indentSize != null) 'indent_size': indentSize, + if (tabWidth != null) 'tab_width': tabWidth, + if (endOfLine != null) 'end_of_line': endOfLine, + if (maxLineLength != null) 'max_line_length': maxLineLength, + if (trimTrailingWhitespace != null) 'trim_trailing_whitespace': trimTrailingWhitespace, + if (insertFinalNewline != null) 'insert_final_newline': insertFinalNewline, + }; factory EditorSettings.fromJson(Object? raw) { if (raw is! Map) return empty; diff --git a/lib/src/editor/editorconfig.dart b/lib/src/editor/editorconfig.dart index 42c9337a..60f771ff 100644 --- a/lib/src/editor/editorconfig.dart +++ b/lib/src/editor/editorconfig.dart @@ -113,10 +113,10 @@ int? _posInt(String? v) { } bool? _bool(String? v) => switch (v) { - 'true' => true, - 'false' => false, - _ => null, - }; + 'true' => true, + 'false' => false, + _ => null, +}; // --------------------------------------------------------------------------- // INI parsing diff --git a/lib/src/editor/registry.dart b/lib/src/editor/registry.dart index 9cb564d0..d7929e23 100644 --- a/lib/src/editor/registry.dart +++ b/lib/src/editor/registry.dart @@ -15,10 +15,7 @@ import 'buffer.dart'; import 'editor_settings_resolver.dart'; class EditorRegistry { - EditorRegistry({ - required this.events, - required this.workspaceRoot, - }); + EditorRegistry({required this.events, required this.workspaceRoot}); final DaemonEventSink events; @@ -53,12 +50,7 @@ class EditorRegistry { } final id = 'b_${_nextId++}'; - final buf = EditorBuffer( - id: id, - path: path, - content: content, - settings: resolveEditorSettings(workspaceRoot, path), - ); + final buf = EditorBuffer(id: id, path: path, content: content, settings: resolveEditorSettings(workspaceRoot, path)); _buffers[id] = buf; _pathToId[path] = id; @@ -111,10 +103,7 @@ class EditorRegistry { void setSelection(String id, Selection sel) { final buf = _buffers[id]; if (buf == null) return; - final clamped = Selection( - start: sel.start.clamp(0, buf.content.length), - end: sel.end.clamp(0, buf.content.length), - ); + final clamped = Selection(start: sel.start.clamp(0, buf.content.length), end: sel.end.clamp(0, buf.content.length)); if (clamped.start == buf.selection.start && clamped.end == buf.selection.end) { return; } @@ -131,23 +120,12 @@ class EditorRegistry { if (buf == null) return; buf.content = content; if (selection != null) { - buf.selection = Selection( - start: selection.start.clamp(0, content.length), - end: selection.end.clamp(0, content.length), - ); + buf.selection = Selection(start: selection.start.clamp(0, content.length), end: selection.end.clamp(0, content.length)); } else { - buf.selection = Selection( - start: buf.selection.start.clamp(0, content.length), - end: buf.selection.end.clamp(0, content.length), - ); + buf.selection = Selection(start: buf.selection.start.clamp(0, content.length), end: buf.selection.end.clamp(0, content.length)); } buf.dirty = true; - _emit('editor.edited', { - 'id': id, - 'kind': 'replace', - 'length': content.length, - 'selection': buf.selection.toJson(), - }); + _emit('editor.edited', {'id': id, 'kind': 'replace', 'length': content.length, 'selection': buf.selection.toJson()}); } /// Persist [id] to disk. Applies the buffer's on-save settings (EOL, @@ -167,19 +145,11 @@ class EditorRegistry { if (changed) { buf.content = normalized; - buf.selection = Selection( - start: buf.selection.start.clamp(0, normalized.length), - end: buf.selection.end.clamp(0, normalized.length), - ); + buf.selection = Selection(start: buf.selection.start.clamp(0, normalized.length), end: buf.selection.end.clamp(0, normalized.length)); // Re-broadcast so the UI reloads the normalized text (the editor.edited // handler re-reads the buffer); emitted before editor.saved clears dirty. buf.dirty = false; - _emit('editor.edited', { - 'id': id, - 'kind': 'replace', - 'length': normalized.length, - 'selection': buf.selection.toJson(), - }); + _emit('editor.edited', {'id': id, 'kind': 'replace', 'length': normalized.length, 'selection': buf.selection.toJson()}); } buf.dirty = false; @@ -197,11 +167,7 @@ class EditorRegistry { final next = resolveEditorSettings(workspaceRoot, buf.path); if (next.toJson().toString() == buf.settings.toJson().toString()) continue; buf.settings = next; - _emit('editor.settings-changed', { - 'id': buf.id, - 'path': buf.path, - 'editorSettings': next.toJson(), - }); + _emit('editor.settings-changed', {'id': buf.id, 'path': buf.path, 'editorSettings': next.toJson()}); } } @@ -235,26 +201,15 @@ class EditorRegistry { void _emitActive() { final buf = active; - _emit('editor.active-changed', { - 'id': buf?.id, - 'path': buf?.path, - }); + _emit('editor.active-changed', {'id': buf?.id, 'path': buf?.path}); } void _emitSelection(EditorBuffer buf) { - _emit('editor.selection-changed', { - 'id': buf.id, - 'selection': buf.selection.toJson(), - }); + _emit('editor.selection-changed', {'id': buf.id, 'selection': buf.selection.toJson()}); } void _emit(String kind, Map data) { - events.emit(IpcEvent( - subsystem: 'editor', - kind: kind, - timestamp: DateTime.now().toUtc(), - data: data, - )); + events.emit(IpcEvent(subsystem: 'editor', kind: kind, timestamp: DateTime.now().toUtc(), data: data)); } String _absolutePathOf(String repoRelative) { diff --git a/lib/src/files/ignore.dart b/lib/src/files/ignore.dart index ca288695..7d2d35e5 100644 --- a/lib/src/files/ignore.dart +++ b/lib/src/files/ignore.dart @@ -18,13 +18,7 @@ library; /// A single parsed gitignore pattern. class IgnorePattern { - IgnorePattern._({ - required this.source, - required this.negated, - required this.directoryOnly, - required this.anchored, - required this.regex, - }); + IgnorePattern._({required this.source, required this.negated, required this.directoryOnly, required this.anchored, required this.regex}); /// The raw line from the file (for diagnostics). final String source; @@ -168,7 +162,5 @@ class IgnoreSet { /// ignore files. Matches D-004's "walker magic: none except /// `.git/`" — but the tree-view UI benefits from hiding `.pql/` and /// `.dart_tool/` too since users never edit those by hand. - static IgnoreSet builtin() => IgnoreSet.parse(const [ - '.git/\n.pql/\n.clide/\n.dart_tool/\nbuild/\nnode_modules/\n', - ]); + static IgnoreSet builtin() => IgnoreSet.parse(const ['.git/\n.pql/\n.clide/\n.dart_tool/\nbuild/\nnode_modules/\n']); } diff --git a/lib/src/files/listing.dart b/lib/src/files/listing.dart index 45d3cb20..0cc3f9f2 100644 --- a/lib/src/files/listing.dart +++ b/lib/src/files/listing.dart @@ -10,14 +10,7 @@ import 'dart:io'; import 'ignore.dart'; class FileEntry { - const FileEntry({ - required this.name, - required this.path, - required this.isDirectory, - required this.isSymlink, - this.sizeBytes, - this.modifiedMs, - }); + const FileEntry({required this.name, required this.path, required this.isDirectory, required this.isSymlink, this.sizeBytes, this.modifiedMs}); /// Display name (basename). final String name; @@ -30,23 +23,19 @@ class FileEntry { final int? modifiedMs; Map toJson() => { - 'name': name, - 'path': path, - 'isDirectory': isDirectory, - 'isSymlink': isSymlink, - if (sizeBytes != null) 'sizeBytes': sizeBytes, - if (modifiedMs != null) 'modifiedMs': modifiedMs, - }; + 'name': name, + 'path': path, + 'isDirectory': isDirectory, + 'isSymlink': isSymlink, + if (sizeBytes != null) 'sizeBytes': sizeBytes, + if (modifiedMs != null) 'modifiedMs': modifiedMs, + }; } /// List the immediate children of [dir] (repo-relative path) under /// [root]. Filters against [ignore]. Returns entries sorted /// directory-first, then by name. -Future> listDir({ - required Directory root, - required String dir, - required IgnoreSet ignore, -}) async { +Future> listDir({required Directory root, required String dir, required IgnoreSet ignore}) async { final resolved = dir.isEmpty ? root : Directory('${root.absolute.path}${Platform.pathSeparator}${dir.replaceAll('/', Platform.pathSeparator)}'); if (!await resolved.exists()) return const []; @@ -57,14 +46,16 @@ Future> listDir({ final stat = await e.stat(); final isDir = stat.type == FileSystemEntityType.directory; if (ignore.isIgnored(rel, isDirectory: isDir)) continue; - entries.add(FileEntry( - name: name, - path: rel, - isDirectory: isDir, - isSymlink: stat.type == FileSystemEntityType.link, - sizeBytes: isDir ? null : stat.size, - modifiedMs: stat.modified.millisecondsSinceEpoch, - )); + entries.add( + FileEntry( + name: name, + path: rel, + isDirectory: isDir, + isSymlink: stat.type == FileSystemEntityType.link, + sizeBytes: isDir ? null : stat.size, + modifiedMs: stat.modified.millisecondsSinceEpoch, + ), + ); } entries.sort((a, b) { @@ -96,11 +87,7 @@ class WalkResult { /// cap is hit the walk stops early and [WalkResult.truncated] is set so /// callers can surface "results truncated". The returned list is sorted /// by repo-relative path for a deterministic contract. -Future walkFiles({ - required Directory root, - required IgnoreSet ignore, - int maxFiles = 50000, -}) async { +Future walkFiles({required Directory root, required IgnoreSet ignore, int maxFiles = 50000}) async { final out = []; // DFS over repo-relative directory paths; '' is the root itself. final stack = ['']; diff --git a/lib/src/files/watcher.dart b/lib/src/files/watcher.dart index 0d109525..6d5288d5 100644 --- a/lib/src/files/watcher.dart +++ b/lib/src/files/watcher.dart @@ -38,11 +38,7 @@ enum FileChangeKind { } class FileChange { - const FileChange({ - required this.kind, - required this.path, - required this.isDirectory, - }); + const FileChange({required this.kind, required this.path, required this.isDirectory}); final FileChangeKind kind; @@ -50,11 +46,7 @@ class FileChange { final String path; final bool isDirectory; - Map toJson() => { - 'kind': kind.wire, - 'path': path, - 'isDirectory': isDirectory, - }; + Map toJson() => {'kind': kind.wire, 'path': path, 'isDirectory': isDirectory}; } class FileWatcher { @@ -70,10 +62,7 @@ class FileWatcher { Future start() async { if (_sub != null) return; - _sub = root.watch(recursive: true).listen( - _onEvent, - onError: (Object e, StackTrace _) => _controller.addError(e), - ); + _sub = root.watch(recursive: true).listen(_onEvent, onError: (Object e, StackTrace _) => _controller.addError(e)); } Future stop() async { @@ -87,11 +76,7 @@ class FileWatcher { if (rel == null) return; final isDir = e.isDirectory; if (_ignored(rel, isDir)) return; - _controller.add(FileChange( - kind: FileChangeKind.fromEvent(e), - path: rel, - isDirectory: isDir, - )); + _controller.add(FileChange(kind: FileChangeKind.fromEvent(e), path: rel, isDirectory: isDir)); } /// True if `rel` is ignored, or sits inside an ignored directory. diff --git a/lib/src/git/client.dart b/lib/src/git/client.dart index 3894cdd1..5380743f 100644 --- a/lib/src/git/client.dart +++ b/lib/src/git/client.dart @@ -61,13 +61,7 @@ class GitClient { return GitStatus(branch: branch, upstream: upstream, ahead: ahead, behind: behind, entries: const []); } - return GitStatus( - branch: branch, - upstream: upstream, - ahead: ahead, - behind: behind, - entries: parsePorcelainV1(result.stdout as String), - ); + return GitStatus(branch: branch, upstream: upstream, ahead: ahead, behind: behind, entries: parsePorcelainV1(result.stdout as String)); } Future> diff({bool staged = false, List paths = const []}) async { @@ -83,12 +77,7 @@ class GitClient { } Future> log({int count = 20}) async { - final r = await _run([ - 'log', - '--format=%H%x00%h%x00%s%x00%an%x00%aI%x00%b%x01', - '-n', - '$count', - ]); + final r = await _run(['log', '--format=%H%x00%h%x00%s%x00%an%x00%aI%x00%b%x01', '-n', '$count']); if (r.exitCode != 0) return const []; return parseLog(r.stdout as String); } @@ -115,11 +104,7 @@ class GitClient { /// Resolve a path to its git repo root. Returns null if not a git repo. Future repoRoot(String path) async { try { - final r = await Process.run( - toolchain.git, - ['rev-parse', '--show-toplevel'], - workingDirectory: path, - ); + final r = await Process.run(toolchain.git, ['rev-parse', '--show-toplevel'], workingDirectory: path); if (r.exitCode != 0) return null; final out = (r.stdout as String).trim(); return out.isEmpty ? null : out; @@ -252,14 +237,16 @@ List parseLog(String output) { if (trimmed.isEmpty) continue; final fields = trimmed.split('\x00'); if (fields.length < 5) continue; - entries.add(GitLogEntry( - hash: fields[0], - shortHash: fields[1], - subject: fields[2], - author: fields[3], - date: fields[4], - body: fields.length > 5 ? fields[5].trim() : '', - )); + entries.add( + GitLogEntry( + hash: fields[0], + shortHash: fields[1], + subject: fields[2], + author: fields[3], + date: fields[4], + body: fields.length > 5 ? fields[5].trim() : '', + ), + ); } return entries; } diff --git a/lib/src/git/diff.dart b/lib/src/git/diff.dart index 5b6b39a4..4891bbf8 100644 --- a/lib/src/git/diff.dart +++ b/lib/src/git/diff.dart @@ -12,12 +12,7 @@ import 'operations.dart' show gitBin; enum DiffLineKind { context, addition, removal, header } class DiffLine { - const DiffLine({ - required this.kind, - required this.text, - this.oldLineNo, - this.newLineNo, - }); + const DiffLine({required this.kind, required this.text, this.oldLineNo, this.newLineNo}); final DiffLineKind kind; final String text; @@ -25,22 +20,15 @@ class DiffLine { final int? newLineNo; Map toJson() => { - 'kind': kind.name, - 'text': text, - if (oldLineNo != null) 'oldLineNo': oldLineNo, - if (newLineNo != null) 'newLineNo': newLineNo, - }; + 'kind': kind.name, + 'text': text, + if (oldLineNo != null) 'oldLineNo': oldLineNo, + if (newLineNo != null) 'newLineNo': newLineNo, + }; } class GitHunk { - const GitHunk({ - required this.header, - required this.oldStart, - required this.oldCount, - required this.newStart, - required this.newCount, - required this.lines, - }); + const GitHunk({required this.header, required this.oldStart, required this.oldCount, required this.newStart, required this.newCount, required this.lines}); final String header; final int oldStart; @@ -70,15 +58,15 @@ class GitHunk { } Map toJson() => { - 'header': header, - 'oldStart': oldStart, - 'oldCount': oldCount, - 'newStart': newStart, - 'newCount': newCount, - 'additions': additions, - 'removals': removals, - 'lines': [for (final l in lines) l.toJson()], - }; + 'header': header, + 'oldStart': oldStart, + 'oldCount': oldCount, + 'newStart': newStart, + 'newCount': newCount, + 'additions': additions, + 'removals': removals, + 'lines': [for (final l in lines) l.toJson()], + }; } class GitDiff { @@ -104,37 +92,29 @@ class GitDiff { int get removals => hunks.fold(0, (s, h) => s + h.removals); Map toJson() => { - 'path': path, - if (oldPath != null) 'oldPath': oldPath, - 'binary': isBinary, - 'new': isNew, - 'deleted': isDeleted, - 'renamed': isRenamed, - 'additions': additions, - 'removals': removals, - 'hunks': [for (final h in hunks) h.toJson()], - }; + 'path': path, + if (oldPath != null) 'oldPath': oldPath, + 'binary': isBinary, + 'new': isNew, + 'deleted': isDeleted, + 'renamed': isRenamed, + 'additions': additions, + 'removals': removals, + 'hunks': [for (final h in hunks) h.toJson()], + }; } /// Run `git diff` and parse the result. /// /// [staged] controls `--cached`. [paths] narrows to specific files. -Future> gitDiff( - Directory workDir, { - bool staged = false, - List paths = const [], -}) async { +Future> gitDiff(Directory workDir, {bool staged = false, List paths = const []}) async { final args = ['diff', '--unified=3']; if (staged) args.add('--cached'); if (paths.isNotEmpty) { args.add('--'); args.addAll(paths); } - final result = await Process.run( - gitBin, - args, - workingDirectory: workDir.path, - ); + final result = await Process.run(gitBin, args, workingDirectory: workDir.path); if (result.exitCode != 0) return const []; return parseDiffOutput(result.stdout as String); } @@ -210,15 +190,9 @@ List parseDiffOutput(String output) { } } - diffs.add(GitDiff( - path: path, - oldPath: isRenamed ? oldPath : null, - hunks: hunks, - isBinary: isBinary, - isNew: isNew, - isDeleted: isDeleted, - isRenamed: isRenamed, - )); + diffs.add( + GitDiff(path: path, oldPath: isRenamed ? oldPath : null, hunks: hunks, isBinary: isBinary, isNew: isNew, isDeleted: isDeleted, isRenamed: isRenamed), + ); } return diffs; } @@ -251,48 +225,22 @@ _HunkParseResult? _parseHunk(List lines, int start) { if (line.startsWith('diff --git ') || line.startsWith('@@')) break; if (line.startsWith('+')) { - hunkLines.add(DiffLine( - kind: DiffLineKind.addition, - text: line.substring(1), - newLineNo: newLine, - )); + hunkLines.add(DiffLine(kind: DiffLineKind.addition, text: line.substring(1), newLineNo: newLine)); newLine++; } else if (line.startsWith('-')) { - hunkLines.add(DiffLine( - kind: DiffLineKind.removal, - text: line.substring(1), - oldLineNo: oldLine, - )); + hunkLines.add(DiffLine(kind: DiffLineKind.removal, text: line.substring(1), oldLineNo: oldLine)); oldLine++; } else if (line.startsWith(' ')) { - hunkLines.add(DiffLine( - kind: DiffLineKind.context, - text: line.substring(1), - oldLineNo: oldLine, - newLineNo: newLine, - )); + hunkLines.add(DiffLine(kind: DiffLineKind.context, text: line.substring(1), oldLineNo: oldLine, newLineNo: newLine)); oldLine++; newLine++; } else if (line == r'\ No newline at end of file') { - hunkLines.add(DiffLine( - kind: DiffLineKind.header, - text: line, - )); + hunkLines.add(DiffLine(kind: DiffLineKind.header, text: line)); } else { break; } i++; } - return _HunkParseResult( - GitHunk( - header: header, - oldStart: oldStart, - oldCount: oldCount, - newStart: newStart, - newCount: newCount, - lines: hunkLines, - ), - i, - ); + return _HunkParseResult(GitHunk(header: header, oldStart: oldStart, oldCount: oldCount, newStart: newStart, newCount: newCount, lines: hunkLines), i); } diff --git a/lib/src/git/operations.dart b/lib/src/git/operations.dart index 9f3b44b9..fb03f43b 100644 --- a/lib/src/git/operations.dart +++ b/lib/src/git/operations.dart @@ -53,14 +53,7 @@ void validateGitRef(String? value, {required String kind}) { } class GitLogEntry { - const GitLogEntry({ - required this.hash, - required this.shortHash, - required this.subject, - required this.author, - required this.date, - this.body = '', - }); + const GitLogEntry({required this.hash, required this.shortHash, required this.subject, required this.author, required this.date, this.body = ''}); final String hash; final String shortHash; @@ -70,13 +63,13 @@ class GitLogEntry { final String body; Map toJson() => { - 'hash': hash, - 'shortHash': shortHash, - 'subject': subject, - 'author': author, - 'date': date, - if (body.isNotEmpty) 'body': body, - }; + 'hash': hash, + 'shortHash': shortHash, + 'subject': subject, + 'author': author, + 'date': date, + if (body.isNotEmpty) 'body': body, + }; } /// Stage files. Empty [paths] means stage all (`git add -A`). @@ -120,22 +113,14 @@ Future gitUnstageHunk(Directory workDir, String patch) async { /// Discard unstaged changes for [paths]. Uses `git checkout -- `. Future gitDiscard(Directory workDir, List paths) async { if (paths.isEmpty) return; - final r = await Process.run( - gitBin, - ['checkout', '--', ...paths], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['checkout', '--', ...paths], workingDirectory: workDir.path); if (r.exitCode != 0) { throw GitException('git checkout failed', stderr: r.stderr as String); } } /// Commit staged changes. -Future gitCommit( - Directory workDir, - String message, { - bool amend = false, -}) async { +Future gitCommit(Directory workDir, String message, {bool amend = false}) async { final args = ['commit', '-m', message]; if (amend) args.add('--amend'); final r = await Process.run(gitBin, args, workingDirectory: workDir.path); @@ -143,20 +128,12 @@ Future gitCommit( throw GitException('git commit failed', stderr: r.stderr as String); } // Return the new commit hash. - final hashResult = await Process.run( - gitBin, - ['rev-parse', 'HEAD'], - workingDirectory: workDir.path, - ); + final hashResult = await Process.run(gitBin, ['rev-parse', 'HEAD'], workingDirectory: workDir.path); return (hashResult.stdout as String).trim(); } /// Stash working changes. -Future gitStash( - Directory workDir, { - String? message, - bool includeUntracked = false, -}) async { +Future gitStash(Directory workDir, {String? message, bool includeUntracked = false}) async { final args = ['stash', 'push']; if (message != null) { args.addAll(['-m', message]); @@ -170,42 +147,22 @@ Future gitStash( /// Pop the top stash entry. Future gitStashPop(Directory workDir) async { - final r = await Process.run( - gitBin, - ['stash', 'pop'], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['stash', 'pop'], workingDirectory: workDir.path); if (r.exitCode != 0) { throw GitException('git stash pop failed', stderr: r.stderr as String); } } /// Git log. Returns the most recent [count] entries. -Future> gitLog( - Directory workDir, { - int count = 20, -}) async { - final r = await Process.run( - gitBin, - [ - 'log', - '--format=%H%x00%h%x00%s%x00%an%x00%aI%x00%b%x01', - '-n', - '$count', - ], - workingDirectory: workDir.path, - ); +Future> gitLog(Directory workDir, {int count = 20}) async { + final r = await Process.run(gitBin, ['log', '--format=%H%x00%h%x00%s%x00%an%x00%aI%x00%b%x01', '-n', '$count'], workingDirectory: workDir.path); if (r.exitCode != 0) return const []; return _parseLog(r.stdout as String); } /// Pull from remote. Future gitPull(Directory workDir) async { - final r = await Process.run( - gitBin, - ['pull'], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['pull'], workingDirectory: workDir.path); if (r.exitCode != 0) { throw GitException('git pull failed', stderr: r.stderr as String); } @@ -213,12 +170,7 @@ Future gitPull(Directory workDir) async { } /// Push to remote. -Future gitPush( - Directory workDir, { - String? remote, - String? branch, - bool setUpstream = false, -}) async { +Future gitPush(Directory workDir, {String? remote, String? branch, bool setUpstream = false}) async { if (remote != null) validateGitRef(remote, kind: 'remote'); if (branch != null) validateGitRef(branch, kind: 'branch'); final args = ['push']; @@ -238,11 +190,7 @@ Future gitPush( /// List local branches. Returns (name, isCurrent) pairs. Future> gitBranches(Directory workDir) async { - final r = await Process.run( - gitBin, - ['branch', '--format=%(refname:short)|%(HEAD)'], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['branch', '--format=%(refname:short)|%(HEAD)'], workingDirectory: workDir.path); if (r.exitCode != 0) return const []; final out = <({String name, bool current})>[]; for (final line in (r.stdout as String).split('\n')) { @@ -265,11 +213,7 @@ Future> gitBranches(Directory workDir) async /// argv-injection defence here. Use `gitSwitch` if/when we adopt it. Future gitCheckout(Directory workDir, String branch) async { validateGitRef(branch, kind: 'branch'); - final r = await Process.run( - gitBin, - ['checkout', branch], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['checkout', branch], workingDirectory: workDir.path); if (r.exitCode != 0) { throw GitException('git checkout failed', stderr: r.stderr as String); } @@ -277,11 +221,7 @@ Future gitCheckout(Directory workDir, String branch) async { /// Get the current branch name. Future gitCurrentBranch(Directory workDir) async { - final r = await Process.run( - gitBin, - ['symbolic-ref', '--short', 'HEAD'], - workingDirectory: workDir.path, - ); + final r = await Process.run(gitBin, ['symbolic-ref', '--short', 'HEAD'], workingDirectory: workDir.path); if (r.exitCode != 0) return null; return (r.stdout as String).trim(); } @@ -297,43 +237,33 @@ List _parseLog(String output) { if (trimmed.isEmpty) continue; final fields = trimmed.split('\x00'); if (fields.length < 5) continue; - entries.add(GitLogEntry( - hash: fields[0], - shortHash: fields[1], - subject: fields[2], - author: fields[3], - date: fields[4], - body: fields.length > 5 ? fields[5].trim() : '', - )); + entries.add( + GitLogEntry( + hash: fields[0], + shortHash: fields[1], + subject: fields[2], + author: fields[3], + date: fields[4], + body: fields.length > 5 ? fields[5].trim() : '', + ), + ); } return entries; } -Future _applyPatch( - Directory workDir, - String patch, { - bool cached = false, - bool reverse = false, -}) async { +Future _applyPatch(Directory workDir, String patch, {bool cached = false, bool reverse = false}) async { final args = ['apply']; if (cached) args.add('--cached'); if (reverse) args.add('--reverse'); args.add('--unidiff-zero'); args.add('-'); - final proc = await Process.start( - 'git', - args, - workingDirectory: workDir.path, - ); + final proc = await Process.start('git', args, workingDirectory: workDir.path); proc.stdin.write(patch); await proc.stdin.close(); final exitCode = await proc.exitCode; if (exitCode != 0) { final stderr = await proc.stderr.transform(const SystemEncoding().decoder).join(); - throw GitException( - 'git apply failed', - stderr: stderr, - ); + throw GitException('git apply failed', stderr: stderr); } } diff --git a/lib/src/git/status.dart b/lib/src/git/status.dart index d6986dc4..c725ece1 100644 --- a/lib/src/git/status.dart +++ b/lib/src/git/status.dart @@ -9,34 +9,12 @@ import 'dart:io'; import 'operations.dart' show gitBin; -enum GitFileState { - added, - modified, - deleted, - renamed, - copied, - untracked, - ignored, -} +enum GitFileState { added, modified, deleted, renamed, copied, untracked, ignored } -enum GitConflictType { - bothModified, - bothAdded, - addedByUs, - addedByThem, - deletedByUs, - deletedByThem, - bothDeleted, -} +enum GitConflictType { bothModified, bothAdded, addedByUs, addedByThem, deletedByUs, deletedByThem, bothDeleted } class GitFileStatus { - const GitFileStatus({ - required this.path, - required this.indexState, - required this.workTreeState, - this.origPath, - this.conflictType, - }); + const GitFileStatus({required this.path, required this.indexState, required this.workTreeState, this.origPath, this.conflictType}); final String path; final GitFileState? indexState; @@ -50,26 +28,20 @@ class GitFileStatus { bool get isConflicted => conflictType != null; Map toJson() => { - 'path': path, - if (indexState != null) 'indexState': indexState!.name, - if (workTreeState != null) 'workTreeState': workTreeState!.name, - if (origPath != null) 'origPath': origPath, - if (conflictType != null) 'conflictType': conflictType!.name, - 'staged': isStaged, - 'unstaged': isUnstaged, - 'untracked': isUntracked, - 'conflicted': isConflicted, - }; + 'path': path, + if (indexState != null) 'indexState': indexState!.name, + if (workTreeState != null) 'workTreeState': workTreeState!.name, + if (origPath != null) 'origPath': origPath, + if (conflictType != null) 'conflictType': conflictType!.name, + 'staged': isStaged, + 'unstaged': isUnstaged, + 'untracked': isUntracked, + 'conflicted': isConflicted, + }; } class GitStatus { - const GitStatus({ - required this.branch, - required this.entries, - this.upstream, - this.ahead = 0, - this.behind = 0, - }); + const GitStatus({required this.branch, required this.entries, this.upstream, this.ahead = 0, this.behind = 0}); final String? branch; final String? upstream; @@ -86,28 +58,24 @@ class GitStatus { bool get hasConflicts => entries.any((e) => e.isConflicted); Map toJson() => { - 'branch': branch, - if (upstream != null) 'upstream': upstream, - 'ahead': ahead, - 'behind': behind, - 'clean': isClean, - 'hasConflicts': hasConflicts, - 'staged': [for (final e in staged) e.toJson()], - 'unstaged': [for (final e in unstaged) e.toJson()], - 'untracked': [for (final e in untracked) e.toJson()], - 'conflicted': [for (final e in conflicted) e.toJson()], - }; + 'branch': branch, + if (upstream != null) 'upstream': upstream, + 'ahead': ahead, + 'behind': behind, + 'clean': isClean, + 'hasConflicts': hasConflicts, + 'staged': [for (final e in staged) e.toJson()], + 'unstaged': [for (final e in unstaged) e.toJson()], + 'untracked': [for (final e in untracked) e.toJson()], + 'conflicted': [for (final e in conflicted) e.toJson()], + }; } /// Run `git status` and parse the result. Future gitStatus(Directory workDir) async { final ProcessResult branchResult; try { - branchResult = await Process.run( - gitBin, - ['status', '--porcelain=v2', '--branch', '-z'], - workingDirectory: workDir.path, - ); + branchResult = await Process.run(gitBin, ['status', '--porcelain=v2', '--branch', '-z'], workingDirectory: workDir.path); } on ProcessException { return const GitStatus(branch: null, entries: []); } @@ -136,33 +104,17 @@ Future gitStatus(Directory workDir) async { final ProcessResult result; try { - result = await Process.run( - gitBin, - ['status', '--porcelain=v1', '-z'], - workingDirectory: workDir.path, - ); + result = await Process.run(gitBin, ['status', '--porcelain=v1', '-z'], workingDirectory: workDir.path); } on ProcessException { return GitStatus(branch: branch, entries: const [], upstream: upstream, ahead: ahead, behind: behind); } if (result.exitCode != 0) { - return GitStatus( - branch: branch, - upstream: upstream, - ahead: ahead, - behind: behind, - entries: const [], - ); + return GitStatus(branch: branch, upstream: upstream, ahead: ahead, behind: behind, entries: const []); } final entries = parsePorcelainV1(result.stdout as String); - return GitStatus( - branch: branch, - upstream: upstream, - ahead: ahead, - behind: behind, - entries: entries, - ); + return GitStatus(branch: branch, upstream: upstream, ahead: ahead, behind: behind, entries: entries); } List parsePorcelainV1(String output) { @@ -193,13 +145,15 @@ List parsePorcelainV1(String output) { } final conflict = _conflictType(x, y); - entries.add(GitFileStatus( - path: path, - indexState: conflict != null ? null : _parseState(x), - workTreeState: conflict != null ? null : _parseState(y), - origPath: origPath, - conflictType: conflict, - )); + entries.add( + GitFileStatus( + path: path, + indexState: conflict != null ? null : _parseState(x), + workTreeState: conflict != null ? null : _parseState(y), + origPath: origPath, + conflictType: conflict, + ), + ); i++; } return entries; diff --git a/lib/src/ipc/command_schema.dart b/lib/src/ipc/command_schema.dart index de817378..dab04f5d 100644 --- a/lib/src/ipc/command_schema.dart +++ b/lib/src/ipc/command_schema.dart @@ -211,10 +211,6 @@ class SchemaResult { /// Build the standard `userError` response for a schema violation. IpcResponse schemaError(String id, String message) => IpcResponse.err( - id: id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: message, - ), - ); + id: id, + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message), +); diff --git a/lib/src/ipc/envelope.dart b/lib/src/ipc/envelope.dart index a834fa46..4d17ad1a 100644 --- a/lib/src/ipc/envelope.dart +++ b/lib/src/ipc/envelope.dart @@ -27,47 +27,25 @@ sealed class IpcMessage { } class IpcRequest extends IpcMessage { - IpcRequest({ - required this.id, - required this.cmd, - this.args = const {}, - }); + IpcRequest({required this.id, required this.cmd, this.args = const {}}); final String id; final String cmd; final Map args; @override - Map toJson() => { - 'type': 'request', - 'v': ipcSchemaVersion, - 'id': id, - 'cmd': cmd, - 'args': args, - }; + Map toJson() => {'type': 'request', 'v': ipcSchemaVersion, 'id': id, 'cmd': cmd, 'args': args}; - factory IpcRequest.fromJson(Map j) => IpcRequest( - id: j['id']! as String, - cmd: j['cmd']! as String, - args: (j['args'] as Map?)?.cast() ?? const {}, - ); + factory IpcRequest.fromJson(Map j) => + IpcRequest(id: j['id']! as String, cmd: j['cmd']! as String, args: (j['args'] as Map?)?.cast() ?? const {}); } class IpcResponse extends IpcMessage { - IpcResponse.ok({required this.id, this.data = const {}}) - : ok = true, - error = null; + IpcResponse.ok({required this.id, this.data = const {}}) : ok = true, error = null; - IpcResponse.err({required this.id, required IpcError this.error}) - : ok = false, - data = const {}; + IpcResponse.err({required this.id, required IpcError this.error}) : ok = false, data = const {}; - IpcResponse._({ - required this.id, - required this.ok, - required this.data, - required this.error, - }); + IpcResponse._({required this.id, required this.ok, required this.data, required this.error}); final String id; final bool ok; @@ -76,13 +54,13 @@ class IpcResponse extends IpcMessage { @override Map toJson() => { - 'type': 'response', - 'v': ipcSchemaVersion, - 'id': id, - 'ok': ok, - if (ok) 'data': data, - if (!ok && error != null) 'error': error!.toJson(), - }; + 'type': 'response', + 'v': ipcSchemaVersion, + 'id': id, + 'ok': ok, + if (ok) 'data': data, + if (!ok && error != null) 'error': error!.toJson(), + }; factory IpcResponse.fromJson(Map j) { final ok = j['ok'] as bool? ?? false; @@ -96,40 +74,21 @@ class IpcResponse extends IpcMessage { } class IpcError { - IpcError({ - required this.code, - required this.kind, - required this.message, - this.hint, - }); + IpcError({required this.code, required this.kind, required this.message, this.hint}); final int code; final String kind; final String message; final String? hint; - Map toJson() => { - 'code': code, - 'kind': kind, - 'message': message, - if (hint != null) 'hint': hint, - }; + Map toJson() => {'code': code, 'kind': kind, 'message': message, if (hint != null) 'hint': hint}; - factory IpcError.fromJson(Map j) => IpcError( - code: (j['code'] as num).toInt(), - kind: j['kind']! as String, - message: j['message']! as String, - hint: j['hint'] as String?, - ); + factory IpcError.fromJson(Map j) => + IpcError(code: (j['code'] as num).toInt(), kind: j['kind']! as String, message: j['message']! as String, hint: j['hint'] as String?); } class IpcEvent extends IpcMessage { - IpcEvent({ - required this.subsystem, - required this.kind, - required this.timestamp, - this.data = const {}, - }); + IpcEvent({required this.subsystem, required this.kind, required this.timestamp, this.data = const {}}); final String subsystem; final String kind; @@ -138,18 +97,18 @@ class IpcEvent extends IpcMessage { @override Map toJson() => { - 'type': 'event', - 'v': ipcSchemaVersion, - 'subsystem': subsystem, - 'kind': kind, - 'ts': timestamp.toIso8601String(), - 'data': data, - }; + 'type': 'event', + 'v': ipcSchemaVersion, + 'subsystem': subsystem, + 'kind': kind, + 'ts': timestamp.toIso8601String(), + 'data': data, + }; factory IpcEvent.fromJson(Map j) => IpcEvent( - subsystem: j['subsystem']! as String, - kind: j['kind']! as String, - timestamp: DateTime.parse(j['ts']! as String), - data: (j['data'] as Map?)?.cast() ?? const {}, - ); + subsystem: j['subsystem']! as String, + kind: j['kind']! as String, + timestamp: DateTime.parse(j['ts']! as String), + data: (j['data'] as Map?)?.cast() ?? const {}, + ); } diff --git a/lib/src/ipc/errno_mapping.dart b/lib/src/ipc/errno_mapping.dart index 66fcb1c7..4f080ee0 100644 --- a/lib/src/ipc/errno_mapping.dart +++ b/lib/src/ipc/errno_mapping.dart @@ -33,20 +33,11 @@ abstract class PosixErrno { /// (e.g. `pane.spawn`, `editor.open`) on optional [target] (a path, /// command name, etc.). The returned error uses `notFound`, /// `userError`, or `toolError` based on what's actionable. -IpcError errnoToIpcError({ - required int errno, - required String op, - String? target, - String? raw, -}) { +IpcError errnoToIpcError({required int errno, required String op, String? target, String? raw}) { final what = target != null ? ' ($target)' : ''; switch (errno) { case PosixErrno.enoent: - return IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: '$op: not found$what', - ); + return IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: '$op: not found$what'); case PosixErrno.eacces: case PosixErrno.eperm: return IpcError( @@ -56,23 +47,11 @@ IpcError errnoToIpcError({ hint: 'check file permissions or run with appropriate access', ); case PosixErrno.eisdir: - return IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: '$op: is a directory$what', - ); + return IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: '$op: is a directory$what'); case PosixErrno.enotdir: - return IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: '$op: not a directory$what', - ); + return IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: '$op: not a directory$what'); case PosixErrno.eexist: - return IpcError( - code: IpcExitCode.conflict, - kind: IpcErrorKind.conflict, - message: '$op: already exists$what', - ); + return IpcError(code: IpcExitCode.conflict, kind: IpcErrorKind.conflict, message: '$op: already exists$what'); case PosixErrno.emfile: case PosixErrno.enfile: return IpcError( @@ -82,23 +61,10 @@ IpcError errnoToIpcError({ hint: 'system or per-process file descriptor limit reached', ); case PosixErrno.enomem: - return IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: '$op: out of memory', - ); + return IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: '$op: out of memory'); case PosixErrno.eagain: - return IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: '$op: resource temporarily unavailable', - hint: 'retry may succeed', - ); + return IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: '$op: resource temporarily unavailable', hint: 'retry may succeed'); default: - return IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: '$op failed${raw != null ? ': $raw' : ' (errno=$errno)'}', - ); + return IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: '$op failed${raw != null ? ': $raw' : ' (errno=$errno)'}'); } } diff --git a/lib/src/ipc/mcp_server.dart b/lib/src/ipc/mcp_server.dart index c0199ffa..774e3900 100644 --- a/lib/src/ipc/mcp_server.dart +++ b/lib/src/ipc/mcp_server.dart @@ -63,14 +63,7 @@ class _McpSession { /// HTTP + SSE MCP server. Lifecycle mirrors [IpcServer]: `start()` /// binds + writes the discovery file; `stop()` unbinds + removes it. class McpServer { - McpServer({ - required this.workspaceRoot, - required this.log, - this.dispatcher, - this.discoveryDirOverride, - this.bindHost = '127.0.0.1', - this.bindPort = 0, - }); + McpServer({required this.workspaceRoot, required this.log, this.dispatcher, this.discoveryDirOverride, this.bindHost = '127.0.0.1', this.bindPort = 0}); /// Workspace root reported in the discovery file. Helps Claude /// Code show "which clide is this" when multiple are running. @@ -109,9 +102,12 @@ class McpServer { _http = server; _port = server.port; _lockFile = await _writeDiscoveryFile(); - server.listen(_route, onError: (Object e, StackTrace st) { - log.warn('mcp', 'http error: $e'); - }); + server.listen( + _route, + onError: (Object e, StackTrace st) { + log.warn('mcp', 'http error: $e'); + }, + ); log.info('mcp', 'MCP/SSE listening at http://$bindHost:${server.port} (workspace: $workspaceRoot)'); } @@ -331,12 +327,7 @@ class McpServer { dirHandle.createSync(recursive: true); } final path = '$dir/$pid.lock'; - final body = jsonEncode({ - 'pid': pid, - 'workspace': workspaceRoot, - 'transport': 'sse', - 'url': 'http://$bindHost:$_port/sse', - }); + final body = jsonEncode({'pid': pid, 'workspace': workspaceRoot, 'transport': 'sse', 'url': 'http://$bindHost:$_port/sse'}); File(path).writeAsStringSync(body); return path; } diff --git a/lib/src/ipc/server.dart b/lib/src/ipc/server.dart index df276f89..fd7742a2 100644 --- a/lib/src/ipc/server.dart +++ b/lib/src/ipc/server.dart @@ -23,14 +23,7 @@ import 'package:clide/src/ipc/schema_v1.dart'; /// D-72. Per-handler isolate offload is the dispatcher / handler's /// concern, not this layer's. class IpcServer { - IpcServer({ - required this.dispatcher, - required this.workspaceRoot, - required this.log, - this.events, - this.replayDepth = 16, - this.eventLogDepth = 1024, - }); + IpcServer({required this.dispatcher, required this.workspaceRoot, required this.log, this.events, this.replayDepth = 16, this.eventLogDepth = 1024}); final DaemonDispatcher dispatcher; final String workspaceRoot; @@ -94,10 +87,7 @@ class IpcServer { final path = workspaceSocketPath(workspaceRoot); await _prepareParentDir(path); await _unlinkStale(path); - final socket = await ServerSocket.bind( - InternetAddress(path, type: InternetAddressType.unix), - 0, - ); + final socket = await ServerSocket.bind(InternetAddress(path, type: InternetAddressType.unix), 0); try { await _chmod(path, 0x180); // 0o600 } catch (e, st) { @@ -108,9 +98,12 @@ class IpcServer { } _socket = socket; _socketPath = path; - _accepts = socket.listen(_onClient, onError: (Object e, StackTrace st) { - log.error('ipc', 'accept loop error', error: e, stackTrace: st); - }); + _accepts = socket.listen( + _onClient, + onError: (Object e, StackTrace st) { + log.error('ipc', 'accept loop error', error: e, stackTrace: st); + }, + ); // Subscribe to the bus so we can populate the replay ring AND // fan out to live `tail --events` subscribers. Idempotent — // we only attach when a bus is supplied. @@ -198,11 +191,7 @@ class IpcServer { if (msg is! IpcRequest) { response = IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'expected request, got ${msg.runtimeType}', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'expected request, got ${msg.runtimeType}'), ); } else { // Peel off the `_argv` envelope at the server layer so the @@ -238,21 +227,13 @@ class IpcServer { } on FormatException catch (e) { response = IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: 'malformed request: ${e.message}', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'malformed request: ${e.message}'), ); } catch (e, st) { log.error('ipc', 'dispatch threw for "$reqCmd"', error: e, stackTrace: st); response = IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'internal error: $e', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'internal error: $e'), ); } try { @@ -280,10 +261,7 @@ class IpcServer { if (!f.existsSync()) return; // Probe: try connecting. If something answers, refuse to bind. try { - final test = await Socket.connect( - InternetAddress(path, type: InternetAddressType.unix), - 0, - ).timeout(const Duration(milliseconds: 200)); + final test = await Socket.connect(InternetAddress(path, type: InternetAddressType.unix), 0).timeout(const Duration(milliseconds: 200)); await test.close(); throw StateError('another clide IPC server is already listening on $path'); } on SocketException { @@ -355,11 +333,7 @@ class IpcServer { if (since == null) { return IpcResponse.err( id: req.id, - error: IpcError( - code: IpcExitCode.userError, - kind: IpcErrorKind.userError, - message: '--since must be a non-negative integer cursor', - ), + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: '--since must be a non-negative integer cursor'), ); } final out = >[]; @@ -371,12 +345,10 @@ class IpcServer { // A gap only means something when the caller had a prior position // (since > 0); a first read (since 0) just gets whatever's retained. final gap = since > 0 && since < _droppedThrough; - return IpcResponse.ok(id: req.id, data: { - 'events': out, - 'cursor': _lastCursor, - 'gap': gap, - if (gap) 'oldestCursor': _eventLog.isEmpty ? _lastCursor : _eventLog.first.cursor, - }); + return IpcResponse.ok( + id: req.id, + data: {'events': out, 'cursor': _lastCursor, 'gap': gap, if (gap) 'oldestCursor': _eventLog.isEmpty ? _lastCursor : _eventLog.first.cursor}, + ); } /// Parse the `--since` flag (a string from argv or an int from a typed @@ -390,12 +362,7 @@ class IpcServer { } void _onBusEvent(DaemonEvent e) { - final ev = IpcEvent( - subsystem: e.subsystem, - kind: e.kind, - data: e.data, - timestamp: e.ts, - ); + final ev = IpcEvent(subsystem: e.subsystem, kind: e.kind, data: e.data, timestamp: e.ts); // Push to replay ring. final ring = _replay.putIfAbsent(e.subsystem, () => Queue()); ring.addLast(ev); diff --git a/lib/src/panes/pane.dart b/lib/src/panes/pane.dart index a057b2aa..f42311f4 100644 --- a/lib/src/panes/pane.dart +++ b/lib/src/panes/pane.dart @@ -16,23 +16,12 @@ enum PaneKind { String get wire => name; static PaneKind parse(String s) { - return PaneKind.values.firstWhere( - (v) => v.wire == s, - orElse: () => throw ArgumentError.value(s, 'kind', 'unknown pane kind'), - ); + return PaneKind.values.firstWhere((v) => v.wire == s, orElse: () => throw ArgumentError.value(s, 'kind', 'unknown pane kind')); } } class Pane { - Pane({ - required this.id, - required this.kind, - required this.pid, - required this.argv, - this.cwd, - this.title, - this.isClosed = false, - }); + Pane({required this.id, required this.kind, required this.pid, required this.argv, this.cwd, this.title, this.isClosed = false}); final String id; final PaneKind kind; @@ -47,12 +36,12 @@ class Pane { bool isClosed; Map toJson() => { - 'id': id, - 'kind': kind.wire, - 'pid': pid, - 'argv': argv, - if (cwd != null) 'cwd': cwd, - if (title != null) 'title': title, - 'closed': isClosed, - }; + 'id': id, + 'kind': kind.wire, + 'pid': pid, + 'argv': argv, + if (cwd != null) 'cwd': cwd, + if (title != null) 'title': title, + 'closed': isClosed, + }; } diff --git a/lib/src/panes/registry.dart b/lib/src/panes/registry.dart index 850ded74..5f6c0858 100644 --- a/lib/src/panes/registry.dart +++ b/lib/src/panes/registry.dart @@ -52,36 +52,17 @@ class PaneRegistry { 'COLORTERM': 'truecolor', 'LANG': 'en_US.UTF-8', 'LC_ALL': 'en_US.UTF-8', - if (env != null) ...env, + ...?env, }; - final session = NativePty.start( - executable: executable, - arguments: arguments, - columns: cols, - rows: rows, - workingDirectory: cwd, - environment: fullEnv, - ); - final pane = Pane( - id: id, - kind: kind, - pid: session.pid, - argv: argv, - cwd: cwd, - title: title, - ); + final session = NativePty.start(executable: executable, arguments: arguments, columns: cols, rows: rows, workingDirectory: cwd, environment: fullEnv); + final pane = Pane(id: id, kind: kind, pid: session.pid, argv: argv, cwd: cwd, title: title); _panes[id] = pane; _sessions[id] = session; _emit('pane.spawned', id, pane.toJson()); - _subs[id] = session.output.listen( - (bytes) => _emit('pane.output', id, { - 'bytes_b64': base64Encode(bytes), - }), - onDone: () => _onExit(pane), - ); + _subs[id] = session.output.listen((bytes) => _emit('pane.output', id, {'bytes_b64': base64Encode(bytes)}), onDone: () => _onExit(pane)); return pane; } @@ -136,11 +117,6 @@ class PaneRegistry { } void _emit(String kind, String id, Map data) { - events.emit(IpcEvent( - subsystem: 'pane', - kind: kind, - timestamp: DateTime.now().toUtc(), - data: {'id': id, ...data}, - )); + events.emit(IpcEvent(subsystem: 'pane', kind: kind, timestamp: DateTime.now().toUtc(), data: {'id': id, ...data})); } } diff --git a/lib/src/panes/view_pane.dart b/lib/src/panes/view_pane.dart index 29910a48..98a46378 100644 --- a/lib/src/panes/view_pane.dart +++ b/lib/src/panes/view_pane.dart @@ -11,13 +11,7 @@ library; class ViewPane { - const ViewPane({ - required this.id, - required this.slot, - required this.title, - required this.active, - required this.visible, - }); + const ViewPane({required this.id, required this.slot, required this.title, required this.active, required this.visible}); /// Stable contribution id (e.g. `claude`, `files`, `editor`) — the same id /// `pane.focus` would target. @@ -36,13 +30,5 @@ class ViewPane { /// Whether the tab's slot is currently visible (not collapsed/hidden). final bool visible; - Map toJson() => { - 'id': id, - 'kind': 'view', - 'slot': slot, - 'title': title, - 'active': active, - 'visible': visible, - 'source': 'ui', - }; + Map toJson() => {'id': id, 'kind': 'view', 'slot': slot, 'title': title, 'active': active, 'visible': visible, 'source': 'ui'}; } diff --git a/lib/src/pql/client.dart b/lib/src/pql/client.dart index 47e8eab7..5f78f5e7 100644 --- a/lib/src/pql/client.dart +++ b/lib/src/pql/client.dart @@ -79,11 +79,7 @@ class PqlClient { return _runObject(['decisions', 'validate']); } - Future>> decisionList({ - String? type, - String? domain, - String? status, - }) async { + Future>> decisionList({String? type, String? domain, String? status}) async { final args = ['decisions', 'list']; if (type != null) args.addAll(['--type', type]); if (domain != null) args.addAll(['--domain', domain]); @@ -91,11 +87,7 @@ class PqlClient { return _runList(args); } - Future> decisionShow( - String id, { - bool withRefs = false, - bool withTickets = false, - }) async { + Future> decisionShow(String id, {bool withRefs = false, bool withTickets = false}) async { final args = ['decisions', 'show', id]; if (withRefs) args.add('--with-refs'); if (withTickets) args.add('--with-tickets'); @@ -106,12 +98,7 @@ class PqlClient { return _runObject(['decisions', 'read', id]); } - Future>> ticketList({ - String? status, - String? team, - String? assigned, - String? decision, - }) async { + Future>> ticketList({String? status, String? team, String? assigned, String? decision}) async { final args = ['ticket', 'list']; if (status != null) args.addAll(['--status', status]); if (team != null) args.addAll(['--team', team]); @@ -120,11 +107,7 @@ class PqlClient { return _runList(args); } - Future> ticketShow( - String id, { - bool withContext = false, - bool withBlockers = false, - }) async { + Future> ticketShow(String id, {bool withContext = false, bool withBlockers = false}) async { final args = ['ticket', 'show', id]; if (withContext) args.add('--with-context'); if (withBlockers) args.add('--with-blockers'); @@ -171,17 +154,9 @@ class PqlClient { for (var attempt = 1; attempt <= _kMaxAttempts; attempt++) { final ProcessResult r; try { - r = await Process.run( - toolchain.pql, - args, - workingDirectory: workDir.path, - ); + r = await Process.run(toolchain.pql, args, workingDirectory: workDir.path); } on ProcessException catch (e) { - throw PqlException( - 'pql ${args.first}: ${e.message}', - exitCode: e.errorCode, - stderr: e.toString(), - ); + throw PqlException('pql ${args.first}: ${e.message}', exitCode: e.errorCode, stderr: e.toString()); } final stderr = (r.stderr as String).trim(); // pql 1.5+ returns exit 0 with an empty `[]` for zero matches, so any @@ -196,11 +171,7 @@ class PqlClient { await Future.delayed(Duration(milliseconds: 100 * attempt)); continue; } - throw PqlException( - 'pql ${args.first} failed', - exitCode: r.exitCode, - stderr: stderr, - ); + throw PqlException('pql ${args.first} failed', exitCode: r.exitCode, stderr: stderr); } final stdout = (r.stdout as String).trim(); if (stdout.isEmpty) return null; diff --git a/lib/src/pty/env.dart b/lib/src/pty/env.dart index e05d53d7..78eed5b9 100644 --- a/lib/src/pty/env.dart +++ b/lib/src/pty/env.dart @@ -25,12 +25,7 @@ String _buildExpandedPath() { final base = Platform.environment['PATH'] ?? ''; if (!Platform.isMacOS) return base; final home = Platform.environment['HOME'] ?? ''; - final extras = [ - if (home.isNotEmpty) '$home/.local/bin', - '/opt/homebrew/bin', - '/opt/homebrew/sbin', - '/usr/local/bin', - ]; + final extras = [if (home.isNotEmpty) '$home/.local/bin', '/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin']; final existing = base.split(':').toSet(); final missing = extras.where((p) => !existing.contains(p)); if (missing.isEmpty) return base; @@ -53,13 +48,6 @@ const Map clidePtyEnvDefaults = { /// Merge [base] onto the process environment; clide defaults override /// user env where they overlap. Explicit [overrides] win over both. -Map mergePtyEnv({ - required Map processEnv, - Map? overrides, -}) { - return { - ...processEnv, - ...clidePtyEnvDefaults, - if (overrides != null) ...overrides, - }; +Map mergePtyEnv({required Map processEnv, Map? overrides}) { + return {...processEnv, ...clidePtyEnvDefaults, ...?overrides}; } diff --git a/lib/src/pty/ffi/libc.dart b/lib/src/pty/ffi/libc.dart index 065b06c9..7edaa945 100644 --- a/lib/src/pty/ffi/libc.dart +++ b/lib/src/pty/ffi/libc.dart @@ -64,82 +64,28 @@ const int sigwinch = 28; // Typedefs // --------------------------------------------------------------------------- -typedef _SocketpairC = ffi.Int32 Function( - ffi.Int32 domain, - ffi.Int32 type, - ffi.Int32 protocol, - ffi.Pointer sv, -); -typedef _SocketpairD = int Function( - int domain, - int type, - int protocol, - ffi.Pointer sv, -); +typedef _SocketpairC = ffi.Int32 Function(ffi.Int32 domain, ffi.Int32 type, ffi.Int32 protocol, ffi.Pointer sv); +typedef _SocketpairD = int Function(int domain, int type, int protocol, ffi.Pointer sv); -typedef _RecvmsgC = ffi.IntPtr Function( - ffi.Int32 sockfd, - ffi.Pointer msg, - ffi.Int32 flags, -); -typedef _RecvmsgD = int Function( - int sockfd, - ffi.Pointer msg, - int flags, -); +typedef _RecvmsgC = ffi.IntPtr Function(ffi.Int32 sockfd, ffi.Pointer msg, ffi.Int32 flags); +typedef _RecvmsgD = int Function(int sockfd, ffi.Pointer msg, int flags); -typedef _RecvmsgDarwinC = ffi.IntPtr Function( - ffi.Int32 sockfd, - ffi.Pointer msg, - ffi.Int32 flags, -); -typedef _RecvmsgDarwinD = int Function( - int sockfd, - ffi.Pointer msg, - int flags, -); +typedef _RecvmsgDarwinC = ffi.IntPtr Function(ffi.Int32 sockfd, ffi.Pointer msg, ffi.Int32 flags); +typedef _RecvmsgDarwinD = int Function(int sockfd, ffi.Pointer msg, int flags); -typedef _ReadC = ffi.IntPtr Function( - ffi.Int32 fd, - ffi.Pointer buf, - ffi.IntPtr count, -); -typedef _ReadD = int Function( - int fd, - ffi.Pointer buf, - int count, -); +typedef _ReadC = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer buf, ffi.IntPtr count); +typedef _ReadD = int Function(int fd, ffi.Pointer buf, int count); -typedef _WriteC = ffi.IntPtr Function( - ffi.Int32 fd, - ffi.Pointer buf, - ffi.IntPtr count, -); -typedef _WriteD = int Function( - int fd, - ffi.Pointer buf, - int count, -); +typedef _WriteC = ffi.IntPtr Function(ffi.Int32 fd, ffi.Pointer buf, ffi.IntPtr count); +typedef _WriteD = int Function(int fd, ffi.Pointer buf, int count); typedef _CloseC = ffi.Int32 Function(ffi.Int32 fd); typedef _CloseD = int Function(int fd); -typedef _IoctlPtrC = ffi.Int32 Function( - ffi.Int32 fd, - ffi.UnsignedLong request, - ffi.Pointer argp, -); -typedef _IoctlPtrD = int Function( - int fd, - int request, - ffi.Pointer argp, -); +typedef _IoctlPtrC = ffi.Int32 Function(ffi.Int32 fd, ffi.UnsignedLong request, ffi.Pointer argp); +typedef _IoctlPtrD = int Function(int fd, int request, ffi.Pointer argp); -typedef _FcntlIntC = ffi.Int32 Function( - ffi.Int32 fd, - ffi.Int32 cmd, - ffi.Int32 arg, -); +typedef _FcntlIntC = ffi.Int32 Function(ffi.Int32 fd, ffi.Int32 cmd, ffi.Int32 arg); typedef _FcntlIntD = int Function(int fd, int cmd, int arg); typedef _ErrnoLocationC = ffi.Pointer Function(); @@ -257,16 +203,12 @@ final _FcntlIntD fcntlInt = _libc.lookupFunction<_FcntlIntC, _FcntlIntD>('fcntl' /// uses `__error`. int get errno { try { - final fn = _libc.lookupFunction<_ErrnoLocationC, _ErrnoLocationD>( - '__errno_location', - ); + final fn = _libc.lookupFunction<_ErrnoLocationC, _ErrnoLocationD>('__errno_location'); return fn().value; } on ArgumentError { // Fall through to macOS-style. } - final fn = _libc.lookupFunction<_ErrnoLocationC, _ErrnoLocationD>( - '__error', - ); + final fn = _libc.lookupFunction<_ErrnoLocationC, _ErrnoLocationD>('__error'); return fn().value; } diff --git a/lib/src/pty/native_pty.dart b/lib/src/pty/native_pty.dart index 9061a212..27dc1108 100644 --- a/lib/src/pty/native_pty.dart +++ b/lib/src/pty/native_pty.dart @@ -65,38 +65,62 @@ final _ptsname = _dl.lookupFunction Function(ffi.Int32), ffi.P // larger than any documented platform layout) and pass it as Pointer. // init() writes the real layout into our memory; destroy() releases any // internal nested allocations. -final _posixSpawn = _dl.lookupFunction< - ffi.Int32 Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer>, - ffi.Pointer>), - int Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer>, - ffi.Pointer>)>('posix_spawn'); +final _posixSpawn = _dl + .lookupFunction< + ffi.Int32 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, + ffi.Pointer>, + ), + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, + ffi.Pointer>, + ) + >('posix_spawn'); final _spawnattrInit = _dl.lookupFunction), int Function(ffi.Pointer)>('posix_spawnattr_init'); final _spawnattrDestroy = _dl.lookupFunction), int Function(ffi.Pointer)>('posix_spawnattr_destroy'); -final _spawnattrSetflags = - _dl.lookupFunction, ffi.Int16), int Function(ffi.Pointer, int)>('posix_spawnattr_setflags'); +final _spawnattrSetflags = _dl.lookupFunction, ffi.Int16), int Function(ffi.Pointer, int)>( + 'posix_spawnattr_setflags', +); final _faInit = _dl.lookupFunction), int Function(ffi.Pointer)>('posix_spawn_file_actions_init'); final _faDestroy = _dl.lookupFunction), int Function(ffi.Pointer)>('posix_spawn_file_actions_destroy'); -final _faAddopen = _dl.lookupFunction, ffi.Int32, ffi.Pointer, ffi.Int32, ffi.Uint32), - int Function(ffi.Pointer, int, ffi.Pointer, int, int)>('posix_spawn_file_actions_addopen'); +final _faAddopen = _dl + .lookupFunction< + ffi.Int32 Function(ffi.Pointer, ffi.Int32, ffi.Pointer, ffi.Int32, ffi.Uint32), + int Function(ffi.Pointer, int, ffi.Pointer, int, int) + >('posix_spawn_file_actions_addopen'); final _faAdddup2 = _dl.lookupFunction, ffi.Int32, ffi.Int32), int Function(ffi.Pointer, int, int)>( - 'posix_spawn_file_actions_adddup2'); -final _faAddclose = - _dl.lookupFunction, ffi.Int32), int Function(ffi.Pointer, int)>('posix_spawn_file_actions_addclose'); + 'posix_spawn_file_actions_adddup2', +); +final _faAddclose = _dl.lookupFunction, ffi.Int32), int Function(ffi.Pointer, int)>( + 'posix_spawn_file_actions_addclose', +); // glibc 2.29+ / macOS 10.15+. Both ship the `_np` suffix. final _faAddchdir = _dl.lookupFunction, ffi.Pointer), int Function(ffi.Pointer, ffi.Pointer)>( - 'posix_spawn_file_actions_addchdir_np'); + 'posix_spawn_file_actions_addchdir_np', +); // libc primitives shared with the reader isolate / lifecycle. -final _nativeWrite = - _dl.lookupFunction, ffi.IntPtr), int Function(int, ffi.Pointer, int)>('write'); +final _nativeWrite = _dl.lookupFunction, ffi.IntPtr), int Function(int, ffi.Pointer, int)>( + 'write', +); final _nativeClose = _dl.lookupFunction('close'); -final _ioctl = - _dl.lookupFunction), int Function(int, int, ffi.Pointer<_Winsize>)>('ioctl'); +final _ioctl = _dl.lookupFunction), int Function(int, int, ffi.Pointer<_Winsize>)>( + 'ioctl', +); final _nativeKill = _dl.lookupFunction('kill'); -final _waitpid = - _dl.lookupFunction, ffi.Int32), int Function(int, ffi.Pointer, int)>('waitpid'); +final _waitpid = _dl.lookupFunction, ffi.Int32), int Function(int, ffi.Pointer, int)>( + 'waitpid', +); // Constants — all duplicated from , , . final int _kTiocsWinsz = Platform.isMacOS ? 0x80087467 : 0x5414; @@ -378,11 +402,7 @@ class NativePty { } var written = 0; while (written < bytes.length) { - final n = _nativeWrite( - _fd, - (buf + written).cast(), - bytes.length - written, - ); + final n = _nativeWrite(_fd, (buf + written).cast(), bytes.length - written); if (n < 0) { final err = libc.errno; if (err == PosixErrno.eintr) continue; @@ -451,10 +471,7 @@ class NativePty { // Wait for the isolate to send `null` (EOF) — confirms it has // exited its poll loop and won't touch the fd again. if (_readerExited != null) { - await _readerExited!.future.timeout( - const Duration(milliseconds: 500), - onTimeout: () {}, - ); + await _readerExited!.future.timeout(const Duration(milliseconds: 500), onTimeout: () {}); } _nativeClose(_fd); diff --git a/lib/src/search/grep_engine.dart b/lib/src/search/grep_engine.dart index 351c1305..d4ba1e64 100644 --- a/lib/src/search/grep_engine.dart +++ b/lib/src/search/grep_engine.dart @@ -69,9 +69,7 @@ Stream> grepWorkspace({ final rootPath = root.absolute.path; // Launch every chunk concurrently; await in chunk order to stream. - final futures = >>[ - for (final c in chunks) _runChunk(rootPath, c, query, maxPerFile, useIsolates), - ]; + final futures = >>[for (final c in chunks) _runChunk(rootPath, c, query, maxPerFile, useIsolates)]; var emitted = 0; for (final f in futures) { @@ -104,13 +102,7 @@ void _drain(List>> futures) { } } -Future> _runChunk( - String rootPath, - List paths, - SearchQuery query, - int maxPerFile, - bool useIsolates, -) { +Future> _runChunk(String rootPath, List paths, SearchQuery query, int maxPerFile, bool useIsolates) { if (useIsolates) { return Isolate.run(() => grepChunk(rootPath, paths, query, maxPerFile)); } @@ -133,12 +125,7 @@ List> _chunk(List items, int buckets) { /// Grep a chunk of files. Runs in a worker isolate (or in-process for /// tests). Reads each file, skips binaries, and collects up to /// [maxPerFile] matches per file. -List grepChunk( - String rootPath, - List relPaths, - SearchQuery query, - int maxPerFile, -) { +List grepChunk(String rootPath, List relPaths, SearchQuery query, int maxPerFile) { final compiled = CompiledQuery(query); final out = []; for (final rel in relPaths) { @@ -160,25 +147,13 @@ List grepChunk( /// Match [content]'s lines, appending up to [maxPerFile] hits to [out]. /// Exposed (with a pre-built [compiled]) for unit testing without I/O. -void grepContent( - String relPath, - String content, - CompiledQuery compiled, - int maxPerFile, - List out, -) { +void grepContent(String relPath, String content, CompiledQuery compiled, int maxPerFile, List out) { var lineNo = 0; final added0 = out.length; for (final line in const LineSplitter().convert(content)) { lineNo++; for (final span in compiled.matches(line)) { - out.add(SearchMatch( - path: relPath, - line: lineNo, - matchStart: span.$1, - matchEnd: span.$2, - preview: line.length > 500 ? line.substring(0, 500) : line, - )); + out.add(SearchMatch(path: relPath, line: lineNo, matchStart: span.$1, matchEnd: span.$2, preview: line.length > 500 ? line.substring(0, 500) : line)); if (out.length - added0 >= maxPerFile) return; } } @@ -188,9 +163,9 @@ void grepContent( /// matcher with an optional case-insensitive fast-path. class CompiledQuery { CompiledQuery(SearchQuery q) - : _regex = q.regex ? RegExp(q.pattern, caseSensitive: !q.ignoreCase) : null, - _needle = q.regex ? '' : (q.ignoreCase ? q.pattern.toLowerCase() : q.pattern), - _ignoreCase = q.ignoreCase; + : _regex = q.regex ? RegExp(q.pattern, caseSensitive: !q.ignoreCase) : null, + _needle = q.regex ? '' : (q.ignoreCase ? q.pattern.toLowerCase() : q.pattern), + _ignoreCase = q.ignoreCase; final RegExp? _regex; final String _needle; diff --git a/lib/src/search/match.dart b/lib/src/search/match.dart index c30124b4..258393b0 100644 --- a/lib/src/search/match.dart +++ b/lib/src/search/match.dart @@ -9,13 +9,7 @@ library; /// result, navigate to the line, and (for replace, T-53) address the /// matched span within the line. class SearchMatch { - const SearchMatch({ - required this.path, - required this.line, - required this.matchStart, - required this.matchEnd, - required this.preview, - }); + const SearchMatch({required this.path, required this.line, required this.matchStart, required this.matchEnd, required this.preview}); /// Repo-relative, forward-slashed path of the file. final String path; @@ -32,32 +26,20 @@ class SearchMatch { /// The matched line's text (capped for transport/render). final String preview; - Map toJson() => { - 'path': path, - 'line': line, - 'matchStart': matchStart, - 'matchEnd': matchEnd, - 'preview': preview, - }; + Map toJson() => {'path': path, 'line': line, 'matchStart': matchStart, 'matchEnd': matchEnd, 'preview': preview}; factory SearchMatch.fromJson(Map j) => SearchMatch( - path: j['path'] as String, - line: (j['line'] as num).toInt(), - matchStart: (j['matchStart'] as num).toInt(), - matchEnd: (j['matchEnd'] as num).toInt(), - preview: j['preview'] as String, - ); + path: j['path'] as String, + line: (j['line'] as num).toInt(), + matchStart: (j['matchStart'] as num).toInt(), + matchEnd: (j['matchEnd'] as num).toInt(), + preview: j['preview'] as String, + ); } /// Parameters for a workspace search. class SearchQuery { - const SearchQuery({ - required this.pattern, - this.regex = false, - this.ignoreCase = false, - this.include = const [], - this.exclude = const [], - }); + const SearchQuery({required this.pattern, this.regex = false, this.ignoreCase = false, this.include = const [], this.exclude = const []}); /// The literal text (when [regex] is false) or regular expression /// source (when true) to search for. @@ -71,21 +53,15 @@ class SearchQuery { /// Glob patterns; matching paths are skipped (applied after [include]). final List exclude; - Map toJson() => { - 'pattern': pattern, - 'regex': regex, - 'ignoreCase': ignoreCase, - 'include': include, - 'exclude': exclude, - }; + Map toJson() => {'pattern': pattern, 'regex': regex, 'ignoreCase': ignoreCase, 'include': include, 'exclude': exclude}; factory SearchQuery.fromJson(Map j) => SearchQuery( - pattern: (j['pattern'] as String?) ?? '', - regex: j['regex'] == true, - ignoreCase: j['ignoreCase'] == true, - include: _strings(j['include']), - exclude: _strings(j['exclude']), - ); + pattern: (j['pattern'] as String?) ?? '', + regex: j['regex'] == true, + ignoreCase: j['ignoreCase'] == true, + include: _strings(j['include']), + exclude: _strings(j['exclude']), + ); static List _strings(Object? v) => v is List ? [for (final e in v) '$e'] : const []; } diff --git a/lib/src/search/replace_engine.dart b/lib/src/search/replace_engine.dart index effe79ed..2dd38831 100644 --- a/lib/src/search/replace_engine.dart +++ b/lib/src/search/replace_engine.dart @@ -28,11 +28,8 @@ class ReplacementEdit { Map toJson() => {'line': line, 'before': before, 'after': after}; - factory ReplacementEdit.fromJson(Map j) => ReplacementEdit( - line: (j['line'] as num).toInt(), - before: j['before'] as String, - after: j['after'] as String, - ); + factory ReplacementEdit.fromJson(Map j) => + ReplacementEdit(line: (j['line'] as num).toInt(), before: j['before'] as String, after: j['after'] as String); } /// The set of edits a replacement would make to one file. @@ -46,18 +43,16 @@ class FileReplacement { final List edits; Map toJson() => { - 'path': path, - 'count': count, - 'edits': [for (final e in edits) e.toJson()], - }; + 'path': path, + 'count': count, + 'edits': [for (final e in edits) e.toJson()], + }; factory FileReplacement.fromJson(Map j) => FileReplacement( - path: j['path'] as String, - count: (j['count'] as num).toInt(), - edits: [ - for (final e in (j['edits'] as List? ?? const []).whereType()) ReplacementEdit.fromJson(e.cast()), - ], - ); + path: j['path'] as String, + count: (j['count'] as num).toInt(), + edits: [for (final e in (j['edits'] as List? ?? const []).whereType()) ReplacementEdit.fromJson(e.cast())], + ); } /// Apply [query]'s pattern to [text], substituting [replacement]. Returns diff --git a/lib/src/terminal/src/core/buffer/buffer.dart b/lib/src/terminal/src/core/buffer/buffer.dart index e5d2d635..22b3287f 100644 --- a/lib/src/terminal/src/core/buffer/buffer.dart +++ b/lib/src/terminal/src/core/buffer/buffer.dart @@ -24,12 +24,7 @@ class Buffer { /// defaults to [defaultWordSeparators]. final Set? wordSeparators; - Buffer( - this.terminal, { - required this.maxLines, - required this.isAltBuffer, - this.wordSeparators, - }) { + Buffer(this.terminal, {required this.maxLines, required this.isAltBuffer, this.wordSeparators}) { for (int i = 0; i < terminal.viewHeight; i++) { lines.push(_newEmptyLine()); } @@ -549,19 +544,13 @@ class Buffer { return null; } - return BufferRangeLine( - CellOffset(start, position.y), - CellOffset(end, position.y), - ); + return BufferRangeLine(CellOffset(start, position.y), CellOffset(end, position.y)); } /// Get the plain text content of the buffer including the scrollback. /// Accepts an optional [range] to get a specific part of the buffer. String getText([BufferRange? range]) { - range ??= BufferRangeLine( - CellOffset(0, 0), - CellOffset(viewWidth - 1, height - 1), - ); + range ??= BufferRangeLine(CellOffset(0, 0), CellOffset(viewWidth - 1, height - 1)); range = range.normalized; diff --git a/lib/src/terminal/src/core/buffer/line.dart b/lib/src/terminal/src/core/buffer/line.dart index 27e8da31..e5b4d7d3 100644 --- a/lib/src/terminal/src/core/buffer/line.dart +++ b/lib/src/terminal/src/core/buffer/line.dart @@ -20,10 +20,7 @@ const _cellAttributes = 2; const _cellContent = 3; class BufferLine with IndexedItem { - BufferLine( - this._length, { - this.isWrapped = false, - }) : _data = Uint32List(_calcCapacity(_length) * _cellSize); + BufferLine(this._length, {this.isWrapped = false}) : _data = Uint32List(_calcCapacity(_length) * _cellSize); int _length; @@ -373,9 +370,7 @@ class BufferLine with IndexedItem { /// of the cell. Anchors are guaranteed to be stable, retaining their relative /// position to each other after mutations to the buffer. class CellAnchor { - CellAnchor(int offset, {BufferLine? owner}) - : _offset = offset, - _owner = owner; + CellAnchor(int offset, {BufferLine? owner}) : _offset = offset, _owner = owner; int _offset; diff --git a/lib/src/terminal/src/core/buffer/range_block.dart b/lib/src/terminal/src/core/buffer/range_block.dart index 427c6fdb..d2aa9e94 100644 --- a/lib/src/terminal/src/core/buffer/range_block.dart +++ b/lib/src/terminal/src/core/buffer/range_block.dart @@ -81,14 +81,8 @@ class BufferRangeBlock extends BufferRange { // Otherwise normalize the block and push the borders outside up to // the position to which the block has to extended. final normal = normalized; - final extendBegin = CellOffset( - min(normal.begin.x, position.x), - min(normal.begin.y, position.y), - ); - final extendEnd = CellOffset( - max(normal.end.x, position.x), - max(normal.end.y, position.y), - ); + final extendBegin = CellOffset(min(normal.begin.x, position.x), min(normal.begin.y, position.y)); + final extendEnd = CellOffset(max(normal.end.x, position.x), max(normal.end.y, position.y)); return BufferRangeBlock(extendBegin, extendEnd); } diff --git a/lib/src/terminal/src/core/cell.dart b/lib/src/terminal/src/core/cell.dart index 611ca64e..dcac054c 100644 --- a/lib/src/terminal/src/core/cell.dart +++ b/lib/src/terminal/src/core/cell.dart @@ -3,20 +3,10 @@ import 'package:clide/src/terminal/src/utils/hash_values.dart'; class CellData { - CellData({ - required this.foreground, - required this.background, - required this.flags, - required this.content, - }); + CellData({required this.foreground, required this.background, required this.flags, required this.content}); factory CellData.empty() { - return CellData( - foreground: 0, - background: 0, - flags: 0, - content: 0, - ); + return CellData(foreground: 0, background: 0, flags: 0, content: 0); } int foreground; diff --git a/lib/src/terminal/src/core/charset.dart b/lib/src/terminal/src/core/charset.dart index 9b22a168..17689297 100644 --- a/lib/src/terminal/src/core/charset.dart +++ b/lib/src/terminal/src/core/charset.dart @@ -2,10 +2,7 @@ typedef CharsetTranslator = int Function(int); -final _charsets = { - '0'.codeUnitAt(0): decSpecGraphicsTranslator, - 'B'.codeUnitAt(0): asciiTranslator, -}; +final _charsets = {'0'.codeUnitAt(0): decSpecGraphicsTranslator, 'B'.codeUnitAt(0): asciiTranslator}; class Charset { var _charsetMap = {}; diff --git a/lib/src/terminal/src/core/input/handler.dart b/lib/src/terminal/src/core/input/handler.dart index 02f5351d..67e5e12e 100644 --- a/lib/src/terminal/src/core/input/handler.dart +++ b/lib/src/terminal/src/core/input/handler.dart @@ -95,11 +95,7 @@ class CascadeInputHandler implements TerminalInputHandler { /// /// See also: /// * [CascadeInputHandler] -const defaultInputHandler = CascadeInputHandler([ - KeytabInputHandler(), - CtrlInputHandler(), - AltInputHandler(), -]); +const defaultInputHandler = CascadeInputHandler([KeytabInputHandler(), CtrlInputHandler(), AltInputHandler()]); /// A [TerminalInputHandler] that translates key events according to a keytab /// file. If no keytab is provided, [Keytab.defaultKeytab] is used. diff --git a/lib/src/terminal/src/core/input/keys.dart b/lib/src/terminal/src/core/input/keys.dart index 9dd20f2c..e465e85e 100644 --- a/lib/src/terminal/src/core/input/keys.dart +++ b/lib/src/terminal/src/core/input/keys.dart @@ -831,7 +831,6 @@ enum TerminalKey { control, // Missing flutter keys. - backtab, returnKey, } diff --git a/lib/src/terminal/src/core/input/keytab/keytab.dart b/lib/src/terminal/src/core/input/keytab/keytab.dart index 82bd00e1..5eb6e578 100644 --- a/lib/src/terminal/src/core/input/keytab/keytab.dart +++ b/lib/src/terminal/src/core/input/keytab/keytab.dart @@ -7,10 +7,7 @@ import 'package:clide/src/terminal/src/core/input/keytab/keytab_record.dart'; import 'package:clide/src/terminal/src/core/input/keytab/keytab_token.dart'; class Keytab { - Keytab({ - required this.name, - required this.records, - }); + Keytab({required this.name, required this.records}); factory Keytab.parse(String source) { final tokens = tokenize(source).toList(); diff --git a/lib/src/terminal/src/core/input/keytab/keytab_record.dart b/lib/src/terminal/src/core/input/keytab/keytab_record.dart index be48649e..86b5c08f 100644 --- a/lib/src/terminal/src/core/input/keytab/keytab_record.dart +++ b/lib/src/terminal/src/core/input/keytab/keytab_record.dart @@ -3,10 +3,7 @@ import 'package:clide/src/terminal/src/core/input/keys.dart'; import 'package:clide/src/terminal/src/core/input/keytab/keytab_escape.dart'; -enum KeytabActionType { - input, - shortcut, -} +enum KeytabActionType { input, shortcut } class KeytabAction { KeytabAction(this.type, this.value); diff --git a/lib/src/terminal/src/core/input/keytab/keytab_token.dart b/lib/src/terminal/src/core/input/keytab/keytab_token.dart index bb4cf9c4..9548ef7d 100644 --- a/lib/src/terminal/src/core/input/keytab/keytab_token.dart +++ b/lib/src/terminal/src/core/input/keytab/keytab_token.dart @@ -2,16 +2,7 @@ import 'dart:math' show min; -enum KeytabTokenType { - keyDefine, - keyboard, - keyName, - mode, - modeStatus, - colon, - input, - shortcut, -} +enum KeytabTokenType { keyDefine, keyboard, keyName, mode, modeStatus, colon, input, shortcut } class KeytabToken { KeytabToken(this.type, this.value); diff --git a/lib/src/terminal/src/core/input/keytab/qt_keyname.dart b/lib/src/terminal/src/core/input/keytab/qt_keyname.dart index 2a70962d..8f5fc247 100644 --- a/lib/src/terminal/src/core/input/keytab/qt_keyname.dart +++ b/lib/src/terminal/src/core/input/keytab/qt_keyname.dart @@ -15,7 +15,7 @@ const qtKeynameMap = { 'Delete': TerminalKey.delete, 'Pause': TerminalKey.pause, 'Print': TerminalKey.print, -// 'SysReq': TerminalKey.sysReq, + // 'SysReq': TerminalKey.sysReq, 'Clear': TerminalKey.numpadClear, 'Home': TerminalKey.home, 'End': TerminalKey.end, @@ -31,7 +31,7 @@ const qtKeynameMap = { 'Control': TerminalKey.control, 'Meta': TerminalKey.meta, 'Alt': TerminalKey.alt, -// 'AltGr': TerminalKey.altGr, + // 'AltGr': TerminalKey.altGr, 'CapsLock': TerminalKey.capsLock, 'NumLock': TerminalKey.numLock, 'ScrollLock': TerminalKey.scrollLock, @@ -59,25 +59,25 @@ const qtKeynameMap = { 'F22': TerminalKey.f22, 'F23': TerminalKey.f23, 'F24': TerminalKey.f24, -// 'F25': TerminalKey.f25, -// 'F26': TerminalKey.f26, -// 'F27': TerminalKey.f27, -// 'F28': TerminalKey.f28, -// 'F29': TerminalKey.f29, -// 'F30': TerminalKey.f30, -// 'F31': TerminalKey.f31, -// 'F32': TerminalKey.f32, -// 'F33': TerminalKey.f33, -// 'F34': TerminalKey.f34, -// 'F35': TerminalKey.f35, -// 'Super_L': TerminalKey.super_L, -// 'Super_R': TerminalKey.super_R, -// 'Menu': TerminalKey.menu, -// 'Hyper_L': TerminalKey.hyper_L, -// 'Hyper_R': TerminalKey.hyper_R, + // 'F25': TerminalKey.f25, + // 'F26': TerminalKey.f26, + // 'F27': TerminalKey.f27, + // 'F28': TerminalKey.f28, + // 'F29': TerminalKey.f29, + // 'F30': TerminalKey.f30, + // 'F31': TerminalKey.f31, + // 'F32': TerminalKey.f32, + // 'F33': TerminalKey.f33, + // 'F34': TerminalKey.f34, + // 'F35': TerminalKey.f35, + // 'Super_L': TerminalKey.super_L, + // 'Super_R': TerminalKey.super_R, + // 'Menu': TerminalKey.menu, + // 'Hyper_L': TerminalKey.hyper_L, + // 'Hyper_R': TerminalKey.hyper_R, 'Help': TerminalKey.help, -// 'Direction_L': TerminalKey.direction_L, -// 'Direction_R': TerminalKey.direction_R, + // 'Direction_L': TerminalKey.direction_L, + // 'Direction_R': TerminalKey.direction_R, 'Space': TerminalKey.space, // 'Any': TerminalKey.any, // 'Exclam': TerminalKey.exclam, @@ -105,13 +105,13 @@ const qtKeynameMap = { '7': TerminalKey.digit7, '8': TerminalKey.digit8, '9': TerminalKey.digit9, -// 'Colon': TerminalKey.colon, + // 'Colon': TerminalKey.colon, 'Semicolon': TerminalKey.semicolon, -// 'Less': TerminalKey.less, -// 'Equal': TerminalKey.equal, -// 'Greater': TerminalKey.greater, -// 'Question': TerminalKey.question, -// 'At': TerminalKey.at, + // 'Less': TerminalKey.less, + // 'Equal': TerminalKey.equal, + // 'Greater': TerminalKey.greater, + // 'Question': TerminalKey.question, + // 'At': TerminalKey.at, 'A': TerminalKey.keyA, 'B': TerminalKey.keyB, 'C': TerminalKey.keyC, @@ -141,339 +141,339 @@ const qtKeynameMap = { 'BracketLeft': TerminalKey.bracketLeft, 'Backslash': TerminalKey.backslash, 'BracketRight': TerminalKey.bracketRight, -// 'AsciiCircum': TerminalKey.asciiCircum, + // 'AsciiCircum': TerminalKey.asciiCircum, // 'Underscore': TerminalKey.underscore, // 'QuoteLeft': TerminalKey.quoteLeft, -// 'BraceLeft': TerminalKey.braceLeft, + // 'BraceLeft': TerminalKey.braceLeft, // 'Bar': TerminalKey.bar, -// 'BraceRight': TerminalKey.braceRight, + // 'BraceRight': TerminalKey.braceRight, // 'AsciiTilde': TerminalKey.asciiTilde, -// 'nobreakspace': TerminalKey.nobreakspace, -// 'exclamdown': TerminalKey.exclamdown, -// 'cent': TerminalKey.cent, -// 'sterling': TerminalKey.sterling, -// 'currency': TerminalKey.currency, -// 'yen': TerminalKey.yen, -// 'brokenbar': TerminalKey.brokenbar, -// 'section': TerminalKey.section, -// 'diaeresis': TerminalKey.diaeresis, -// 'copyright': TerminalKey.copyright, -// 'ordfeminine': TerminalKey.ordfeminine, -// 'guillemotleft': TerminalKey.guillemotleft, -// 'notsign': TerminalKey.notsign, -// 'hyphen': TerminalKey.hyphen, -// 'registered': TerminalKey.registered, -// 'macron': TerminalKey.macron, -// 'degree': TerminalKey.degree, -// 'plusminus': TerminalKey.plusminus, -// 'twosuperior': TerminalKey.twosuperior, -// 'threesuperior': TerminalKey.threesuperior, -// 'acute': TerminalKey.acute, -// // 'mu': TerminalKey.mu, -// 'paragraph': TerminalKey.paragraph, -// 'periodcentered': TerminalKey.periodcentered, -// 'cedilla': TerminalKey.cedilla, -// 'onesuperior': TerminalKey.onesuperior, -// 'masculine': TerminalKey.masculine, -// 'guillemotright': TerminalKey.guillemotright, -// 'onequarter': TerminalKey.onequarter, -// 'onehalf': TerminalKey.onehalf, -// 'threequarters': TerminalKey.threequarters, -// 'questiondown': TerminalKey.questiondown, -// 'Agrave': TerminalKey.agrave, -// 'Aacute': TerminalKey.aacute, -// 'Acircumflex': TerminalKey.acircumflex, -// 'Atilde': TerminalKey.atilde, -// 'Adiaeresis': TerminalKey.adiaeresis, -// 'Aring': TerminalKey.aring, -// 'AE': TerminalKey.aE, -// 'Ccedilla': TerminalKey.ccedilla, -// 'Egrave': TerminalKey.egrave, -// 'Eacute': TerminalKey.eacute, -// 'Ecircumflex': TerminalKey.ecircumflex, -// 'Ediaeresis': TerminalKey.ediaeresis, -// 'Igrave': TerminalKey.igrave, -// 'Iacute': TerminalKey.iacute, -// 'Icircumflex': TerminalKey.icircumflex, -// 'Idiaeresis': TerminalKey.idiaeresis, -// 'ETH': TerminalKey.eTH, -// 'Ntilde': TerminalKey.ntilde, -// 'Ograve': TerminalKey.ograve, -// 'Oacute': TerminalKey.oacute, -// 'Ocircumflex': TerminalKey.ocircumflex, -// 'Otilde': TerminalKey.otilde, -// 'Odiaeresis': TerminalKey.odiaeresis, -// 'multiply': TerminalKey.multiply, -// 'Ooblique': TerminalKey.ooblique, -// 'Ugrave': TerminalKey.ugrave, -// 'Uacute': TerminalKey.uacute, -// 'Ucircumflex': TerminalKey.ucircumflex, -// 'Udiaeresis': TerminalKey.udiaeresis, -// 'Yacute': TerminalKey.yacute, -// 'THORN': TerminalKey.tHORN, -// 'ssharp': TerminalKey.ssharp, -// 'division': TerminalKey.division, -// 'ydiaeresis': TerminalKey.ydiaeresis, -// 'Multi_key': TerminalKey.multi_key, -// 'Codeinput': TerminalKey.codeinput, -// 'SingleCandidate': TerminalKey.singleCandidate, -// 'MultipleCandidate': TerminalKey.multipleCandidate, -// 'PreviousCandidate': TerminalKey.previousCandidate, -// 'Mode_switch': TerminalKey.mode_switch, -// 'Kanji': TerminalKey.kanji, -// 'Muhenkan': TerminalKey.muhenkan, -// 'Henkan': TerminalKey.henkan, -// 'Romaji': TerminalKey.romaji, -// 'Hiragana': TerminalKey.hiragana, -// 'Katakana': TerminalKey.katakana, -// 'Hiragana_Katakana': TerminalKey.hiragana_Katakana, -// 'Zenkaku': TerminalKey.zenkaku, -// 'Hankaku': TerminalKey.hankaku, -// 'Zenkaku_Hankaku': TerminalKey.zenkaku_Hankaku, -// 'Touroku': TerminalKey.touroku, -// 'Massyo': TerminalKey.massyo, -// 'Kana_Lock': TerminalKey.kana_Lock, -// 'Kana_Shift': TerminalKey.kana_Shift, -// 'Eisu_Shift': TerminalKey.eisu_Shift, -// 'Eisu_toggle': TerminalKey.eisu_toggle, -// 'Hangul': TerminalKey.hangul, -// 'Hangul_Start': TerminalKey.hangul_Start, -// 'Hangul_End': TerminalKey.hangul_End, -// 'Hangul_Hanja': TerminalKey.hangul_Hanja, -// 'Hangul_Jamo': TerminalKey.hangul_Jamo, -// 'Hangul_Romaja': TerminalKey.hangul_Romaja, -// 'Hangul_Jeonja': TerminalKey.hangul_Jeonja, -// 'Hangul_Banja': TerminalKey.hangul_Banja, -// 'Hangul_PreHanja': TerminalKey.hangul_PreHanja, -// 'Hangul_PostHanja': TerminalKey.hangul_PostHanja, -// 'Hangul_Special': TerminalKey.hangul_Special, -// 'Dead_Grave': TerminalKey.dead_Grave, -// 'Dead_Acute': TerminalKey.dead_Acute, -// 'Dead_Circumflex': TerminalKey.dead_Circumflex, -// 'Dead_Tilde': TerminalKey.dead_Tilde, -// 'Dead_Macron': TerminalKey.dead_Macron, -// 'Dead_Breve': TerminalKey.dead_Breve, -// 'Dead_Abovedot': TerminalKey.dead_Abovedot, -// 'Dead_Diaeresis': TerminalKey.dead_Diaeresis, -// 'Dead_Abovering': TerminalKey.dead_Abovering, -// 'Dead_Doubleacute': TerminalKey.dead_Doubleacute, -// 'Dead_Caron': TerminalKey.dead_Caron, -// 'Dead_Cedilla': TerminalKey.dead_Cedilla, -// 'Dead_Ogonek': TerminalKey.dead_Ogonek, -// 'Dead_Iota': TerminalKey.dead_Iota, -// 'Dead_Voiced_Sound': TerminalKey.dead_Voiced_Sound, -// 'Dead_Semivoiced_Sound': TerminalKey.dead_Semivoiced_Sound, -// 'Dead_Belowdot': TerminalKey.dead_Belowdot, -// 'Dead_Hook': TerminalKey.dead_Hook, -// 'Dead_Horn': TerminalKey.dead_Horn, -// 'Dead_Stroke': TerminalKey.dead_Stroke, -// 'Dead_Abovecomma': TerminalKey.dead_Abovecomma, -// 'Dead_Abovereversedcomma': TerminalKey.dead_Abovereversedcomma, -// 'Dead_Doublegrave': TerminalKey.dead_Doublegrave, -// 'Dead_Belowring': TerminalKey.dead_Belowring, -// 'Dead_Belowmacron': TerminalKey.dead_Belowmacron, -// 'Dead_Belowcircumflex': TerminalKey.dead_Belowcircumflex, -// 'Dead_Belowtilde': TerminalKey.dead_Belowtilde, -// 'Dead_Belowbreve': TerminalKey.dead_Belowbreve, -// 'Dead_Belowdiaeresis': TerminalKey.dead_Belowdiaeresis, -// 'Dead_Invertedbreve': TerminalKey.dead_Invertedbreve, -// 'Dead_Belowcomma': TerminalKey.dead_Belowcomma, -// 'Dead_Currency': TerminalKey.dead_Currency, -// 'Dead_a': TerminalKey.dead_a, -// 'Dead_A': TerminalKey.dead_A, -// 'Dead_e': TerminalKey.dead_e, -// 'Dead_E': TerminalKey.dead_E, -// 'Dead_i': TerminalKey.dead_i, -// 'Dead_I': TerminalKey.dead_I, -// 'Dead_o': TerminalKey.dead_o, -// 'Dead_O': TerminalKey.dead_O, -// 'Dead_u': TerminalKey.dead_u, -// 'Dead_U': TerminalKey.dead_U, -// 'Dead_Small_Schwa': TerminalKey.dead_Small_Schwa, -// 'Dead_Capital_Schwa': TerminalKey.dead_Capital_Schwa, -// 'Dead_Greek': TerminalKey.dead_Greek, -// 'Dead_Lowline': TerminalKey.dead_Lowline, -// 'Dead_Aboveverticalline': TerminalKey.dead_Aboveverticalline, -// 'Dead_Belowverticalline': TerminalKey.dead_Belowverticalline, -// 'Dead_Longsolidusoverlay': TerminalKey.dead_Longsolidusoverlay, -// 'Back': TerminalKey.back, -// 'Forward': TerminalKey.forward, -// 'Stop': TerminalKey.stop, -// 'Refresh': TerminalKey.refresh, + // 'nobreakspace': TerminalKey.nobreakspace, + // 'exclamdown': TerminalKey.exclamdown, + // 'cent': TerminalKey.cent, + // 'sterling': TerminalKey.sterling, + // 'currency': TerminalKey.currency, + // 'yen': TerminalKey.yen, + // 'brokenbar': TerminalKey.brokenbar, + // 'section': TerminalKey.section, + // 'diaeresis': TerminalKey.diaeresis, + // 'copyright': TerminalKey.copyright, + // 'ordfeminine': TerminalKey.ordfeminine, + // 'guillemotleft': TerminalKey.guillemotleft, + // 'notsign': TerminalKey.notsign, + // 'hyphen': TerminalKey.hyphen, + // 'registered': TerminalKey.registered, + // 'macron': TerminalKey.macron, + // 'degree': TerminalKey.degree, + // 'plusminus': TerminalKey.plusminus, + // 'twosuperior': TerminalKey.twosuperior, + // 'threesuperior': TerminalKey.threesuperior, + // 'acute': TerminalKey.acute, + // // 'mu': TerminalKey.mu, + // 'paragraph': TerminalKey.paragraph, + // 'periodcentered': TerminalKey.periodcentered, + // 'cedilla': TerminalKey.cedilla, + // 'onesuperior': TerminalKey.onesuperior, + // 'masculine': TerminalKey.masculine, + // 'guillemotright': TerminalKey.guillemotright, + // 'onequarter': TerminalKey.onequarter, + // 'onehalf': TerminalKey.onehalf, + // 'threequarters': TerminalKey.threequarters, + // 'questiondown': TerminalKey.questiondown, + // 'Agrave': TerminalKey.agrave, + // 'Aacute': TerminalKey.aacute, + // 'Acircumflex': TerminalKey.acircumflex, + // 'Atilde': TerminalKey.atilde, + // 'Adiaeresis': TerminalKey.adiaeresis, + // 'Aring': TerminalKey.aring, + // 'AE': TerminalKey.aE, + // 'Ccedilla': TerminalKey.ccedilla, + // 'Egrave': TerminalKey.egrave, + // 'Eacute': TerminalKey.eacute, + // 'Ecircumflex': TerminalKey.ecircumflex, + // 'Ediaeresis': TerminalKey.ediaeresis, + // 'Igrave': TerminalKey.igrave, + // 'Iacute': TerminalKey.iacute, + // 'Icircumflex': TerminalKey.icircumflex, + // 'Idiaeresis': TerminalKey.idiaeresis, + // 'ETH': TerminalKey.eTH, + // 'Ntilde': TerminalKey.ntilde, + // 'Ograve': TerminalKey.ograve, + // 'Oacute': TerminalKey.oacute, + // 'Ocircumflex': TerminalKey.ocircumflex, + // 'Otilde': TerminalKey.otilde, + // 'Odiaeresis': TerminalKey.odiaeresis, + // 'multiply': TerminalKey.multiply, + // 'Ooblique': TerminalKey.ooblique, + // 'Ugrave': TerminalKey.ugrave, + // 'Uacute': TerminalKey.uacute, + // 'Ucircumflex': TerminalKey.ucircumflex, + // 'Udiaeresis': TerminalKey.udiaeresis, + // 'Yacute': TerminalKey.yacute, + // 'THORN': TerminalKey.tHORN, + // 'ssharp': TerminalKey.ssharp, + // 'division': TerminalKey.division, + // 'ydiaeresis': TerminalKey.ydiaeresis, + // 'Multi_key': TerminalKey.multi_key, + // 'Codeinput': TerminalKey.codeinput, + // 'SingleCandidate': TerminalKey.singleCandidate, + // 'MultipleCandidate': TerminalKey.multipleCandidate, + // 'PreviousCandidate': TerminalKey.previousCandidate, + // 'Mode_switch': TerminalKey.mode_switch, + // 'Kanji': TerminalKey.kanji, + // 'Muhenkan': TerminalKey.muhenkan, + // 'Henkan': TerminalKey.henkan, + // 'Romaji': TerminalKey.romaji, + // 'Hiragana': TerminalKey.hiragana, + // 'Katakana': TerminalKey.katakana, + // 'Hiragana_Katakana': TerminalKey.hiragana_Katakana, + // 'Zenkaku': TerminalKey.zenkaku, + // 'Hankaku': TerminalKey.hankaku, + // 'Zenkaku_Hankaku': TerminalKey.zenkaku_Hankaku, + // 'Touroku': TerminalKey.touroku, + // 'Massyo': TerminalKey.massyo, + // 'Kana_Lock': TerminalKey.kana_Lock, + // 'Kana_Shift': TerminalKey.kana_Shift, + // 'Eisu_Shift': TerminalKey.eisu_Shift, + // 'Eisu_toggle': TerminalKey.eisu_toggle, + // 'Hangul': TerminalKey.hangul, + // 'Hangul_Start': TerminalKey.hangul_Start, + // 'Hangul_End': TerminalKey.hangul_End, + // 'Hangul_Hanja': TerminalKey.hangul_Hanja, + // 'Hangul_Jamo': TerminalKey.hangul_Jamo, + // 'Hangul_Romaja': TerminalKey.hangul_Romaja, + // 'Hangul_Jeonja': TerminalKey.hangul_Jeonja, + // 'Hangul_Banja': TerminalKey.hangul_Banja, + // 'Hangul_PreHanja': TerminalKey.hangul_PreHanja, + // 'Hangul_PostHanja': TerminalKey.hangul_PostHanja, + // 'Hangul_Special': TerminalKey.hangul_Special, + // 'Dead_Grave': TerminalKey.dead_Grave, + // 'Dead_Acute': TerminalKey.dead_Acute, + // 'Dead_Circumflex': TerminalKey.dead_Circumflex, + // 'Dead_Tilde': TerminalKey.dead_Tilde, + // 'Dead_Macron': TerminalKey.dead_Macron, + // 'Dead_Breve': TerminalKey.dead_Breve, + // 'Dead_Abovedot': TerminalKey.dead_Abovedot, + // 'Dead_Diaeresis': TerminalKey.dead_Diaeresis, + // 'Dead_Abovering': TerminalKey.dead_Abovering, + // 'Dead_Doubleacute': TerminalKey.dead_Doubleacute, + // 'Dead_Caron': TerminalKey.dead_Caron, + // 'Dead_Cedilla': TerminalKey.dead_Cedilla, + // 'Dead_Ogonek': TerminalKey.dead_Ogonek, + // 'Dead_Iota': TerminalKey.dead_Iota, + // 'Dead_Voiced_Sound': TerminalKey.dead_Voiced_Sound, + // 'Dead_Semivoiced_Sound': TerminalKey.dead_Semivoiced_Sound, + // 'Dead_Belowdot': TerminalKey.dead_Belowdot, + // 'Dead_Hook': TerminalKey.dead_Hook, + // 'Dead_Horn': TerminalKey.dead_Horn, + // 'Dead_Stroke': TerminalKey.dead_Stroke, + // 'Dead_Abovecomma': TerminalKey.dead_Abovecomma, + // 'Dead_Abovereversedcomma': TerminalKey.dead_Abovereversedcomma, + // 'Dead_Doublegrave': TerminalKey.dead_Doublegrave, + // 'Dead_Belowring': TerminalKey.dead_Belowring, + // 'Dead_Belowmacron': TerminalKey.dead_Belowmacron, + // 'Dead_Belowcircumflex': TerminalKey.dead_Belowcircumflex, + // 'Dead_Belowtilde': TerminalKey.dead_Belowtilde, + // 'Dead_Belowbreve': TerminalKey.dead_Belowbreve, + // 'Dead_Belowdiaeresis': TerminalKey.dead_Belowdiaeresis, + // 'Dead_Invertedbreve': TerminalKey.dead_Invertedbreve, + // 'Dead_Belowcomma': TerminalKey.dead_Belowcomma, + // 'Dead_Currency': TerminalKey.dead_Currency, + // 'Dead_a': TerminalKey.dead_a, + // 'Dead_A': TerminalKey.dead_A, + // 'Dead_e': TerminalKey.dead_e, + // 'Dead_E': TerminalKey.dead_E, + // 'Dead_i': TerminalKey.dead_i, + // 'Dead_I': TerminalKey.dead_I, + // 'Dead_o': TerminalKey.dead_o, + // 'Dead_O': TerminalKey.dead_O, + // 'Dead_u': TerminalKey.dead_u, + // 'Dead_U': TerminalKey.dead_U, + // 'Dead_Small_Schwa': TerminalKey.dead_Small_Schwa, + // 'Dead_Capital_Schwa': TerminalKey.dead_Capital_Schwa, + // 'Dead_Greek': TerminalKey.dead_Greek, + // 'Dead_Lowline': TerminalKey.dead_Lowline, + // 'Dead_Aboveverticalline': TerminalKey.dead_Aboveverticalline, + // 'Dead_Belowverticalline': TerminalKey.dead_Belowverticalline, + // 'Dead_Longsolidusoverlay': TerminalKey.dead_Longsolidusoverlay, + // 'Back': TerminalKey.back, + // 'Forward': TerminalKey.forward, + // 'Stop': TerminalKey.stop, + // 'Refresh': TerminalKey.refresh, 'VolumeDown': TerminalKey.audioVolumeDown, 'VolumeMute': TerminalKey.audioVolumeMute, 'VolumeUp': TerminalKey.audioVolumeUp, 'BassBoost': TerminalKey.bassBoost, -// 'BassUp': TerminalKey.bassUp, -// 'BassDown': TerminalKey.bassDown, -// 'TrebleUp': TerminalKey.trebleUp, -// 'TrebleDown': TerminalKey.trebleDown, + // 'BassUp': TerminalKey.bassUp, + // 'BassDown': TerminalKey.bassDown, + // 'TrebleUp': TerminalKey.trebleUp, + // 'TrebleDown': TerminalKey.trebleDown, 'MediaPlay': TerminalKey.mediaPlay, 'MediaStop': TerminalKey.mediaStop, -// 'MediaPrevious': TerminalKey.mediaPrevious, -// 'MediaNext': TerminalKey.mediaNext, + // 'MediaPrevious': TerminalKey.mediaPrevious, + // 'MediaNext': TerminalKey.mediaNext, 'MediaRecord': TerminalKey.mediaRecord, 'MediaPause': TerminalKey.mediaPause, 'MediaTogglePlayPause': TerminalKey.mediaPlayPause, 'HomePage': TerminalKey.browserHome, -// 'Favorites': TerminalKey.favorites, -// 'Search': TerminalKey.search, -// 'Standby': TerminalKey.standby, -// 'OpenUrl': TerminalKey.openUrl, -// 'LaunchMail': TerminalKey.launchMail, -// 'LaunchMedia': TerminalKey.launchMedia, -// 'Launch0': TerminalKey.launch0, -// 'Launch1': TerminalKey.launch1, -// 'Launch2': TerminalKey.launch2, -// 'Launch3': TerminalKey.launch3, -// 'Launch4': TerminalKey.launch4, -// 'Launch5': TerminalKey.launch5, -// 'Launch6': TerminalKey.launch6, -// 'Launch7': TerminalKey.launch7, -// 'Launch8': TerminalKey.launch8, -// 'Launch9': TerminalKey.launch9, -// 'LaunchA': TerminalKey.launchA, -// 'LaunchB': TerminalKey.launchB, -// 'LaunchC': TerminalKey.launchC, -// 'LaunchD': TerminalKey.launchD, -// 'LaunchE': TerminalKey.launchE, -// 'LaunchF': TerminalKey.launchF, -// 'LaunchG': TerminalKey.launchG, -// 'LaunchH': TerminalKey.launchH, + // 'Favorites': TerminalKey.favorites, + // 'Search': TerminalKey.search, + // 'Standby': TerminalKey.standby, + // 'OpenUrl': TerminalKey.openUrl, + // 'LaunchMail': TerminalKey.launchMail, + // 'LaunchMedia': TerminalKey.launchMedia, + // 'Launch0': TerminalKey.launch0, + // 'Launch1': TerminalKey.launch1, + // 'Launch2': TerminalKey.launch2, + // 'Launch3': TerminalKey.launch3, + // 'Launch4': TerminalKey.launch4, + // 'Launch5': TerminalKey.launch5, + // 'Launch6': TerminalKey.launch6, + // 'Launch7': TerminalKey.launch7, + // 'Launch8': TerminalKey.launch8, + // 'Launch9': TerminalKey.launch9, + // 'LaunchA': TerminalKey.launchA, + // 'LaunchB': TerminalKey.launchB, + // 'LaunchC': TerminalKey.launchC, + // 'LaunchD': TerminalKey.launchD, + // 'LaunchE': TerminalKey.launchE, + // 'LaunchF': TerminalKey.launchF, + // 'LaunchG': TerminalKey.launchG, + // 'LaunchH': TerminalKey.launchH, 'MonBrightnessUp': TerminalKey.brightnessUp, 'MonBrightnessDown': TerminalKey.brightnessDown, -// 'KeyboardLightOnOff': TerminalKey.keyboardLightOnOff, -// 'KeyboardBrightnessUp': TerminalKey.keyboardBrightnessUp, -// 'KeyboardBrightnessDown': TerminalKey.keyboardBrightnessDown, + // 'KeyboardLightOnOff': TerminalKey.keyboardLightOnOff, + // 'KeyboardBrightnessUp': TerminalKey.keyboardBrightnessUp, + // 'KeyboardBrightnessDown': TerminalKey.keyboardBrightnessDown, 'PowerOff': TerminalKey.power, 'WakeUp': TerminalKey.wakeUp, 'Eject': TerminalKey.eject, -// 'ScreenSaver': TerminalKey.screenSaver, -// 'WWW': TerminalKey.wWW, -// 'Memo': TerminalKey.memo, -// 'LightBulb': TerminalKey.lightBulb, -// 'Shop': TerminalKey.shop, -// 'History': TerminalKey.history, -// 'AddFavorite': TerminalKey.addFavorite, -// 'HotLinks': TerminalKey.hotLinks, -// 'BrightnessAdjust': TerminalKey.brightnessAdjust, -// 'Finance': TerminalKey.finance, -// 'Community': TerminalKey.community, -// 'AudioRewind': TerminalKey.audioRewind, -// 'BackForward': TerminalKey.backForward, -// 'ApplicationLeft': TerminalKey.applicationLeft, -// 'ApplicationRight': TerminalKey.applicationRight, -// 'Book': TerminalKey.book, -// 'CD': TerminalKey.cD, -// 'Calculator': TerminalKey.calculator, -// 'ToDoList': TerminalKey.toDoList, -// 'ClearGrab': TerminalKey.clearGrab, + // 'ScreenSaver': TerminalKey.screenSaver, + // 'WWW': TerminalKey.wWW, + // 'Memo': TerminalKey.memo, + // 'LightBulb': TerminalKey.lightBulb, + // 'Shop': TerminalKey.shop, + // 'History': TerminalKey.history, + // 'AddFavorite': TerminalKey.addFavorite, + // 'HotLinks': TerminalKey.hotLinks, + // 'BrightnessAdjust': TerminalKey.brightnessAdjust, + // 'Finance': TerminalKey.finance, + // 'Community': TerminalKey.community, + // 'AudioRewind': TerminalKey.audioRewind, + // 'BackForward': TerminalKey.backForward, + // 'ApplicationLeft': TerminalKey.applicationLeft, + // 'ApplicationRight': TerminalKey.applicationRight, + // 'Book': TerminalKey.book, + // 'CD': TerminalKey.cD, + // 'Calculator': TerminalKey.calculator, + // 'ToDoList': TerminalKey.toDoList, + // 'ClearGrab': TerminalKey.clearGrab, 'Close': TerminalKey.close, 'Copy': TerminalKey.copy, 'Cut': TerminalKey.cut, -// 'Display': TerminalKey.display, -// 'DOS': TerminalKey.dOS, -// 'Documents': TerminalKey.documents, -// 'Excel': TerminalKey.excel, -// 'Explorer': TerminalKey.explorer, -// 'Game': TerminalKey.game, -// 'Go': TerminalKey.go, -// 'iTouch': TerminalKey.iTouch, -// 'LogOff': TerminalKey.logOff, -// 'Market': TerminalKey.market, -// 'Meeting': TerminalKey.meeting, -// 'MenuKB': TerminalKey.menuKB, -// 'MenuPB': TerminalKey.menuPB, -// 'MySites': TerminalKey.mySites, -// 'News': TerminalKey.news, -// 'OfficeHome': TerminalKey.officeHome, -// 'Option': TerminalKey.option, -// 'Paste': TerminalKey.paste, -// 'Phone': TerminalKey.phone, -// 'Calendar': TerminalKey.calendar, -// 'Reply': TerminalKey.reply, -// 'Reload': TerminalKey.reload, -// 'RotateWindows': TerminalKey.rotateWindows, -// 'RotationPB': TerminalKey.rotationPB, -// 'RotationKB': TerminalKey.rotationKB, + // 'Display': TerminalKey.display, + // 'DOS': TerminalKey.dOS, + // 'Documents': TerminalKey.documents, + // 'Excel': TerminalKey.excel, + // 'Explorer': TerminalKey.explorer, + // 'Game': TerminalKey.game, + // 'Go': TerminalKey.go, + // 'iTouch': TerminalKey.iTouch, + // 'LogOff': TerminalKey.logOff, + // 'Market': TerminalKey.market, + // 'Meeting': TerminalKey.meeting, + // 'MenuKB': TerminalKey.menuKB, + // 'MenuPB': TerminalKey.menuPB, + // 'MySites': TerminalKey.mySites, + // 'News': TerminalKey.news, + // 'OfficeHome': TerminalKey.officeHome, + // 'Option': TerminalKey.option, + // 'Paste': TerminalKey.paste, + // 'Phone': TerminalKey.phone, + // 'Calendar': TerminalKey.calendar, + // 'Reply': TerminalKey.reply, + // 'Reload': TerminalKey.reload, + // 'RotateWindows': TerminalKey.rotateWindows, + // 'RotationPB': TerminalKey.rotationPB, + // 'RotationKB': TerminalKey.rotationKB, 'Save': TerminalKey.save, -// 'Send': TerminalKey.send, -// 'Spell': TerminalKey.spell, -// 'SplitScreen': TerminalKey.splitScreen, -// 'Support': TerminalKey.support, -// 'TaskPane': TerminalKey.taskPane, -// 'Terminal': TerminalKey.terminal, -// 'Tools': TerminalKey.tools, -// 'Travel': TerminalKey.travel, -// 'Video': TerminalKey.video, -// 'Word': TerminalKey.word, -// 'Xfer': TerminalKey.xfer, + // 'Send': TerminalKey.send, + // 'Spell': TerminalKey.spell, + // 'SplitScreen': TerminalKey.splitScreen, + // 'Support': TerminalKey.support, + // 'TaskPane': TerminalKey.taskPane, + // 'Terminal': TerminalKey.terminal, + // 'Tools': TerminalKey.tools, + // 'Travel': TerminalKey.travel, + // 'Video': TerminalKey.video, + // 'Word': TerminalKey.word, + // 'Xfer': TerminalKey.xfer, 'ZoomIn': TerminalKey.zoomIn, 'ZoomOut': TerminalKey.zoomOut, -// 'Away': TerminalKey.away, -// 'Messenger': TerminalKey.messenger, -// 'WebCam': TerminalKey.webCam, -// 'MailForward': TerminalKey.mailForward, -// 'Pictures': TerminalKey.pictures, -// 'Music': TerminalKey.music, -// 'Battery': TerminalKey.battery, -// 'Bluetooth': TerminalKey.bluetooth, -// 'WLAN': TerminalKey.wLAN, -// 'UWB': TerminalKey.uWB, -// 'AudioForward': TerminalKey.audioForward, -// 'AudioRepeat': TerminalKey.audioRepeat, -// 'AudioRandomPlay': TerminalKey.audioRandomPlay, -// 'Subtitle': TerminalKey.subtitle, -// 'AudioCycleTrack': TerminalKey.audioCycleTrack, -// 'Time': TerminalKey.time, -// 'Hibernate': TerminalKey.hibernate, -// 'View': TerminalKey.view, -// 'TopMenu': TerminalKey.topMenu, -// 'PowerDown': TerminalKey.powerDown, -// 'Suspend': TerminalKey.suspend, -// 'ContrastAdjust': TerminalKey.contrastAdjust, -// 'TouchpadToggle': TerminalKey.touchpadToggle, -// 'TouchpadOn': TerminalKey.touchpadOn, -// 'TouchpadOff': TerminalKey.touchpadOff, -// 'MicMute': TerminalKey.micMute, -// 'Red': TerminalKey.red, -// 'Green': TerminalKey.green, -// 'Yellow': TerminalKey.yellow, -// 'Blue': TerminalKey.blue, + // 'Away': TerminalKey.away, + // 'Messenger': TerminalKey.messenger, + // 'WebCam': TerminalKey.webCam, + // 'MailForward': TerminalKey.mailForward, + // 'Pictures': TerminalKey.pictures, + // 'Music': TerminalKey.music, + // 'Battery': TerminalKey.battery, + // 'Bluetooth': TerminalKey.bluetooth, + // 'WLAN': TerminalKey.wLAN, + // 'UWB': TerminalKey.uWB, + // 'AudioForward': TerminalKey.audioForward, + // 'AudioRepeat': TerminalKey.audioRepeat, + // 'AudioRandomPlay': TerminalKey.audioRandomPlay, + // 'Subtitle': TerminalKey.subtitle, + // 'AudioCycleTrack': TerminalKey.audioCycleTrack, + // 'Time': TerminalKey.time, + // 'Hibernate': TerminalKey.hibernate, + // 'View': TerminalKey.view, + // 'TopMenu': TerminalKey.topMenu, + // 'PowerDown': TerminalKey.powerDown, + // 'Suspend': TerminalKey.suspend, + // 'ContrastAdjust': TerminalKey.contrastAdjust, + // 'TouchpadToggle': TerminalKey.touchpadToggle, + // 'TouchpadOn': TerminalKey.touchpadOn, + // 'TouchpadOff': TerminalKey.touchpadOff, + // 'MicMute': TerminalKey.micMute, + // 'Red': TerminalKey.red, + // 'Green': TerminalKey.green, + // 'Yellow': TerminalKey.yellow, + // 'Blue': TerminalKey.blue, 'ChannelUp': TerminalKey.channelUp, 'ChannelDown': TerminalKey.channelDown, -// 'Guide': TerminalKey.guide, + // 'Guide': TerminalKey.guide, 'Info': TerminalKey.info, -// 'Settings': TerminalKey.settings, -// 'MicVolumeUp': TerminalKey.micVolumeUp, -// 'MicVolumeDown': TerminalKey.micVolumeDown, -// 'New': TerminalKey.new, + // 'Settings': TerminalKey.settings, + // 'MicVolumeUp': TerminalKey.micVolumeUp, + // 'MicVolumeDown': TerminalKey.micVolumeDown, + // 'New': TerminalKey.new, 'Open': TerminalKey.open, 'Find': TerminalKey.find, 'Undo': TerminalKey.undo, 'Redo': TerminalKey.redo, 'MediaLast': TerminalKey.mediaLast, -// 'unknown': TerminalKey.unknown, -// 'Call': TerminalKey.call, -// 'Camera': TerminalKey.camera, -// 'CameraFocus': TerminalKey.cameraFocus, -// 'Context1': TerminalKey.context1, -// 'Context2': TerminalKey.context2, -// 'Context3': TerminalKey.context3, -// 'Context4': TerminalKey.context4, -// 'Flip': TerminalKey.flip, -// 'Hangup': TerminalKey.hangup, -// 'No': TerminalKey.no, + // 'unknown': TerminalKey.unknown, + // 'Call': TerminalKey.call, + // 'Camera': TerminalKey.camera, + // 'CameraFocus': TerminalKey.cameraFocus, + // 'Context1': TerminalKey.context1, + // 'Context2': TerminalKey.context2, + // 'Context3': TerminalKey.context3, + // 'Context4': TerminalKey.context4, + // 'Flip': TerminalKey.flip, + // 'Hangup': TerminalKey.hangup, + // 'No': TerminalKey.no, 'Select': TerminalKey.select, -// 'Yes': TerminalKey.yes, -// 'ToggleCallHangup': TerminalKey.toggleCallHangup, -// 'VoiceDial': TerminalKey.voiceDial, -// 'LastNumberRedial': TerminalKey.lastNumberRedial, -// 'Execute': TerminalKey.execute, -// 'Printer': TerminalKey.printer, -// 'Play': TerminalKey.play, + // 'Yes': TerminalKey.yes, + // 'ToggleCallHangup': TerminalKey.toggleCallHangup, + // 'VoiceDial': TerminalKey.voiceDial, + // 'LastNumberRedial': TerminalKey.lastNumberRedial, + // 'Execute': TerminalKey.execute, + // 'Printer': TerminalKey.printer, + // 'Play': TerminalKey.play, 'Sleep': TerminalKey.sleep, -// 'Zoom': TerminalKey.zoom, + // 'Zoom': TerminalKey.zoom, 'Exit': TerminalKey.exit, -// 'Cancel': TerminalKey.cancel, + // 'Cancel': TerminalKey.cancel, }; diff --git a/lib/src/terminal/src/core/mouse/button.dart b/lib/src/terminal/src/core/mouse/button.dart index 87117a24..f3b6da34 100644 --- a/lib/src/terminal/src/core/mouse/button.dart +++ b/lib/src/terminal/src/core/mouse/button.dart @@ -13,8 +13,7 @@ enum TerminalMouseButton { wheelLeft(id: 64 + 6, isWheel: true), - wheelRight(id: 64 + 7, isWheel: true), - ; + wheelRight(id: 64 + 7, isWheel: true); /// The id that is used to report a button press or release to the terminal. /// diff --git a/lib/src/terminal/src/core/mouse/button_state.dart b/lib/src/terminal/src/core/mouse/button_state.dart index 4308ed5e..10cc84fb 100644 --- a/lib/src/terminal/src/core/mouse/button_state.dart +++ b/lib/src/terminal/src/core/mouse/button_state.dart @@ -1,7 +1,3 @@ // Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory. -enum TerminalMouseButtonState { - up, - - down, -} +enum TerminalMouseButtonState { up, down } diff --git a/lib/src/terminal/src/core/mouse/handler.dart b/lib/src/terminal/src/core/mouse/handler.dart index 2ded95c4..46f07065 100644 --- a/lib/src/terminal/src/core/mouse/handler.dart +++ b/lib/src/terminal/src/core/mouse/handler.dart @@ -24,19 +24,10 @@ class TerminalMouseEvent { /// The platform of the terminal. final TerminalTargetPlatform platform; - TerminalMouseEvent({ - required this.button, - required this.buttonState, - required this.position, - required this.state, - required this.platform, - }); + TerminalMouseEvent({required this.button, required this.buttonState, required this.position, required this.state, required this.platform}); } -const defaultMouseHandler = CascadeMouseHandler([ - ClickMouseHandler(), - UpDownMouseHandler(), -]); +const defaultMouseHandler = CascadeMouseHandler([ClickMouseHandler(), UpDownMouseHandler()]); abstract class TerminalMouseHandler { const TerminalMouseHandler(); @@ -70,12 +61,7 @@ class ClickMouseHandler implements TerminalMouseHandler { case MouseMode.clickOnly: // Only clicks and only the first 3 buttons are reported. if (event.buttonState == TerminalMouseButtonState.down && (event.button.id < 3)) { - return MouseReporter.report( - event.button, - event.buttonState, - event.position, - event.state.mouseReportMode, - ); + return MouseReporter.report(event.button, event.buttonState, event.position, event.state.mouseReportMode); } return null; case MouseMode.none: @@ -103,12 +89,7 @@ class UpDownMouseHandler implements TerminalMouseHandler { if (event.button.isWheel && event.buttonState == TerminalMouseButtonState.up) { return null; } - return MouseReporter.report( - event.button, - event.buttonState, - event.position, - event.state.mouseReportMode, - ); + return MouseReporter.report(event.button, event.buttonState, event.position, event.state.mouseReportMode); } } } diff --git a/lib/src/terminal/src/core/mouse/mode.dart b/lib/src/terminal/src/core/mouse/mode.dart index 622cab25..f3e94d8d 100644 --- a/lib/src/terminal/src/core/mouse/mode.dart +++ b/lib/src/terminal/src/core/mouse/mode.dart @@ -10,8 +10,7 @@ enum MouseMode { upDownScrollDrag(reportScroll: true), - upDownScrollMove(reportScroll: true), - ; + upDownScrollMove(reportScroll: true); const MouseMode({this.reportScroll = false}); diff --git a/lib/src/terminal/src/core/mouse/reporter.dart b/lib/src/terminal/src/core/mouse/reporter.dart index 83204822..bd760131 100644 --- a/lib/src/terminal/src/core/mouse/reporter.dart +++ b/lib/src/terminal/src/core/mouse/reporter.dart @@ -6,12 +6,7 @@ import 'package:clide/src/terminal/src/core/mouse/button.dart'; import 'package:clide/src/terminal/src/core/mouse/button_state.dart'; abstract class MouseReporter { - static String report( - TerminalMouseButton button, - TerminalMouseButtonState state, - CellOffset position, - MouseReportMode reportMode, - ) { + static String report(TerminalMouseButton button, TerminalMouseButtonState state, CellOffset position, MouseReportMode reportMode) { // x and y offsets have to be incremented by 1 as the offset if 0-based, // The position has to be reported using 1-based coordinates. final x = position.x + 1; @@ -28,8 +23,9 @@ abstract class MouseReporter { // supports positions up to 2015. Both modes send a null byte if the // position exceeds that limit. final col = (reportMode == MouseReportMode.normal && x > 223) || (reportMode == MouseReportMode.utf && x > 2015) ? '\x00' : String.fromCharCode(32 + x); - final row = - (reportMode == MouseReportMode.normal && y > 223) || (reportMode == MouseReportMode.utf && y > 2015) ? '\x00' : String.fromCharCode(32 + y + 1); + final row = (reportMode == MouseReportMode.normal && y > 223) || (reportMode == MouseReportMode.utf && y > 2015) + ? '\x00' + : String.fromCharCode(32 + y + 1); return "\x1b[M$btn$col$row"; case MouseReportMode.sgr: final buttonID = button.id; diff --git a/lib/src/terminal/src/core/platform.dart b/lib/src/terminal/src/core/platform.dart index d30f691b..06574b5c 100644 --- a/lib/src/terminal/src/core/platform.dart +++ b/lib/src/terminal/src/core/platform.dart @@ -1,19 +1,3 @@ // Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory. -enum TerminalTargetPlatform { - unknown, - - android, - - ios, - - fuchsia, - - linux, - - macos, - - windows, - - web, -} +enum TerminalTargetPlatform { unknown, android, ios, fuchsia, linux, macos, windows, web } diff --git a/lib/src/terminal/src/core/reflow.dart b/lib/src/terminal/src/core/reflow.dart index 4b227a7b..5335deed 100644 --- a/lib/src/terminal/src/core/reflow.dart +++ b/lib/src/terminal/src/core/reflow.dart @@ -173,11 +173,7 @@ class _LineReflow { } } -List reflow( - IndexAwareCircularBuffer lines, - int oldWidth, - int newWidth, -) { +List reflow(IndexAwareCircularBuffer lines, int oldWidth, int newWidth) { final result = []; for (var i = 0; i < lines.length; i++) { diff --git a/lib/src/terminal/src/terminal.dart b/lib/src/terminal/src/terminal.dart index c9a25295..65176bd2 100644 --- a/lib/src/terminal/src/terminal.dart +++ b/lib/src/terminal/src/terminal.dart @@ -92,19 +92,9 @@ class Terminal with Observable implements TerminalState, EscapeHandler { late var _buffer = _mainBuffer; - late final _mainBuffer = Buffer( - this, - maxLines: maxLines, - isAltBuffer: false, - wordSeparators: wordSeparators, - ); + late final _mainBuffer = Buffer(this, maxLines: maxLines, isAltBuffer: false, wordSeparators: wordSeparators); - late final _altBuffer = Buffer( - this, - maxLines: maxLines, - isAltBuffer: true, - wordSeparators: wordSeparators, - ); + late final _altBuffer = Buffer(this, maxLines: maxLines, isAltBuffer: true, wordSeparators: wordSeparators); final _tabStops = TabStops(); @@ -236,22 +226,9 @@ class Terminal with Observable implements TerminalState, EscapeHandler { /// - [charInput] /// - [textInput] /// - [paste] - bool keyInput( - TerminalKey key, { - bool shift = false, - bool alt = false, - bool ctrl = false, - }) { + bool keyInput(TerminalKey key, {bool shift = false, bool alt = false, bool ctrl = false}) { final output = inputHandler?.call( - TerminalKeyboardEvent( - key: key, - shift: shift, - alt: alt, - ctrl: ctrl, - state: this, - altBuffer: isUsingAltBuffer, - platform: platform, - ), + TerminalKeyboardEvent(key: key, shift: shift, alt: alt, ctrl: ctrl, state: this, altBuffer: isUsingAltBuffer, platform: platform), ); if (output != null) { @@ -269,11 +246,7 @@ class Terminal with Observable implements TerminalState, EscapeHandler { /// - [keyInput] /// - [textInput] /// - [paste] - bool charInput( - int charCode, { - bool alt = false, - bool ctrl = false, - }) { + bool charInput(int charCode, {bool alt = false, bool ctrl = false}) { if (ctrl) { // a(97) ~ z(122) if (charCode >= Ascii.a && charCode <= Ascii.z) { @@ -328,18 +301,8 @@ class Terminal with Observable implements TerminalState, EscapeHandler { } // Handle a mouse event and return true if it was handled. - bool mouseInput( - TerminalMouseButton button, - TerminalMouseButtonState buttonState, - CellOffset position, - ) { - final output = mouseHandler?.call(TerminalMouseEvent( - button: button, - buttonState: buttonState, - position: position, - state: this, - platform: platform, - )); + bool mouseInput(TerminalMouseButton button, TerminalMouseButtonState buttonState, CellOffset position) { + final output = mouseHandler?.call(TerminalMouseEvent(button: button, buttonState: buttonState, position: position, state: this, platform: platform)); if (output != null) { onOutput?.call(output); return true; @@ -351,12 +314,7 @@ class Terminal with Observable implements TerminalState, EscapeHandler { /// than 0. Text reflow is currently not implemented and will be avaliable in /// the future. @override - void resize( - int newWidth, - int newHeight, [ - int? pixelWidth, - int? pixelHeight, - ]) { + void resize(int newWidth, int newHeight, [int? pixelWidth, int? pixelHeight]) { newWidth = max(newWidth, 1); newHeight = max(newHeight, 1); diff --git a/lib/src/terminal/src/terminal_view.dart b/lib/src/terminal/src/terminal_view.dart index d9d59746..0f54b6b6 100644 --- a/lib/src/terminal/src/terminal_view.dart +++ b/lib/src/terminal/src/terminal_view.dart @@ -181,9 +181,7 @@ class TerminalViewState extends State { void initState() { _focusNode = widget.focusNode ?? FocusNode(); _controller = widget.controller ?? TerminalController(); - _shortcutManager = ShortcutManager( - shortcuts: widget.shortcuts ?? defaultTerminalShortcuts, - ); + _shortcutManager = ShortcutManager(shortcuts: widget.shortcuts ?? defaultTerminalShortcuts); super.initState(); } @@ -268,11 +266,7 @@ class TerminalViewState extends State { ); } - child = TerminalActions( - terminal: widget.terminal, - controller: _controller, - child: child, - ); + child = TerminalActions(terminal: widget.terminal, controller: _controller, child: child); child = TerminalGestureHandler( terminalView: this, @@ -285,10 +279,7 @@ class TerminalViewState extends State { child: child, ); - child = MouseRegion( - cursor: widget.mouseCursor, - child: child, - ); + child = MouseRegion(cursor: widget.mouseCursor, child: child); child = Container( color: widget.theme.background.withValues(alpha: widget.backgroundOpacity), @@ -296,10 +287,7 @@ class TerminalViewState extends State { child: child, ); - return Listener( - onPointerSignal: _onPointerSignal, - child: child, - ); + return Listener(onPointerSignal: _onPointerSignal, child: child); } void requestKeyboard() { @@ -380,10 +368,7 @@ class TerminalViewState extends State { // ancestor. Wrapping in a Shortcuts widget would invert that. // T-107 approved leaving this suppression with an inline reason. // ignore: invalid_use_of_protected_member - final shortcutResult = _shortcutManager.handleKeypress( - focusNode.context!, - event, - ); + final shortcutResult = _shortcutManager.handleKeypress(focusNode.context!, event); if (shortcutResult != KeyEventResult.ignored) { return shortcutResult; diff --git a/lib/src/terminal/src/ui/char_metrics.dart b/lib/src/terminal/src/ui/char_metrics.dart index a4393d4f..f7702636 100644 --- a/lib/src/terminal/src/ui/char_metrics.dart +++ b/lib/src/terminal/src/ui/char_metrics.dart @@ -16,8 +16,5 @@ Size calcCharSize(TerminalStyle style, TextScaler textScaler) { final paragraph = builder.build(); paragraph.layout(ParagraphConstraints(width: double.infinity)); - return Size( - paragraph.maxIntrinsicWidth / test.length, - paragraph.height, - ); + return Size(paragraph.maxIntrinsicWidth / test.length, paragraph.height); } diff --git a/lib/src/terminal/src/ui/controller.dart b/lib/src/terminal/src/ui/controller.dart index fc26fa61..d0d63db5 100644 --- a/lib/src/terminal/src/ui/controller.dart +++ b/lib/src/terminal/src/ui/controller.dart @@ -16,9 +16,9 @@ class TerminalController with ChangeNotifier { SelectionMode selectionMode = SelectionMode.line, PointerInputs pointerInputs = const PointerInputs({PointerInput.tap}), bool suspendPointerInput = false, - }) : _selectionMode = selectionMode, - _pointerInputs = pointerInputs, - _suspendPointerInputs = suspendPointerInput; + }) : _selectionMode = selectionMode, + _pointerInputs = pointerInputs, + _suspendPointerInputs = suspendPointerInput; CellAnchor? _selectionBase; CellAnchor? _selectionExtent; @@ -123,17 +123,8 @@ class TerminalController with ChangeNotifier { /// Creates a new highlight on the terminal from [p1] to [p2] with the given /// [color]. The highlight will be removed when the returned object is /// disposed. - TerminalHighlight highlight({ - required CellAnchor p1, - required CellAnchor p2, - required Color color, - }) { - final highlight = TerminalHighlight( - this, - p1: p1, - p2: p2, - color: color, - ); + TerminalHighlight highlight({required CellAnchor p1, required CellAnchor p2, required Color color}) { + final highlight = TerminalHighlight(this, p1: p1, p2: p2, color: color); _highlights.add(highlight); notifyListeners(); @@ -156,12 +147,7 @@ class TerminalHighlight with Disposable { final Color color; - TerminalHighlight( - this.owner, { - required this.p1, - required this.p2, - required this.color, - }); + TerminalHighlight(this.owner, {required this.p1, required this.p2, required this.color}); /// Returns the range of the highlight. May be null if the anchors that /// define the highlight are not attached to the terminal. diff --git a/lib/src/terminal/src/ui/cursor_type.dart b/lib/src/terminal/src/ui/cursor_type.dart index 2990cbd2..d600889a 100644 --- a/lib/src/terminal/src/ui/cursor_type.dart +++ b/lib/src/terminal/src/ui/cursor_type.dart @@ -1,9 +1,3 @@ // Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory. -enum TerminalCursorType { - block, - - underline, - - verticalBar, -} +enum TerminalCursorType { block, underline, verticalBar } diff --git a/lib/src/terminal/src/ui/custom_text_edit.dart b/lib/src/terminal/src/ui/custom_text_edit.dart index e003a543..9300be36 100644 --- a/lib/src/terminal/src/ui/custom_text_edit.dart +++ b/lib/src/terminal/src/ui/custom_text_edit.dart @@ -89,12 +89,7 @@ class CustomTextEditState extends State with TextInputClient { @override Widget build(BuildContext context) { - return Focus( - focusNode: widget.focusNode, - autofocus: widget.autofocus, - onKeyEvent: _onKeyEvent, - child: widget.child, - ); + return Focus(focusNode: widget.focusNode, autofocus: widget.autofocus, onKeyEvent: _onKeyEvent, child: widget.child); } bool get hasInputConnection => _connection != null && _connection!.attached; @@ -123,10 +118,7 @@ class CustomTextEditState extends State with TextInputClient { return; } - _connection?.setEditableSizeAndTransform( - rect.size, - Matrix4.translationValues(0, 0, 0), - ); + _connection?.setEditableSizeAndTransform(rect.size, Matrix4.translationValues(0, 0, 0)); _connection?.setCaretRect(caretRect); } @@ -188,14 +180,8 @@ class CustomTextEditState extends State with TextInputClient { } TextEditingValue get _initEditingState => widget.deleteDetection - ? const TextEditingValue( - text: ' ', - selection: TextSelection.collapsed(offset: 2), - ) - : const TextEditingValue( - text: '', - selection: TextSelection.collapsed(offset: 0), - ); + ? const TextEditingValue(text: ' ', selection: TextSelection.collapsed(offset: 2)) + : const TextEditingValue(text: '', selection: TextSelection.collapsed(offset: 0)); late var _currentEditingState = _initEditingState.copyWith(); @@ -226,9 +212,7 @@ class CustomTextEditState extends State with TextInputClient { if (_currentEditingState.text.length < _initEditingState.text.length) { widget.onDelete(); } else { - final textDelta = _currentEditingState.text.substring( - _initEditingState.text.length, - ); + final textDelta = _currentEditingState.text.substring(_initEditingState.text.length); widget.onInsert(textDelta); } diff --git a/lib/src/terminal/src/ui/gesture/gesture_detector.dart b/lib/src/terminal/src/ui/gesture/gesture_detector.dart index 78258299..3934210f 100644 --- a/lib/src/terminal/src/ui/gesture/gesture_detector.dart +++ b/lib/src/terminal/src/ui/gesture/gesture_detector.dart @@ -99,16 +99,15 @@ class _TerminalGestureDetectorState extends State { Widget build(BuildContext context) { final gestures = {}; - gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(debugOwner: this), - (TapGestureRecognizer instance) { - instance - ..onTapDown = _handleTapDown - ..onTapUp = _handleTapUp - ..onSecondaryTapDown = widget.onSecondaryTapDown - ..onSecondaryTapUp = widget.onSecondaryTapUp; - }, - ); + gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers(() => TapGestureRecognizer(debugOwner: this), ( + TapGestureRecognizer instance, + ) { + instance + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..onSecondaryTapDown = widget.onSecondaryTapDown + ..onSecondaryTapUp = widget.onSecondaryTapUp; + }); gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers( () => LongPressGestureRecognizer( @@ -127,10 +126,7 @@ class _TerminalGestureDetectorState extends State { ); gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers( - () => PanGestureRecognizer( - debugOwner: this, - supportedDevices: {PointerDeviceKind.mouse}, - ), + () => PanGestureRecognizer(debugOwner: this, supportedDevices: {PointerDeviceKind.mouse}), (PanGestureRecognizer instance) { instance ..dragStartBehavior = DragStartBehavior.down @@ -139,10 +135,6 @@ class _TerminalGestureDetectorState extends State { }, ); - return RawGestureDetector( - gestures: gestures, - excludeFromSemantics: true, - child: widget.child, - ); + return RawGestureDetector(gestures: gestures, excludeFromSemantics: true, child: widget.child); } } diff --git a/lib/src/terminal/src/ui/gesture/gesture_handler.dart b/lib/src/terminal/src/ui/gesture/gesture_handler.dart index 77a75e41..586e577a 100644 --- a/lib/src/terminal/src/ui/gesture/gesture_handler.dart +++ b/lib/src/terminal/src/ui/gesture/gesture_handler.dart @@ -70,20 +70,11 @@ class _TerminalGestureHandlerState extends State { bool get _shouldSendTapEvent => !widget.readOnly && widget.terminalController.shouldSendPointerInput(PointerInput.tap); - void _tapDown( - GestureTapDownCallback? callback, - TapDownDetails details, - TerminalMouseButton button, { - bool forceCallback = false, - }) { + void _tapDown(GestureTapDownCallback? callback, TapDownDetails details, TerminalMouseButton button, {bool forceCallback = false}) { // Check if the terminal should and can handle the tap down event. var handled = false; if (_shouldSendTapEvent) { - handled = renderTerminal.mouseEvent( - button, - TerminalMouseButtonState.down, - details.localPosition, - ); + handled = renderTerminal.mouseEvent(button, TerminalMouseButtonState.down, details.localPosition); } // If the event was not handled by the terminal, use the supplied callback. if (!handled || forceCallback) { @@ -91,20 +82,11 @@ class _TerminalGestureHandlerState extends State { } } - void _tapUp( - GestureTapUpCallback? callback, - TapUpDetails details, - TerminalMouseButton button, { - bool forceCallback = false, - }) { + void _tapUp(GestureTapUpCallback? callback, TapUpDetails details, TerminalMouseButton button, {bool forceCallback = false}) { // Check if the terminal should and can handle the tap up event. var handled = false; if (_shouldSendTapEvent) { - handled = renderTerminal.mouseEvent( - button, - TerminalMouseButtonState.up, - details.localPosition, - ); + handled = renderTerminal.mouseEvent(button, TerminalMouseButtonState.up, details.localPosition); } // If the event was not handled by the terminal, use the supplied callback. if (!handled || forceCallback) { @@ -115,12 +97,7 @@ class _TerminalGestureHandlerState extends State { void onTapDown(TapDownDetails details) { // onTapDown is special, as it will always call the supplied callback. // The TerminalView depends on it to bring the terminal into focus. - _tapDown( - widget.onTapDown, - details, - TerminalMouseButton.left, - forceCallback: true, - ); + _tapDown(widget.onTapDown, details, TerminalMouseButton.left, forceCallback: true); } void onSingleTapUp(TapUpDetails details) { @@ -145,10 +122,7 @@ class _TerminalGestureHandlerState extends State { } void onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - renderTerminal.selectWord( - _lastLongPressStartDetails!.localPosition, - details.localPosition, - ); + renderTerminal.selectWord(_lastLongPressStartDetails!.localPosition, details.localPosition); } // void onLongPressUp() {} @@ -162,9 +136,6 @@ class _TerminalGestureHandlerState extends State { } void onDragUpdate(DragUpdateDetails details) { - renderTerminal.selectCharacters( - _lastDragStartDetails!.localPosition, - details.localPosition, - ); + renderTerminal.selectCharacters(_lastDragStartDetails!.localPosition, details.localPosition); } } diff --git a/lib/src/terminal/src/ui/keyboard_listener.dart b/lib/src/terminal/src/ui/keyboard_listener.dart index 8513ff33..3011f7bb 100644 --- a/lib/src/terminal/src/ui/keyboard_listener.dart +++ b/lib/src/terminal/src/ui/keyboard_listener.dart @@ -41,11 +41,6 @@ class CustomKeyboardListener extends StatelessWidget { @override Widget build(BuildContext context) { - return Focus( - focusNode: focusNode, - autofocus: autofocus, - onKeyEvent: _onKeyEvent, - child: child, - ); + return Focus(focusNode: focusNode, autofocus: autofocus, onKeyEvent: _onKeyEvent, child: child); } } diff --git a/lib/src/terminal/src/ui/painter.dart b/lib/src/terminal/src/ui/painter.dart index b4a965e7..4469c79e 100644 --- a/lib/src/terminal/src/ui/painter.dart +++ b/lib/src/terminal/src/ui/painter.dart @@ -9,13 +9,10 @@ import 'package:clide/src/terminal/terminal.dart'; /// Encapsulates the logic for painting various terminal elements. class TerminalPainter { - TerminalPainter({ - required TerminalTheme theme, - required TerminalStyle textStyle, - required TextScaler textScaler, - }) : _textStyle = textStyle, - _theme = theme, - _textScaler = textScaler; + TerminalPainter({required TerminalTheme theme, required TerminalStyle textStyle, required TextScaler textScaler}) + : _textStyle = textStyle, + _theme = theme, + _textScaler = textScaler; /// A lookup table from terminal colors to Flutter colors. late var _colorPalette = PaletteBuilder(_theme).build(); @@ -60,18 +57,13 @@ class TerminalPainter { final textStyle = _textStyle.toTextStyle(); final builder = ParagraphBuilder(textStyle.getParagraphStyle()); - builder.pushStyle( - textStyle.getTextStyle(textScaler: _textScaler), - ); + builder.pushStyle(textStyle.getTextStyle(textScaler: _textScaler)); builder.addText(test); final paragraph = builder.build(); paragraph.layout(ParagraphConstraints(width: double.infinity)); - final result = Size( - paragraph.maxIntrinsicWidth / test.length, - paragraph.height, - ); + final result = Size(paragraph.maxIntrinsicWidth / test.length, paragraph.height); paragraph.dispose(); return result; @@ -88,12 +80,7 @@ class TerminalPainter { } /// Paints the cursor based on the current cursor type. - void paintCursor( - Canvas canvas, - Offset offset, { - required TerminalCursorType cursorType, - bool hasFocus = true, - }) { + void paintCursor(Canvas canvas, Offset offset, {required TerminalCursorType cursorType, bool hasFocus = true}) { final paint = Paint() ..color = _theme.cursor ..strokeWidth = 1; @@ -110,17 +97,9 @@ class TerminalPainter { canvas.drawRect(offset & _cellSize, paint); return; case TerminalCursorType.underline: - return canvas.drawLine( - Offset(offset.dx, _cellSize.height - 1), - Offset(offset.dx + _cellSize.width, _cellSize.height - 1), - paint, - ); + return canvas.drawLine(Offset(offset.dx, _cellSize.height - 1), Offset(offset.dx + _cellSize.width, _cellSize.height - 1), paint); case TerminalCursorType.verticalBar: - return canvas.drawLine( - Offset(offset.dx, 0), - Offset(offset.dx, _cellSize.height), - paint, - ); + return canvas.drawLine(Offset(offset.dx, 0), Offset(offset.dx, _cellSize.height), paint); } } @@ -132,19 +111,12 @@ class TerminalPainter { ..color = color ..strokeWidth = 1; - canvas.drawRect( - Rect.fromPoints(offset, endOffset), - paint, - ); + canvas.drawRect(Rect.fromPoints(offset, endOffset), paint); } /// Paints [line] to [canvas] at [offset]. The x offset of [offset] is usually /// 0, and the y offset is the top of the line. - void paintLine( - Canvas canvas, - Offset offset, - BufferLine line, - ) { + void paintLine(Canvas canvas, Offset offset, BufferLine line) { final cellData = CellData.empty(); final cellWidth = _cellSize.width; @@ -205,12 +177,7 @@ class TerminalPainter { char = String.fromCharCode(0xA0); } - paragraph = _paragraphCache.performAndCacheLayout( - char, - style, - _textScaler, - cacheKey, - ); + paragraph = _paragraphCache.performAndCacheLayout(char, style, _textScaler, cacheKey); } canvas.drawParagraph(paragraph, offset); diff --git a/lib/src/terminal/src/ui/palette_builder.dart b/lib/src/terminal/src/ui/palette_builder.dart index 4f233f6e..3436d760 100644 --- a/lib/src/terminal/src/ui/palette_builder.dart +++ b/lib/src/terminal/src/ui/palette_builder.dart @@ -10,11 +10,7 @@ class PaletteBuilder { PaletteBuilder(this.theme); List build() { - return List.generate( - 256, - paletteColor, - growable: false, - ); + return List.generate(256, paletteColor, growable: false); } /// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit diff --git a/lib/src/terminal/src/ui/paragraph_cache.dart b/lib/src/terminal/src/ui/paragraph_cache.dart index a19c2eac..d99745d1 100644 --- a/lib/src/terminal/src/ui/paragraph_cache.dart +++ b/lib/src/terminal/src/ui/paragraph_cache.dart @@ -34,12 +34,7 @@ class ParagraphCache { Paragraph? getLayoutFromCache(int key) => _cache[key]; - Paragraph performAndCacheLayout( - String text, - TextStyle style, - TextScaler textScaler, - int key, - ) { + Paragraph performAndCacheLayout(String text, TextStyle style, TextScaler textScaler, int key) { final builder = ParagraphBuilder(style.getParagraphStyle()); builder.pushStyle(style.getTextStyle(textScaler: textScaler)); builder.addText(text); diff --git a/lib/src/terminal/src/ui/pointer_input.dart b/lib/src/terminal/src/ui/pointer_input.dart index ff165e23..a68daf03 100644 --- a/lib/src/terminal/src/ui/pointer_input.dart +++ b/lib/src/terminal/src/ui/pointer_input.dart @@ -21,11 +21,5 @@ class PointerInputs { const PointerInputs.none() : inputs = const {}; - const PointerInputs.all() - : inputs = const { - PointerInput.tap, - PointerInput.scroll, - PointerInput.drag, - PointerInput.move, - }; + const PointerInputs.all() : inputs = const {PointerInput.tap, PointerInput.scroll, PointerInput.drag, PointerInput.move}; } diff --git a/lib/src/terminal/src/ui/render.dart b/lib/src/terminal/src/ui/render.dart index cd82d163..b3e00d26 100644 --- a/lib/src/terminal/src/ui/render.dart +++ b/lib/src/terminal/src/ui/render.dart @@ -36,21 +36,17 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { required bool alwaysShowCursor, EditableRectCallback? onEditableRect, String? composingText, - }) : _terminal = terminal, - _controller = controller, - _offset = offset, - _padding = padding, - _autoResize = autoResize, - _focusNode = focusNode, - _cursorType = cursorType, - _alwaysShowCursor = alwaysShowCursor, - _onEditableRect = onEditableRect, - _composingText = composingText, - _painter = TerminalPainter( - theme: theme, - textStyle: textStyle, - textScaler: textScaler, - ); + }) : _terminal = terminal, + _controller = controller, + _offset = offset, + _padding = padding, + _autoResize = autoResize, + _focusNode = focusNode, + _cursorType = cursorType, + _alwaysShowCursor = alwaysShowCursor, + _onEditableRect = onEditableRect, + _composingText = composingText, + _painter = TerminalPainter(theme: theme, textStyle: textStyle, textScaler: textScaler); Terminal _terminal; set terminal(Terminal terminal) { @@ -248,10 +244,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { final y = offset.dy - _padding.top + _scrollOffset; final row = y ~/ _painter.cellSize.height; final col = x ~/ _painter.cellSize.width; - return CellOffset( - col.clamp(0, _terminal.viewWidth - 1), - row.clamp(0, _terminal.buffer.lines.length - 1), - ); + return CellOffset(col.clamp(0, _terminal.viewWidth - 1), row.clamp(0, _terminal.buffer.lines.length - 1)); } /// Selects entire words in the terminal that contains [from] and [to]. @@ -283,28 +276,18 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { void selectCharacters(Offset from, [Offset? to]) { final fromPosition = getCellOffset(from); if (to == null) { - _controller.setSelection( - _terminal.buffer.createAnchorFromOffset(fromPosition), - _terminal.buffer.createAnchorFromOffset(fromPosition), - ); + _controller.setSelection(_terminal.buffer.createAnchorFromOffset(fromPosition), _terminal.buffer.createAnchorFromOffset(fromPosition)); } else { var toPosition = getCellOffset(to); if (toPosition.x >= fromPosition.x) { toPosition = CellOffset(toPosition.x + 1, toPosition.y); } - _controller.setSelection( - _terminal.buffer.createAnchorFromOffset(fromPosition), - _terminal.buffer.createAnchorFromOffset(toPosition), - ); + _controller.setSelection(_terminal.buffer.createAnchorFromOffset(fromPosition), _terminal.buffer.createAnchorFromOffset(toPosition)); } } /// Send a mouse event at [offset] with [button] being currently in [buttonState]. - bool mouseEvent( - TerminalMouseButton button, - TerminalMouseButtonState buttonState, - Offset offset, - ) { + bool mouseEvent(TerminalMouseButton button, TerminalMouseButtonState buttonState, Offset offset) { final position = getCellOffset(offset); return _terminal.mouseInput(button, buttonState, position); } @@ -312,12 +295,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { void _notifyEditableRect() { final cursor = localToGlobal(cursorOffset); - final rect = Rect.fromLTRB( - cursor.dx, - cursor.dy, - size.width, - cursor.dy + _painter.cellSize.height, - ); + final rect = Rect.fromLTRB(cursor.dx, cursor.dy, size.width, cursor.dy + _painter.cellSize.height); final caretRect = cursor & _painter.cellSize; @@ -331,10 +309,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { return; } - final viewportSize = TerminalSize( - size.width ~/ _painter.cellSize.width, - _viewportHeight ~/ _painter.cellSize.height, - ); + final viewportSize = TerminalSize(size.width ~/ _painter.cellSize.width, _viewportHeight ~/ _painter.cellSize.height); if (_viewportSize != viewportSize) { _viewportSize = viewportSize; @@ -345,12 +320,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { /// Notify the underlying terminal that the viewport size has changed. void _resizeTerminalIfNeeded() { if (_autoResize && _viewportSize != null) { - _terminal.resize( - _viewportSize!.width, - _viewportSize!.height, - _painter.cellSize.width.round(), - _painter.cellSize.height.round(), - ); + _terminal.resize(_viewportSize!.width, _viewportSize!.height, _painter.cellSize.width.round(), _painter.cellSize.height.round()); } } @@ -383,10 +353,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { /// The offset of the cursor from the top left corner of this render object. Offset get cursorOffset { - return Offset( - _terminal.buffer.cursorX * _painter.cellSize.width, - _terminal.buffer.absoluteCursorY * _painter.cellSize.height + _lineOffset, - ); + return Offset(_terminal.buffer.cursorX * _painter.cellSize.width, _terminal.buffer.absoluteCursorY * _painter.cellSize.height + _lineOffset); } Size get cellSize { @@ -415,11 +382,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { final effectLastLine = lastLine.clamp(0, lines.length - 1); for (var i = effectFirstLine; i <= effectLastLine; i++) { - _painter.paintLine( - canvas, - offset.translate(0, (i * charHeight + _lineOffset).truncateToDouble()), - lines[i], - ); + _painter.paintLine(canvas, offset.translate(0, (i * charHeight + _lineOffset).truncateToDouble()), lines[i]); } if (_terminal.buffer.absoluteCursorY >= effectFirstLine && _terminal.buffer.absoluteCursorY <= effectLastLine) { @@ -428,29 +391,14 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } if (_shouldShowCursor) { - _painter.paintCursor( - canvas, - offset + cursorOffset, - cursorType: _cursorType, - hasFocus: _focusNode.hasFocus, - ); + _painter.paintCursor(canvas, offset + cursorOffset, cursorType: _cursorType, hasFocus: _focusNode.hasFocus); } } - _paintHighlights( - canvas, - _controller.highlights, - effectFirstLine, - effectLastLine, - ); + _paintHighlights(canvas, _controller.highlights, effectFirstLine, effectLastLine); if (_controller.selection != null) { - _paintSelection( - canvas, - _controller.selection!, - effectFirstLine, - effectLastLine, - ); + _paintSelection(canvas, _controller.selection!, effectFirstLine, effectLastLine); } } @@ -469,14 +417,8 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ); final builder = ParagraphBuilder(style.getParagraphStyle()); - builder.addPlaceholder( - offset.dx, - _painter.cellSize.height, - PlaceholderAlignment.middle, - ); - builder.pushStyle( - style.getTextStyle(textScaler: _painter.textScaler), - ); + builder.addPlaceholder(offset.dx, _painter.cellSize.height, PlaceholderAlignment.middle); + builder.pushStyle(style.getTextStyle(textScaler: _painter.textScaler)); builder.addText(composingText); final paragraph = builder.build(); @@ -485,12 +427,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { canvas.drawParagraph(paragraph, Offset(0, offset.dy)); } - void _paintSelection( - Canvas canvas, - BufferRange selection, - int firstLine, - int lastLine, - ) { + void _paintSelection(Canvas canvas, BufferRange selection, int firstLine, int lastLine) { for (final segment in selection.toSegments()) { if (segment.line >= _terminal.buffer.lines.length) { break; @@ -508,12 +445,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } } - void _paintHighlights( - Canvas canvas, - List highlights, - int firstLine, - int lastLine, - ) { + void _paintHighlights(Canvas canvas, List highlights, int firstLine, int lastLine) { for (var highlight in _controller.highlights) { final range = highlight.range?.normalized; @@ -540,10 +472,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { final start = segment.start ?? 0; final end = segment.end ?? _terminal.viewWidth; - final startOffset = Offset( - start * _painter.cellSize.width, - segment.line * _painter.cellSize.height + _lineOffset, - ); + final startOffset = Offset(start * _painter.cellSize.width, segment.line * _painter.cellSize.height + _lineOffset); _painter.paintHighlight(canvas, startOffset, end - start, color); } diff --git a/lib/src/terminal/src/ui/selection_mode.dart b/lib/src/terminal/src/ui/selection_mode.dart index 222c2891..bcb1f1b5 100644 --- a/lib/src/terminal/src/ui/selection_mode.dart +++ b/lib/src/terminal/src/ui/selection_mode.dart @@ -1,7 +1,3 @@ // Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory. -enum SelectionMode { - line, - - block, -} +enum SelectionMode { line, block } diff --git a/lib/src/terminal/src/ui/shortcut/actions.dart b/lib/src/terminal/src/ui/shortcut/actions.dart index ca98944b..cea51ed6 100644 --- a/lib/src/terminal/src/ui/shortcut/actions.dart +++ b/lib/src/terminal/src/ui/shortcut/actions.dart @@ -7,12 +7,7 @@ import 'package:clide/src/terminal/src/ui/controller.dart'; import 'package:clide/src/terminal/src/ui/selection_mode.dart'; class TerminalActions extends StatelessWidget { - const TerminalActions({ - super.key, - required this.terminal, - required this.controller, - required this.child, - }); + const TerminalActions({super.key, required this.terminal, required this.controller, required this.child}); final Terminal terminal; @@ -53,14 +48,8 @@ class TerminalActions extends StatelessWidget { SelectAllTextIntent: CallbackAction( onInvoke: (intent) { controller.setSelection( - terminal.buffer.createAnchor( - 0, - terminal.buffer.height - terminal.viewHeight, - ), - terminal.buffer.createAnchor( - terminal.viewWidth, - terminal.buffer.height - 1, - ), + terminal.buffer.createAnchor(0, terminal.buffer.height - terminal.viewHeight), + terminal.buffer.createAnchor(terminal.viewWidth, terminal.buffer.height - 1), mode: SelectionMode.line, ); return null; diff --git a/lib/src/terminal/src/ui/terminal_text_style.dart b/lib/src/terminal/src/ui/terminal_text_style.dart index 549d9439..d391c17a 100644 --- a/lib/src/terminal/src/ui/terminal_text_style.dart +++ b/lib/src/terminal/src/ui/terminal_text_style.dart @@ -50,13 +50,7 @@ class TerminalStyle { final List fontFamilyFallback; - TextStyle toTextStyle({ - Color? color, - Color? backgroundColor, - bool bold = false, - bool italic = false, - bool underline = false, - }) { + TextStyle toTextStyle({Color? color, Color? backgroundColor, bool bold = false, bool italic = false, bool underline = false}) { return TextStyle( fontSize: fontSize, height: height, @@ -70,12 +64,7 @@ class TerminalStyle { ); } - TerminalStyle copyWith({ - double? fontSize, - double? height, - String? fontFamily, - List? fontFamilyFallback, - }) { + TerminalStyle copyWith({double? fontSize, double? height, String? fontFamily, List? fontFamilyFallback}) { return TerminalStyle( fontSize: fontSize ?? this.fontSize, height: height ?? this.height, diff --git a/lib/src/terminal/src/utils/circular_buffer.dart b/lib/src/terminal/src/utils/circular_buffer.dart index a561545d..4e64bec4 100644 --- a/lib/src/terminal/src/utils/circular_buffer.dart +++ b/lib/src/terminal/src/utils/circular_buffer.dart @@ -77,10 +77,7 @@ class IndexAwareCircularBuffer { // Reconstruct array, starting at index 0. Only transfer values from the // indexes 0 to length. - final newArray = List.generate( - value, - (index) => index < _length ? _getChild(index) : null, - ); + final newArray = List.generate(value, (index) => index < _length ? _getChild(index) : null); _startIndex = 0; _array = newArray; diff --git a/lib/test_app.dart b/lib/test_app.dart index c766c982..dc402ffb 100644 --- a/lib/test_app.dart +++ b/lib/test_app.dart @@ -100,15 +100,7 @@ class _ClideTestAppState extends State { // Emitted under a distinct source so the harness's grep // (`make run-testmode` checks for `"failed":0`) keeps working // without depending on the human-readable lines above. - _logger.info( - 'testmode:json', - jsonEncode({ - 'passed': passed, - 'failed': failed, - 'total': _results.length, - 'failures': failedNames, - }), - ); + _logger.info('testmode:json', jsonEncode({'passed': passed, 'failed': failed, 'total': _results.length, 'failures': failedNames})); setState(() => _done = true); await Future.delayed(const Duration(seconds: 2)); @@ -198,30 +190,18 @@ class _ClideTestAppState extends State { // ping round-trip final pingReq = IpcRequest(id: 'test-ping-1', cmd: 'ping'); final pingResp = await dispatcher.dispatch(pingReq); - _addResult( - 'ipc ping', - pingResp.ok && pingResp.data['pong'] == true, - pingResp.ok ? 'pong=${pingResp.data['pong']}' : 'error: ${pingResp.error?.message}', - ); + _addResult('ipc ping', pingResp.ok && pingResp.data['pong'] == true, pingResp.ok ? 'pong=${pingResp.data['pong']}' : 'error: ${pingResp.error?.message}'); // version round-trip final verReq = IpcRequest(id: 'test-ver-1', cmd: 'version'); final verResp = await dispatcher.dispatch(verReq); final version = verResp.data['version']; - _addResult( - 'ipc version', - verResp.ok && version is String && version.isNotEmpty, - 'version=$version', - ); + _addResult('ipc version', verResp.ok && version is String && version.isNotEmpty, 'version=$version'); // unknown command → notFound final badReq = IpcRequest(id: 'test-bad-1', cmd: 'no_such_command'); final badResp = await dispatcher.dispatch(badReq); - _addResult( - 'ipc unknown cmd', - !badResp.ok && badResp.error?.kind == 'not_found', - badResp.ok ? 'unexpected ok' : 'kind=${badResp.error?.kind}', - ); + _addResult('ipc unknown cmd', !badResp.ok && badResp.error?.kind == 'not_found', badResp.ok ? 'unexpected ok' : 'kind=${badResp.error?.kind}'); // envelope encode/decode round-trip final encoded = pingReq.encode(); @@ -282,12 +262,7 @@ class _ClideTestAppState extends State { toolchain: tc, ); - final extensions = [ - DiffExtension(), - FilesExtension(), - GitExtension(), - TerminalExtension(), - ]; + final extensions = [DiffExtension(), FilesExtension(), GitExtension(), TerminalExtension()]; for (final ext in extensions) { try { @@ -453,10 +428,15 @@ class _ClideTestAppState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('ClideTestApp', style: TextStyle(color: Color(0xFFCDD6F4), fontSize: 20, fontWeight: FontWeight.bold, decoration: TextDecoration.none)), + const Text( + 'ClideTestApp', + style: TextStyle(color: Color(0xFFCDD6F4), fontSize: 20, fontWeight: FontWeight.bold, decoration: TextDecoration.none), + ), const SizedBox(height: 4), - Text(_done ? 'Done — exiting' : 'Running tests...', - style: const TextStyle(color: Color(0xFF6C7086), fontSize: 13, decoration: TextDecoration.none)), + Text( + _done ? 'Done — exiting' : 'Running tests...', + style: const TextStyle(color: Color(0xFF6C7086), fontSize: 13, decoration: TextDecoration.none), + ), const SizedBox(height: 16), Expanded( child: ListView.builder( @@ -467,17 +447,25 @@ class _ClideTestAppState extends State { padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ - Text(r.ok ? '●' : '●', - style: TextStyle(color: r.ok ? const Color(0xFFA6E3A1) : const Color(0xFFF38BA8), fontSize: 12, decoration: TextDecoration.none)), + Text( + r.ok ? '●' : '●', + style: TextStyle(color: r.ok ? const Color(0xFFA6E3A1) : const Color(0xFFF38BA8), fontSize: 12, decoration: TextDecoration.none), + ), const SizedBox(width: 8), SizedBox( - width: 220, - child: Text(r.name, - style: const TextStyle(color: Color(0xFFCDD6F4), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none))), + width: 220, + child: Text( + r.name, + style: const TextStyle(color: Color(0xFFCDD6F4), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none), + ), + ), Expanded( - child: Text(r.output, - style: const TextStyle(color: Color(0xFF9399B2), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none), - overflow: TextOverflow.ellipsis)), + child: Text( + r.output, + style: const TextStyle(color: Color(0xFF9399B2), fontSize: 12, fontFamily: 'monospace', decoration: TextDecoration.none), + overflow: TextOverflow.ellipsis, + ), + ), ], ), ); @@ -497,12 +485,7 @@ class _TestEventSink implements DaemonEventSink { @override void emit(IpcEvent event) { - _bus.emit(DaemonEvent( - subsystem: event.subsystem, - kind: event.kind, - data: event.data, - ts: DateTime.now(), - )); + _bus.emit(DaemonEvent(subsystem: event.subsystem, kind: event.kind, data: event.data, ts: DateTime.now())); } } diff --git a/lib/widgets/src/clide_accordion.dart b/lib/widgets/src/clide_accordion.dart index 8c8d2be2..38470045 100644 --- a/lib/widgets/src/clide_accordion.dart +++ b/lib/widgets/src/clide_accordion.dart @@ -39,8 +39,12 @@ class ClideAccordion extends StatelessWidget { ClideIcon(expanded ? PhosphorIcons.byName('caret-down') : PhosphorIcons.byName('caret-right'), size: 10, color: tokens.globalTextMuted), const SizedBox(width: 6), if (leading != null) ...[leading!, const SizedBox(width: 6)], - ClideText('$label · $count', - fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), + ClideText( + '$label · $count', + fontSize: clideFontSmall, + color: hovered ? tokens.globalForeground : tokens.sidebarSectionHeader, + fontFamily: clideMonoFamily, + ), ], ), ), diff --git a/lib/widgets/src/clide_anchored.dart b/lib/widgets/src/clide_anchored.dart index baf5865e..4106628d 100644 --- a/lib/widgets/src/clide_anchored.dart +++ b/lib/widgets/src/clide_anchored.dart @@ -186,52 +186,52 @@ class _ClideAnchoredOverlayState extends State { a == ClideAnchorAlign.start ? Alignment.bottomLeft : a == ClideAnchorAlign.end - ? Alignment.bottomRight - : Alignment.bottomCenter, + ? Alignment.bottomRight + : Alignment.bottomCenter, a == ClideAnchorAlign.start ? Alignment.topLeft : a == ClideAnchorAlign.end - ? Alignment.topRight - : Alignment.topCenter, + ? Alignment.topRight + : Alignment.topCenter, ); case ClideAnchorSide.above: return ( a == ClideAnchorAlign.start ? Alignment.topLeft : a == ClideAnchorAlign.end - ? Alignment.topRight - : Alignment.topCenter, + ? Alignment.topRight + : Alignment.topCenter, a == ClideAnchorAlign.start ? Alignment.bottomLeft : a == ClideAnchorAlign.end - ? Alignment.bottomRight - : Alignment.bottomCenter, + ? Alignment.bottomRight + : Alignment.bottomCenter, ); case ClideAnchorSide.right: return ( a == ClideAnchorAlign.start ? Alignment.topRight : a == ClideAnchorAlign.end - ? Alignment.bottomRight - : Alignment.centerRight, + ? Alignment.bottomRight + : Alignment.centerRight, a == ClideAnchorAlign.start ? Alignment.topLeft : a == ClideAnchorAlign.end - ? Alignment.bottomLeft - : Alignment.centerLeft, + ? Alignment.bottomLeft + : Alignment.centerLeft, ); case ClideAnchorSide.left: return ( a == ClideAnchorAlign.start ? Alignment.topLeft : a == ClideAnchorAlign.end - ? Alignment.bottomLeft - : Alignment.centerLeft, + ? Alignment.bottomLeft + : Alignment.centerLeft, a == ClideAnchorAlign.start ? Alignment.topRight : a == ClideAnchorAlign.end - ? Alignment.bottomRight - : Alignment.centerRight, + ? Alignment.bottomRight + : Alignment.centerRight, ); } } diff --git a/lib/widgets/src/clide_button.dart b/lib/widgets/src/clide_button.dart index f0bb025b..d00b8cc5 100644 --- a/lib/widgets/src/clide_button.dart +++ b/lib/widgets/src/clide_button.dart @@ -49,8 +49,8 @@ class ClideButton extends StatelessWidget { bg = pressed ? tokens.buttonActiveBackground : hovered - ? tokens.buttonHoverBackground - : tokens.buttonBackground; + ? tokens.buttonHoverBackground + : tokens.buttonBackground; fg = tokens.buttonForeground; case ClideButtonVariant.primary: bg = pressed ? tokens.panelActiveBorder : tokens.buttonActiveBackground; @@ -67,11 +67,7 @@ class ClideButton extends StatelessWidget { border: Border.all(color: tokens.buttonBorder, width: 1), borderRadius: BorderRadius.circular(3), ), - child: ClideText( - label, - color: fg, - fontWeight: variant == ClideButtonVariant.primary ? FontWeight.w600 : FontWeight.w500, - ), + child: ClideText(label, color: fg, fontWeight: variant == ClideButtonVariant.primary ? FontWeight.w600 : FontWeight.w500), ); }, ), diff --git a/lib/widgets/src/clide_code_block.dart b/lib/widgets/src/clide_code_block.dart index 68241e9b..f86e581a 100644 --- a/lib/widgets/src/clide_code_block.dart +++ b/lib/widgets/src/clide_code_block.dart @@ -50,12 +50,7 @@ class _ClideCodeBlockState extends State { @override Widget build(BuildContext context) { final tokens = ClideTheme.of(context).surface; - final style = TextStyle( - fontFamily: clideMonoFamily, - fontFamilyFallback: clideMonoFamilyFallback, - fontSize: clideFontMono, - color: tokens.globalForeground, - ); + final style = TextStyle(fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback, fontSize: clideFontMono, color: tokens.globalForeground); final spans = _spans; TextSpan textSpan; @@ -74,10 +69,7 @@ class _ClideCodeBlockState extends State { borderRadius: BorderRadius.circular(4), border: Border.all(color: tokens.panelBorder), ), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text.rich(textSpan), - ), + child: SingleChildScrollView(scrollDirection: Axis.horizontal, child: Text.rich(textSpan)), ); } @@ -114,7 +106,12 @@ class _ClideCodeBlockState extends State { } if (eChar > clippedStart) { final color = TreeSitterService.colorForRole(span.role, tokens); - children.add(TextSpan(text: source.substring(clippedStart, eChar), style: base.copyWith(color: color))); + children.add( + TextSpan( + text: source.substring(clippedStart, eChar), + style: base.copyWith(color: color), + ), + ); } if (eChar > lastChar) lastChar = eChar; } diff --git a/lib/widgets/src/clide_collapser_card.dart b/lib/widgets/src/clide_collapser_card.dart index 2c4b01b4..66dfb8d3 100644 --- a/lib/widgets/src/clide_collapser_card.dart +++ b/lib/widgets/src/clide_collapser_card.dart @@ -140,98 +140,87 @@ class _ClideCollapserCardState extends State { ), ], // Status — the icon hard against the right edge. - if (widget.status != null) ...[ - const SizedBox(width: 8), - ClideStatusIndicator(status: widget.status!, size: clideIconHero), - ], + if (widget.status != null) ...[const SizedBox(width: 8), ClideStatusIndicator(status: widget.status!, size: clideIconHero)], ], ); } /// Collapsed: the ticker row IS the toggle, focusable for keyboard/AT. Widget _tickerRow(SurfaceTokens tokens) => ClideTappable( - focusNode: _controlFocus, - onTap: _toggle, - tooltip: 'Expand', - builder: (context, hovered, focused) => Container( - padding: const EdgeInsets.symmetric(horizontal: kClideCardHeaderPadH, vertical: kClideCardHeaderPadV), - decoration: BoxDecoration( - color: (hovered || focused) ? tokens.listItemHoverBackground : tokens.listItemBackground, - border: Border.all(color: widget.color ?? tokens.panelBorder), - borderRadius: BorderRadius.circular(kClideCardRadius), - ), - child: _headerContent(tokens, expanded: false), - ), - ); + focusNode: _controlFocus, + onTap: _toggle, + tooltip: 'Expand', + builder: (context, hovered, focused) => Container( + padding: const EdgeInsets.symmetric(horizontal: kClideCardHeaderPadH, vertical: kClideCardHeaderPadV), + decoration: BoxDecoration( + color: (hovered || focused) ? tokens.listItemHoverBackground : tokens.listItemBackground, + border: Border.all(color: widget.color ?? tokens.panelBorder), + borderRadius: BorderRadius.circular(kClideCardRadius), + ), + child: _headerContent(tokens, expanded: false), + ), + ); /// Expanded: a framed inner canvas wrapping the item cards. The frame /// BACKGROUND is a gesture target behind the items that only fires for hits /// the items don't consume. Widget _expandedFrame(SurfaceTokens tokens) => DecoratedBox( - decoration: BoxDecoration( - border: Border.all(color: widget.color ?? tokens.panelBorder), - borderRadius: BorderRadius.circular(kClideCardRadius), + decoration: BoxDecoration( + border: Border.all(color: widget.color ?? tokens.panelBorder), + borderRadius: BorderRadius.circular(kClideCardRadius), + ), + child: Stack( + children: [ + // Background toggle: behind the items, not a whole-card overlay, so + // item taps are never intercepted. Excluded from focus traversal — + // the header caret is the single keyboard stop. + Positioned.fill( + child: ExcludeFocus( + child: ClideTappable(onTap: _toggle, tooltip: 'Collapse', builder: (_, _, _) => const SizedBox.expand()), + ), ), - child: Stack( + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // Background toggle: behind the items, not a whole-card overlay, so - // item taps are never intercepted. Excluded from focus traversal — - // the header caret is the single keyboard stop. - Positioned.fill( - child: ExcludeFocus( - child: ClideTappable( - onTap: _toggle, - tooltip: 'Collapse', - builder: (_, __, ___) => const SizedBox.expand(), - ), + _headerRow(tokens), + // Even padding around the inner item canvas (T-305): the sides + + // top match, and each inner item carries a matching bottom margin + // (so the last item's margin is the bottom inset and items in a + // multi-item run are evenly separated) — hence bottom 0 here. + Padding( + padding: const EdgeInsets.fromLTRB(kClideCardHeaderPadH, kClideCardHeaderPadH, kClideCardHeaderPadH, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final child in widget.children) + // Each item opaquely consumes its full bounds so a body + // tap interacts with that card (or does nothing), never + // the collapser background. Deeper controls still win; + // only taps are absorbed, so selection drags pass through. + GestureDetector(behavior: HitTestBehavior.opaque, onTap: () {}, child: child), + ], ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _headerRow(tokens), - // Even padding around the inner item canvas (T-305): the sides + - // top match, and each inner item carries a matching bottom margin - // (so the last item's margin is the bottom inset and items in a - // multi-item run are evenly separated) — hence bottom 0 here. - Padding( - padding: const EdgeInsets.fromLTRB(kClideCardHeaderPadH, kClideCardHeaderPadH, kClideCardHeaderPadH, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - for (final child in widget.children) - // Each item opaquely consumes its full bounds so a body - // tap interacts with that card (or does nothing), never - // the collapser background. Deeper controls still win; - // only taps are absorbed, so selection drags pass through. - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () {}, - child: child, - ), - ], - ), - ), - ], - ), ], ), - ); + ], + ), + ); /// The explicit, focusable collapse control in the expanded header. A /// background tap alone is not keyboard/AT reachable, so this keeps the /// control on the Tab path and Enter/Space-activatable (D-78). Widget _headerRow(SurfaceTokens tokens) => ClideTappable( - focusNode: _controlFocus, - onTap: _toggle, - tooltip: 'Collapse', - builder: (context, hovered, focused) => Container( - padding: const EdgeInsets.symmetric(horizontal: kClideCardHeaderPadH, vertical: kClideCardHeaderPadV), - decoration: BoxDecoration( - color: (hovered || focused) ? tokens.listItemHoverBackground : tokens.listItemBackground, - border: Border(bottom: BorderSide(color: widget.color ?? tokens.panelBorder)), - ), - child: _headerContent(tokens, expanded: true), - ), - ); + focusNode: _controlFocus, + onTap: _toggle, + tooltip: 'Collapse', + builder: (context, hovered, focused) => Container( + padding: const EdgeInsets.symmetric(horizontal: kClideCardHeaderPadH, vertical: kClideCardHeaderPadV), + decoration: BoxDecoration( + color: (hovered || focused) ? tokens.listItemHoverBackground : tokens.listItemBackground, + border: Border(bottom: BorderSide(color: widget.color ?? tokens.panelBorder)), + ), + child: _headerContent(tokens, expanded: true), + ), + ); } diff --git a/lib/widgets/src/clide_column_hat.dart b/lib/widgets/src/clide_column_hat.dart index 907f9c0e..d542c059 100644 --- a/lib/widgets/src/clide_column_hat.dart +++ b/lib/widgets/src/clide_column_hat.dart @@ -117,11 +117,7 @@ class _WinButton extends StatelessWidget { height: hatHeight, color: hovered ? hoverBg : null, alignment: Alignment.center, - child: ClideIcon( - icon, - size: 14, - color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.globalTextMuted, - ), + child: ClideIcon(icon, size: 14, color: hovered && isClose ? tokens.windowControlCloseHoverForeground : tokens.globalTextMuted), ), ); } diff --git a/lib/widgets/src/clide_divider.dart b/lib/widgets/src/clide_divider.dart index c2704075..484f7689 100644 --- a/lib/widgets/src/clide_divider.dart +++ b/lib/widgets/src/clide_divider.dart @@ -2,11 +2,7 @@ import 'package:clide/kernel/src/theme/controller.dart'; import 'package:flutter/widgets.dart'; class ClideDivider extends StatelessWidget { - const ClideDivider({ - super.key, - this.axis = Axis.horizontal, - this.thickness = 1, - }); + const ClideDivider({super.key, this.axis = Axis.horizontal, this.thickness = 1}); final Axis axis; final double thickness; @@ -14,10 +10,6 @@ class ClideDivider extends StatelessWidget { @override Widget build(BuildContext context) { final color = ClideTheme.of(context).surface.dividerColor; - return Container( - width: axis == Axis.vertical ? thickness : null, - height: axis == Axis.horizontal ? thickness : null, - color: color, - ); + return Container(width: axis == Axis.vertical ? thickness : null, height: axis == Axis.horizontal ? thickness : null, color: color); } } diff --git a/lib/widgets/src/clide_file_image.dart b/lib/widgets/src/clide_file_image.dart index aeae00e2..84398016 100644 --- a/lib/widgets/src/clide_file_image.dart +++ b/lib/widgets/src/clide_file_image.dart @@ -13,9 +13,7 @@ import 'package:flutter/widgets.dart'; /// into `==`/`hashCode` makes an overwrite a fresh key → a cache miss → a /// re-read of the current bytes; an unchanged file still hits the cache. class ClideFileImage extends FileImage { - ClideFileImage(String path, {double scale = 1.0}) - : _stamp = _stampOf(path), - super(File(path), scale: scale); + ClideFileImage(String path, {double scale = 1.0}) : _stamp = _stampOf(path), super(File(path), scale: scale); /// mtime ⊕ size — changes on any in-place overwrite (a write bumps mtime; a /// different length bumps size even within one clock tick). 0 if the file diff --git a/lib/widgets/src/clide_icon.dart b/lib/widgets/src/clide_icon.dart index 104e2369..7fb5f5ae 100644 --- a/lib/widgets/src/clide_icon.dart +++ b/lib/widgets/src/clide_icon.dart @@ -29,12 +29,7 @@ class EmptyIconPainter extends ClideIconPainter { } class ClideIcon extends StatelessWidget { - const ClideIcon( - this.painter, { - super.key, - this.size = 14, - this.color, - }); + const ClideIcon(this.painter, {super.key, this.size = 14, this.color}); final ClideIconPainter painter; final double size; diff --git a/lib/widgets/src/clide_icon_rail.dart b/lib/widgets/src/clide_icon_rail.dart index f0170a38..77b1576c 100644 --- a/lib/widgets/src/clide_icon_rail.dart +++ b/lib/widgets/src/clide_icon_rail.dart @@ -4,11 +4,7 @@ import 'package:clide/widgets/src/clide_tappable.dart'; import 'package:flutter/widgets.dart'; class ClideIconRailItem { - const ClideIconRailItem({ - required this.id, - required this.icon, - required this.tooltip, - }); + const ClideIconRailItem({required this.id, required this.icon, required this.tooltip}); final String id; final ClideIconPainter icon; @@ -16,12 +12,7 @@ class ClideIconRailItem { } class ClideIconRail extends StatelessWidget { - const ClideIconRail({ - super.key, - required this.items, - required this.activeId, - required this.onSelect, - }); + const ClideIconRail({super.key, required this.items, required this.activeId, required this.onSelect}); final List items; final String? activeId; @@ -42,14 +33,7 @@ class ClideIconRail extends StatelessWidget { constraints: BoxConstraints(minWidth: minWidth), child: Row( mainAxisAlignment: MainAxisAlignment.center, - children: [ - for (final item in items) - _RailButton( - item: item, - active: item.id == activeId, - onTap: () => onSelect(item.id), - ), - ], + children: [for (final item in items) _RailButton(item: item, active: item.id == activeId, onTap: () => onSelect(item.id))], ), ), ); @@ -59,11 +43,7 @@ class ClideIconRail extends StatelessWidget { } class _RailButton extends StatelessWidget { - const _RailButton({ - required this.item, - required this.active, - required this.onTap, - }); + const _RailButton({required this.item, required this.active, required this.onTap}); final ClideIconRailItem item; final bool active; @@ -83,17 +63,12 @@ class _RailButton extends StatelessWidget { final color = active ? tokens.globalForeground : hovered - ? tokens.sidebarForeground - : tokens.sidebarSectionHeader; + ? tokens.sidebarForeground + : tokens.sidebarSectionHeader; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: active ? tokens.tabActiveBorder : const Color(0x00000000), - width: 2, - ), - ), + border: Border(bottom: BorderSide(color: active ? tokens.tabActiveBorder : const Color(0x00000000), width: 2)), ), child: ClideIcon(item.icon, size: 16, color: color), ); diff --git a/lib/widgets/src/clide_lightbox.dart b/lib/widgets/src/clide_lightbox.dart index 3eb58781..fa244474 100644 --- a/lib/widgets/src/clide_lightbox.dart +++ b/lib/widgets/src/clide_lightbox.dart @@ -22,13 +22,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class ClideLightbox extends StatefulWidget { - const ClideLightbox({ - super.key, - required this.child, - required this.onDismiss, - this.minScale = 0.5, - this.maxScale = 8.0, - }); + const ClideLightbox({super.key, required this.child, required this.onDismiss, this.minScale = 0.5, this.maxScale = 8.0}); /// The content to zoom — constrained to the viewport on open (pass an image /// with `fit: BoxFit.contain` so it fits, then scales on zoom). @@ -159,17 +153,10 @@ class _ClideLightboxState extends State { right: 0, child: Center( child: DecoratedBox( - decoration: BoxDecoration( - color: tokens.panelHeader, - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: tokens.panelHeader, borderRadius: BorderRadius.circular(4)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - child: ClideText( - 'scroll to zoom · double-click to reset · Esc to close', - fontSize: clideFontMeta, - color: tokens.globalTextMuted, - ), + child: ClideText('scroll to zoom · double-click to reset · Esc to close', fontSize: clideFontMeta, color: tokens.globalTextMuted), ), ), ), diff --git a/lib/widgets/src/clide_markdown.dart b/lib/widgets/src/clide_markdown.dart index 5155a747..7cf94b41 100644 --- a/lib/widgets/src/clide_markdown.dart +++ b/lib/widgets/src/clide_markdown.dart @@ -118,11 +118,7 @@ class ClideMarkdown extends StatelessWidget { onOpenFile: onOpenFile, ); final widgets = _buildNodes(nodes, tokens, hooks); - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: widgets, - ); + return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: widgets); } static const _inlineTags = {'strong', 'em', 'code', 'a', 'del', 'br', 'img', 'span'}; @@ -147,19 +143,21 @@ class ClideMarkdown extends StatelessWidget { spans.add(_inlineElementSpan(n, tokens, hooks)); } } - out.add(Text.rich( - TextSpan( - style: TextStyle( - fontFamily: clideUiFamily, - fontFamilyFallback: clideUiFamilyFallback, - fontWeight: clideUiDefaultWeight, - color: tokens.globalForeground, - fontSize: _fontSize, - height: _lineHeight, + out.add( + Text.rich( + TextSpan( + style: TextStyle( + fontFamily: clideUiFamily, + fontFamilyFallback: clideUiFamilyFallback, + fontWeight: clideUiDefaultWeight, + color: tokens.globalForeground, + fontSize: _fontSize, + height: _lineHeight, + ), + children: spans, ), - children: spans, ), - )); + ); inlineRun.clear(); } @@ -202,10 +200,7 @@ class ClideMarkdown extends StatelessWidget { child: _inlineText(el, tokens, hooks, fontSize: clideFontBody, fontWeight: FontWeight.w600), ); case 'p': - return Padding( - padding: const EdgeInsets.only(bottom: 14), - child: _inlineRichText(el, tokens, hooks), - ); + return Padding(padding: const EdgeInsets.only(bottom: 14), child: _inlineRichText(el, tokens, hooks)); case 'ul': return Padding( padding: const EdgeInsets.only(left: 2, bottom: 14), @@ -214,7 +209,7 @@ class ClideMarkdown extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ for (final c in el.children ?? const []) - if (c is md.Element) _buildListItem(c, tokens, hooks, ordered: false) + if (c is md.Element) _buildListItem(c, tokens, hooks, ordered: false), ], ), ); @@ -234,7 +229,9 @@ class ClideMarkdown extends StatelessWidget { return Container( margin: const EdgeInsets.only(bottom: 14), padding: const EdgeInsets.only(left: 6), - decoration: BoxDecoration(border: Border(left: BorderSide(color: tokens.globalTextMuted, width: 3))), + decoration: BoxDecoration( + border: Border(left: BorderSide(color: tokens.globalTextMuted, width: 3)), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, @@ -256,10 +253,7 @@ class ClideMarkdown extends StatelessWidget { case 'hr': return Padding(padding: const EdgeInsets.symmetric(vertical: 8), child: ClideDivider()); case 'table': - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: _buildTable(el, tokens, hooks), - ); + return Padding(padding: const EdgeInsets.only(bottom: 8), child: _buildTable(el, tokens, hooks)); default: return _inlineRichText(el, tokens, hooks); } @@ -295,16 +289,24 @@ class ClideMarkdown extends StatelessWidget { final isHeader = row.tag == 'tr' && (child.tag == 'thead'); for (final cell in row.children ?? const []) { if (cell is! md.Element) continue; - cells.add(Padding( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), - child: _inlineText(cell, tokens, hooks, fontWeight: isHeader ? FontWeight.w600 : null), - )); + cells.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), + child: _inlineText(cell, tokens, hooks, fontWeight: isHeader ? FontWeight.w600 : null), + ), + ); } if (cells.isNotEmpty) { - rows.add(TableRow( - decoration: isHeader ? BoxDecoration(border: Border(bottom: BorderSide(color: tokens.dividerColor))) : null, - children: cells, - )); + rows.add( + TableRow( + decoration: isHeader + ? BoxDecoration( + border: Border(bottom: BorderSide(color: tokens.dividerColor)), + ) + : null, + children: cells, + ), + ); } } } @@ -353,7 +355,7 @@ class ClideMarkdown extends StatelessWidget { style: const TextStyle(fontWeight: FontWeight.w700), children: [ for (final c in el.children ?? const []) - if (c is md.Text) ..._linkifyText(_unescapeHtml(c.text), tokens, hooks) else if (c is md.Element) _inlineElementSpan(c, tokens, hooks) + if (c is md.Text) ..._linkifyText(_unescapeHtml(c.text), tokens, hooks) else if (c is md.Element) _inlineElementSpan(c, tokens, hooks), ], ); case 'em': @@ -361,7 +363,7 @@ class ClideMarkdown extends StatelessWidget { style: const TextStyle(fontStyle: FontStyle.italic), children: [ for (final c in el.children ?? const []) - if (c is md.Text) ..._linkifyText(_unescapeHtml(c.text), tokens, hooks) else if (c is md.Element) _inlineElementSpan(c, tokens, hooks) + if (c is md.Text) ..._linkifyText(_unescapeHtml(c.text), tokens, hooks) else if (c is md.Element) _inlineElementSpan(c, tokens, hooks), ], ); case 'code': @@ -420,10 +422,12 @@ class ClideMarkdown extends StatelessWidget { var last = 0; for (final m in _imageTokenPattern.allMatches(text)) { if (m.start > last) spans.addAll(_linkifyProse(text.substring(last, m.start), tokens, hooks)); - spans.add(WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Padding(padding: const EdgeInsets.symmetric(horizontal: 2), child: hooks.onImageToken!(m.group(1)!)), - )); + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding(padding: const EdgeInsets.symmetric(horizontal: 2), child: hooks.onImageToken!(m.group(1)!)), + ), + ); last = m.end; } if (last < text.length) spans.addAll(_linkifyProse(text.substring(last), tokens, hooks)); @@ -503,7 +507,7 @@ class ClideMarkdown extends StatelessWidget { baseline: TextBaseline.alphabetic, child: ClideTappable( onTap: () => onRecordTap(id), - builder: (_, hovered, __) => Text( + builder: (_, hovered, _) => Text( id, style: TextStyle( color: tokens.globalFocus, @@ -534,7 +538,7 @@ class ClideMarkdown extends StatelessWidget { child: ClideTappable( onTap: () => onOpenFile(absPath, line), tooltip: 'Open in editor', - builder: (_, hovered, __) => Text( + builder: (_, hovered, _) => Text( display, style: TextStyle( color: tokens.globalFocus, @@ -560,7 +564,7 @@ class ClideMarkdown extends StatelessWidget { child: ClideTappable( onTap: () => onLinkTap(href), tooltip: href, - builder: (_, hovered, __) => Text( + builder: (_, hovered, _) => Text( text, style: TextStyle( color: tokens.globalFocus, diff --git a/lib/widgets/src/clide_marquee.dart b/lib/widgets/src/clide_marquee.dart index fc233bde..f1d09975 100644 --- a/lib/widgets/src/clide_marquee.dart +++ b/lib/widgets/src/clide_marquee.dart @@ -10,12 +10,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; class ClideMarquee extends StatefulWidget { - const ClideMarquee({ - super.key, - required this.child, - this.pxPerSecond = 28, - this.gap = 48, - }); + const ClideMarquee({super.key, required this.child, this.pxPerSecond = 28, this.gap = 48}); final Widget child; diff --git a/lib/widgets/src/clide_menu.dart b/lib/widgets/src/clide_menu.dart index 95bfe512..b89a828a 100644 --- a/lib/widgets/src/clide_menu.dart +++ b/lib/widgets/src/clide_menu.dart @@ -254,9 +254,7 @@ class _ClideMenuState extends State { final col = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - for (var i = 0; i < widget.entries.length; i++) _row(i, widget.entries[i], t), - ], + children: [for (var i = 0; i < widget.entries.length; i++) _row(i, widget.entries[i], t)], ); return Focus( focusNode: _focus, @@ -264,11 +262,7 @@ class _ClideMenuState extends State { onKeyEvent: _onKey, child: IntrinsicWidth( child: Container( - constraints: BoxConstraints( - minWidth: widget.minWidth, - maxWidth: widget.maxWidth, - maxHeight: widget.maxHeight ?? double.infinity, - ), + constraints: BoxConstraints(minWidth: widget.minWidth, maxWidth: widget.maxWidth, maxHeight: widget.maxHeight ?? double.infinity), decoration: BoxDecoration( color: t.dropdownBackground, border: Border.all(color: t.dropdownBorder), @@ -285,9 +279,9 @@ class _ClideMenuState extends State { Widget _row(int index, ClideMenuEntry entry, SurfaceTokens t) { return switch (entry) { ClideMenuSeparator() => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Container(height: 1, color: t.dividerColor), - ), + padding: const EdgeInsets.symmetric(vertical: 4), + child: Container(height: 1, color: t.dividerColor), + ), ClideMenuItem() => _itemRow(index, entry, t), }; } @@ -310,11 +304,10 @@ class _ClideMenuState extends State { color: highlighted || (hovered && item.enabled) ? t.listItemHoverBackground : null, child: Row( children: [ - if (item.leading != null) ...[ - ClideIcon(item.leading!, size: 14, color: fg), - const SizedBox(width: 8), - ], - Expanded(child: ClideText(item.label, fontSize: clideFontSmall, color: fg, maxLines: 1)), + if (item.leading != null) ...[ClideIcon(item.leading!, size: 14, color: fg), const SizedBox(width: 8)], + Expanded( + child: ClideText(item.label, fontSize: clideFontSmall, color: fg, maxLines: 1), + ), if (item.trailing != null) item.trailing! else if (item.active) ...[ diff --git a/lib/widgets/src/clide_palette.dart b/lib/widgets/src/clide_palette.dart index 9a658e39..77d5fccc 100644 --- a/lib/widgets/src/clide_palette.dart +++ b/lib/widgets/src/clide_palette.dart @@ -61,7 +61,10 @@ class _ClidePaletteState extends State { void _syncFromController() { final f = _palette?.filter ?? ''; if (_input.text != f) { - _input.value = TextEditingValue(text: f, selection: TextSelection.collapsed(offset: f.length)); + _input.value = TextEditingValue( + text: f, + selection: TextSelection.collapsed(offset: f.length), + ); } } @@ -126,13 +129,7 @@ class _ClidePaletteState extends State { color: tokens.dropdownBackground, border: Border.all(color: tokens.dropdownBorder), borderRadius: BorderRadius.circular(6), - boxShadow: [ - BoxShadow( - color: tokens.shadowAmbient, - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], + boxShadow: [BoxShadow(color: tokens.shadowAmbient, blurRadius: 12, offset: const Offset(0, 4))], ), child: Column( mainAxisSize: MainAxisSize.min, @@ -142,11 +139,7 @@ class _ClidePaletteState extends State { child: EditableText( controller: _input, focusNode: _focus, - style: TextStyle( - fontFamily: clideMonoFamily, - fontSize: clideFontMono, - color: tokens.dropdownForeground, - ), + style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.dropdownForeground), cursorColor: tokens.globalFocus, backgroundCursorColor: tokens.globalFocus, maxLines: 1, @@ -194,14 +187,7 @@ class _ClidePaletteState extends State { } class _PaletteItem extends StatefulWidget { - const _PaletteItem({ - super.key, - required this.title, - required this.command, - required this.onTap, - required this.highlighted, - this.binding, - }); + const _PaletteItem({super.key, required this.title, required this.command, required this.onTap, required this.highlighted, this.binding}); final String title; final String command; @@ -230,24 +216,13 @@ class _PaletteItemState extends State<_PaletteItem> { color: selected ? tokens.listItemSelectedBackground : _hover - ? tokens.listItemHoverBackground - : null, + ? tokens.listItemHoverBackground + : null, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( children: [ - Expanded( - child: ClideText( - widget.title, - color: selected ? tokens.listItemSelectedForeground : tokens.listItemForeground, - ), - ), - if (widget.binding != null) - ClideText( - widget.binding!, - fontSize: clideFontCaption, - fontFamily: clideMonoFamily, - color: tokens.globalTextMuted, - ), + Expanded(child: ClideText(widget.title, color: selected ? tokens.listItemSelectedForeground : tokens.listItemForeground)), + if (widget.binding != null) ClideText(widget.binding!, fontSize: clideFontCaption, fontFamily: clideMonoFamily, color: tokens.globalTextMuted), ], ), ), diff --git a/lib/widgets/src/clide_pane.dart b/lib/widgets/src/clide_pane.dart index 7a1ba703..c7cde994 100644 --- a/lib/widgets/src/clide_pane.dart +++ b/lib/widgets/src/clide_pane.dart @@ -16,13 +16,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; class ClidePane extends StatefulWidget { - const ClidePane({ - super.key, - required this.contributionId, - required this.child, - this.statusWidget, - this.active = true, - }); + const ClidePane({super.key, required this.contributionId, required this.child, this.statusWidget, this.active = true}); /// The workspace contribution id this pane belongs to (what /// [FocusTracker.activeContributionId] reports when it's focused). diff --git a/lib/widgets/src/clide_pane_chrome.dart b/lib/widgets/src/clide_pane_chrome.dart index 8f355c18..7cbcb265 100644 --- a/lib/widgets/src/clide_pane_chrome.dart +++ b/lib/widgets/src/clide_pane_chrome.dart @@ -13,15 +13,7 @@ import 'typography.dart'; /// underneath. `ClidePtyView`, diff views, canvas tabs, graph tabs — /// everything with a "pane header" surface reuses this. class ClidePaneChrome extends StatelessWidget { - const ClidePaneChrome({ - super.key, - required this.title, - required this.child, - this.subtitle, - this.leading, - this.onClose, - this.trailing, - }); + const ClidePaneChrome({super.key, required this.title, required this.child, this.subtitle, this.leading, this.onClose, this.trailing}); /// Primary label in the header — typically the pane kind + an /// abbreviated path / session name (`terminal — ~/clide`). @@ -52,13 +44,7 @@ class ClidePaneChrome extends StatelessWidget { color: tokens.panelBackground, child: Column( children: [ - _Header( - title: title, - subtitle: subtitle, - leading: leading, - onClose: onClose, - trailing: trailing, - ), + _Header(title: title, subtitle: subtitle, leading: leading, onClose: onClose, trailing: trailing), const ClideDivider(), Expanded(child: child), ], @@ -85,14 +71,8 @@ class _CloseButton extends StatelessWidget { width: 20, height: 20, alignment: Alignment.center, - decoration: BoxDecoration( - color: hovered ? tokens.tabCloseHover : null, - ), - child: ClideIcon( - const CloseIcon(), - size: 10, - color: tokens.panelHeaderForeground, - ), + decoration: BoxDecoration(color: hovered ? tokens.tabCloseHover : null), + child: ClideIcon(const CloseIcon(), size: 10, color: tokens.panelHeaderForeground), ), ), ); @@ -100,13 +80,7 @@ class _CloseButton extends StatelessWidget { } class _Header extends StatelessWidget { - const _Header({ - required this.title, - required this.subtitle, - required this.leading, - required this.onClose, - required this.trailing, - }); + const _Header({required this.title, required this.subtitle, required this.leading, required this.onClose, required this.trailing}); final String title; final String? subtitle; @@ -133,31 +107,12 @@ class _Header extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - ClideText( - title, - fontSize: clideFontCaption, - color: tokens.panelHeaderForeground, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (subtitle != null) - ClideText( - subtitle!, - fontSize: clideFontCaption, - muted: true, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + ClideText(title, fontSize: clideFontCaption, color: tokens.panelHeaderForeground, maxLines: 1, overflow: TextOverflow.ellipsis), + if (subtitle != null) ClideText(subtitle!, fontSize: clideFontCaption, muted: true, maxLines: 1, overflow: TextOverflow.ellipsis), ], ), ), - if (trailing != null) - ...trailing!.map( - (w) => Padding( - padding: const EdgeInsets.only(left: 6), - child: w, - ), - ), + if (trailing != null) ...trailing!.map((w) => Padding(padding: const EdgeInsets.only(left: 6), child: w)), if (onClose != null) Padding( padding: const EdgeInsets.only(left: 6), diff --git a/lib/widgets/src/clide_pty_view.dart b/lib/widgets/src/clide_pty_view.dart index 22112d14..6a6ccb2b 100644 --- a/lib/widgets/src/clide_pty_view.dart +++ b/lib/widgets/src/clide_pty_view.dart @@ -14,14 +14,7 @@ import 'package:clide/src/terminal/terminal.dart'; /// `terminal.write()` is the consumer's job (typically a builtin /// extension — see `builtin.terminal` / `builtin.claude`). class ClidePtyView extends StatelessWidget { - const ClidePtyView({ - super.key, - required this.terminal, - this.label, - this.focusNode, - this.autofocus = false, - this.fontSize = clideFontMono, - }); + const ClidePtyView({super.key, required this.terminal, this.label, this.focusNode, this.autofocus = false, this.fontSize = clideFontMono}); final Terminal terminal; @@ -49,11 +42,7 @@ class ClidePtyView extends StatelessWidget { focusNode: focusNode, autofocus: autofocus, theme: _buildTheme(tokens), - textStyle: TerminalStyle( - fontSize: fontSize, - fontFamily: clideMonoFamily, - fontFamilyFallback: clideMonoFamilyFallback, - ), + textStyle: TerminalStyle(fontSize: fontSize, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback), padding: const EdgeInsets.all(2), backgroundOpacity: 1, cursorType: TerminalCursorType.block, diff --git a/lib/widgets/src/clide_scrollbar.dart b/lib/widgets/src/clide_scrollbar.dart index 1420a28e..3d522aa8 100644 --- a/lib/widgets/src/clide_scrollbar.dart +++ b/lib/widgets/src/clide_scrollbar.dart @@ -4,12 +4,7 @@ import 'package:flutter/widgets.dart'; /// Thin themed scrollbar. Tier-0 shell; more refined scrolling (velocity /// multiplier, keyboard nav) lands with the editor. class ClideScrollbar extends StatelessWidget { - const ClideScrollbar({ - super.key, - required this.controller, - required this.child, - this.axis = Axis.vertical, - }); + const ClideScrollbar({super.key, required this.controller, required this.child, this.axis = Axis.vertical}); final ScrollController controller; final Widget child; @@ -22,25 +17,13 @@ class ClideScrollbar extends StatelessWidget { slider: tokens.scrollbarSlider, sliderHover: tokens.scrollbarSliderHover, track: tokens.scrollbarTrack, - child: RawScrollbar( - controller: controller, - thumbColor: tokens.scrollbarSlider, - thickness: 8, - radius: const Radius.circular(4), - child: child, - ), + child: RawScrollbar(controller: controller, thumbColor: tokens.scrollbarSlider, thickness: 8, radius: const Radius.circular(4), child: child), ); } } class ScrollbarTheme extends InheritedWidget { - const ScrollbarTheme({ - super.key, - required this.slider, - required this.sliderHover, - required this.track, - required super.child, - }); + const ScrollbarTheme({super.key, required this.slider, required this.sliderHover, required this.track, required super.child}); final Color slider; final Color sliderHover; diff --git a/lib/widgets/src/clide_selection_area.dart b/lib/widgets/src/clide_selection_area.dart index 48a3a25d..a6cafc53 100644 --- a/lib/widgets/src/clide_selection_area.dart +++ b/lib/widgets/src/clide_selection_area.dart @@ -33,11 +33,7 @@ class _ClideSelectionAreaState extends State { @override Widget build(BuildContext context) { return DefaultTextEditingShortcuts( - child: SelectableRegion( - focusNode: _focusNode, - selectionControls: emptyTextSelectionControls, - child: widget.child, - ), + child: SelectableRegion(focusNode: _focusNode, selectionControls: emptyTextSelectionControls, child: widget.child), ); } } diff --git a/lib/widgets/src/clide_spine.dart b/lib/widgets/src/clide_spine.dart index 67703fab..62263dfc 100644 --- a/lib/widgets/src/clide_spine.dart +++ b/lib/widgets/src/clide_spine.dart @@ -5,13 +5,7 @@ import 'package:clide/widgets/src/clide_tappable.dart'; import 'package:flutter/widgets.dart'; class ClideSpine extends StatelessWidget { - const ClideSpine({ - super.key, - required this.label, - required this.onExpand, - this.side = SpineSide.left, - this.badgeCount = 0, - }); + const ClideSpine({super.key, required this.label, required this.onExpand, this.side = SpineSide.left, this.badgeCount = 0}); final String label; final VoidCallback onExpand; @@ -34,10 +28,7 @@ class ClideSpine extends StatelessWidget { width: ClideSpine.width, decoration: BoxDecoration( color: hovered ? tokens.sidebarItemHover : tokens.chromeBackground, - border: Border( - left: side == SpineSide.right ? borderSide : BorderSide.none, - right: side == SpineSide.left ? borderSide : BorderSide.none, - ), + border: Border(left: side == SpineSide.right ? borderSide : BorderSide.none, right: side == SpineSide.left ? borderSide : BorderSide.none), ), child: Stack( children: [ @@ -46,11 +37,7 @@ class ClideSpine extends StatelessWidget { angle: side == SpineSide.left ? -math.pi / 2 : math.pi / 2, child: Text( label, - style: TextStyle( - fontSize: 9, - color: tokens.globalTextMuted, - letterSpacing: 0.5, - ), + style: TextStyle(fontSize: 9, color: tokens.globalTextMuted, letterSpacing: 0.5), maxLines: 1, overflow: TextOverflow.clip, ), @@ -65,10 +52,7 @@ class ClideSpine extends StatelessWidget { child: Container( width: 6, height: 6, - decoration: BoxDecoration( - color: tokens.statusInfo, - shape: BoxShape.circle, - ), + decoration: BoxDecoration(color: tokens.statusInfo, shape: BoxShape.circle), ), ), ), diff --git a/lib/widgets/src/clide_spinner.dart b/lib/widgets/src/clide_spinner.dart index 8da45a7c..2af578e2 100644 --- a/lib/widgets/src/clide_spinner.dart +++ b/lib/widgets/src/clide_spinner.dart @@ -16,13 +16,7 @@ import 'package:clide/widgets/src/clide_svg_view.dart'; import 'package:flutter/widgets.dart'; class ClideSpinner extends StatefulWidget { - const ClideSpinner({ - super.key, - this.size = 14, - this.color, - this.period = const Duration(milliseconds: 1500), - this.semanticLabel, - }); + const ClideSpinner({super.key, this.size = 14, this.color, this.period = const Duration(milliseconds: 1500), this.semanticLabel}); final double size; diff --git a/lib/widgets/src/clide_status_indicator.dart b/lib/widgets/src/clide_status_indicator.dart index 55c62861..188c96e8 100644 --- a/lib/widgets/src/clide_status_indicator.dart +++ b/lib/widgets/src/clide_status_indicator.dart @@ -55,10 +55,7 @@ class _ClideStatusIndicatorState extends State { return Semantics( label: label, container: true, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: glyph, - ), + child: AnimatedSwitcher(duration: const Duration(milliseconds: 200), child: glyph), ); } } diff --git a/lib/widgets/src/clide_surface.dart b/lib/widgets/src/clide_surface.dart index b968a019..81dcf84b 100644 --- a/lib/widgets/src/clide_surface.dart +++ b/lib/widgets/src/clide_surface.dart @@ -5,16 +5,7 @@ import 'package:flutter/widgets.dart'; /// for clide widgets — pulls color, border, and padding from the /// current theme's surface tokens. class ClideSurface extends StatelessWidget { - const ClideSurface({ - super.key, - this.child, - this.color, - this.border, - this.padding = EdgeInsets.zero, - this.width, - this.height, - this.borderRadius, - }); + const ClideSurface({super.key, this.child, this.color, this.border, this.padding = EdgeInsets.zero, this.width, this.height, this.borderRadius}); final Widget? child; final Color? color; diff --git a/lib/widgets/src/clide_svg_view.dart b/lib/widgets/src/clide_svg_view.dart index 122ef889..782377fe 100644 --- a/lib/widgets/src/clide_svg_view.dart +++ b/lib/widgets/src/clide_svg_view.dart @@ -15,9 +15,7 @@ class ClideSvgView extends StatelessWidget { Widget build(BuildContext context) { Widget child; if (assetPath != null) { - child = ScalableImageWidget.fromSISource( - si: ScalableImageSource.fromSvg(rootBundle, assetPath!), - ); + child = ScalableImageWidget.fromSISource(si: ScalableImageSource.fromSvg(rootBundle, assetPath!)); } else if (svgString != null) { final si = ScalableImage.fromSvgString(svgString!); child = ScalableImageWidget(si: si); diff --git a/lib/widgets/src/clide_tab_bar.dart b/lib/widgets/src/clide_tab_bar.dart index 6e54b8fb..1cb45f14 100644 --- a/lib/widgets/src/clide_tab_bar.dart +++ b/lib/widgets/src/clide_tab_bar.dart @@ -6,11 +6,7 @@ import 'package:flutter/widgets.dart'; @immutable class ClideTabItem { - const ClideTabItem({ - required this.id, - required this.title, - this.icon, - }); + const ClideTabItem({required this.id, required this.title, this.icon}); final String id; final String title; @@ -18,14 +14,7 @@ class ClideTabItem { } class ClideTabBar extends StatelessWidget { - const ClideTabBar({ - super.key, - required this.items, - required this.activeId, - required this.onSelect, - this.height = 28, - this.semanticContainerLabel, - }); + const ClideTabBar({super.key, required this.items, required this.activeId, required this.onSelect, this.height = 28, this.semanticContainerLabel}); final List items; final String? activeId; @@ -49,14 +38,7 @@ class ClideTabBar extends StatelessWidget { margin: const EdgeInsets.only(top: 1), color: tokens.tabBarBackground, child: Row( - children: [ - for (final item in items) - _Tab( - item: item, - active: item.id == activeId, - onTap: () => onSelect(item.id), - ), - ], + children: [for (final item in items) _Tab(item: item, active: item.id == activeId, onTap: () => onSelect(item.id))], ), ), ); @@ -89,20 +71,12 @@ class _Tab extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: bg, - border: Border( - bottom: BorderSide( - color: active ? tokens.tabActiveBorder : const Color(0x00000000), - width: 2, - ), - ), + border: Border(bottom: BorderSide(color: active ? tokens.tabActiveBorder : const Color(0x00000000), width: 2)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (item.icon != null) ...[ - ClideIcon(item.icon!, size: 12, color: fg), - const SizedBox(width: 6), - ], + if (item.icon != null) ...[ClideIcon(item.icon!, size: 12, color: fg), const SizedBox(width: 6)], ClideText(item.title, color: fg), ], ), diff --git a/lib/widgets/src/clide_tappable.dart b/lib/widgets/src/clide_tappable.dart index 56c8b0d8..2247b426 100644 --- a/lib/widgets/src/clide_tappable.dart +++ b/lib/widgets/src/clide_tappable.dart @@ -95,10 +95,7 @@ class _ClideTappableState extends State { child = DecoratedBox( position: DecorationPosition.foreground, decoration: BoxDecoration( - border: Border.all( - color: _focused ? tokens.globalFocus : const Color(0x00000000), - width: 2, - ), + border: Border.all(color: _focused ? tokens.globalFocus : const Color(0x00000000), width: 2), borderRadius: BorderRadius.circular(3), ), child: child, @@ -109,16 +106,8 @@ class _ClideTappableState extends State { // provider. The reverse nesting would leave Actions as a descendant // of the focused context — unreachable. child = Actions( - actions: >{ - ActivateIntent: CallbackAction(onInvoke: _activate), - }, - child: Focus( - focusNode: _effectiveFocus, - canRequestFocus: enabled, - autofocus: widget.autofocus, - onFocusChange: _setFocused, - child: child, - ), + actions: >{ActivateIntent: CallbackAction(onInvoke: _activate)}, + child: Focus(focusNode: _effectiveFocus, canRequestFocus: enabled, autofocus: widget.autofocus, onFocusChange: _setFocused, child: child), ); if (widget.tooltip != null) { diff --git a/lib/widgets/src/clide_text.dart b/lib/widgets/src/clide_text.dart index e5bbfcbe..fbf486c3 100644 --- a/lib/widgets/src/clide_text.dart +++ b/lib/widgets/src/clide_text.dart @@ -43,12 +43,7 @@ class ClideText extends StatelessWidget { maxLines: maxLines, overflow: overflow, textAlign: textAlign, - style: TextStyle( - color: resolved, - fontSize: fontSize, - fontFamily: fontFamily, - fontWeight: fontWeight, - ), + style: TextStyle(color: resolved, fontSize: fontSize, fontFamily: fontFamily, fontWeight: fontWeight), ); } } diff --git a/lib/widgets/src/clide_toast.dart b/lib/widgets/src/clide_toast.dart index 1d342823..fbd42e0a 100644 --- a/lib/widgets/src/clide_toast.dart +++ b/lib/widgets/src/clide_toast.dart @@ -79,18 +79,17 @@ class _ClideToastState extends State { children: [ ClideIcon(style.icon, size: clideIconStandard, color: style.color), const SizedBox(width: clideGapStandard), - Flexible(child: ClideText(widget.entry.message, fontSize: clideFontBody, maxLines: 3, overflow: TextOverflow.ellipsis)), + Flexible( + child: ClideText(widget.entry.message, fontSize: clideFontBody, maxLines: 3, overflow: TextOverflow.ellipsis), + ), const SizedBox(width: clideGapStandard), Semantics( button: true, label: 'Dismiss notification', child: ClideTappable( onTap: widget.onDismiss, - builder: (ctx, hovered, _) => ClideIcon( - PhosphorIcons.byName('x'), - size: clideIconStandard, - color: hovered ? tokens.globalForeground : tokens.globalTextMuted, - ), + builder: (ctx, hovered, _) => + ClideIcon(PhosphorIcons.byName('x'), size: clideIconStandard, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ], diff --git a/lib/widgets/src/clide_tooltip.dart b/lib/widgets/src/clide_tooltip.dart index 13e80fa9..24c09e7e 100644 --- a/lib/widgets/src/clide_tooltip.dart +++ b/lib/widgets/src/clide_tooltip.dart @@ -3,12 +3,7 @@ import 'package:clide/widgets/src/clide_text.dart'; import 'package:flutter/widgets.dart'; class ClideTooltip extends StatefulWidget { - const ClideTooltip({ - super.key, - required this.message, - required this.child, - this.showDelay = const Duration(milliseconds: 500), - }); + const ClideTooltip({super.key, required this.message, required this.child, this.showDelay = const Duration(milliseconds: 500)}); final String message; final Widget child; @@ -49,17 +44,9 @@ class _ClideTooltipState extends State { final showAbove = spaceBelow < 60; if (showAbove) { - return Positioned( - left: target.dx, - bottom: screenSize.height - target.dy + 4, - child: tooltip, - ); + return Positioned(left: target.dx, bottom: screenSize.height - target.dy + 4, child: tooltip); } - return Positioned( - left: target.dx, - top: target.dy + size.height + 4, - child: tooltip, - ); + return Positioned(left: target.dx, top: target.dy + size.height + 4, child: tooltip); }, ); overlay.insert(_entry!); diff --git a/lib/widgets/src/clide_typeahead.dart b/lib/widgets/src/clide_typeahead.dart index 27f69cc2..870946c5 100644 --- a/lib/widgets/src/clide_typeahead.dart +++ b/lib/widgets/src/clide_typeahead.dart @@ -112,9 +112,7 @@ class _ClideTypeaheadState extends State { controller: widget.navController, minWidth: 0, maxWidth: widget.maxWidth, - entries: [ - for (final s in items) ClideMenuItem(label: widget.formatLabel?.call(s) ?? s, onSelect: () => widget.onSelect(s)), - ], + entries: [for (final s in items) ClideMenuItem(label: widget.formatLabel?.call(s) ?? s, onSelect: () => widget.onSelect(s))], ), ), ); diff --git a/lib/widgets/src/icons/phosphor.dart b/lib/widgets/src/icons/phosphor.dart index 82e54806..08a774af 100644 --- a/lib/widgets/src/icons/phosphor.dart +++ b/lib/widgets/src/icons/phosphor.dart @@ -11,9 +11,7 @@ class PhosphorIconPainter extends ClideIconPainter { @override void paint(ui.Canvas canvas, ui.Color color) { - final builder = ui.ParagraphBuilder( - ui.ParagraphStyle(fontFamily: family, fontSize: 1.0, height: 1.0, textAlign: ui.TextAlign.center), - ) + final builder = ui.ParagraphBuilder(ui.ParagraphStyle(fontFamily: family, fontSize: 1.0, height: 1.0, textAlign: ui.TextAlign.center)) ..pushStyle(ui.TextStyle(color: color, fontFamily: family)) ..addText(String.fromCharCode(codePoint)); final paragraph = builder.build()..layout(const ui.ParagraphConstraints(width: 1.0)); diff --git a/lib/widgets/src/multitab_controller.dart b/lib/widgets/src/multitab_controller.dart index 89c1ae00..980c9376 100644 --- a/lib/widgets/src/multitab_controller.dart +++ b/lib/widgets/src/multitab_controller.dart @@ -5,13 +5,7 @@ import 'package:flutter/foundation.dart'; /// an editor buffer ref). @immutable class MultitabEntry { - const MultitabEntry({ - required this.id, - required this.title, - required this.payload, - this.closeable = true, - this.reorderable = true, - }); + const MultitabEntry({required this.id, required this.title, required this.payload, this.closeable = true, this.reorderable = true}); /// Stable id for this tab. Must be unique within the controller. final String id; @@ -31,12 +25,7 @@ class MultitabEntry { /// reorderable tabs cannot move past a pinned tab on either side. final bool reorderable; - MultitabEntry copyWith({ - String? title, - T? payload, - bool? closeable, - bool? reorderable, - }) { + MultitabEntry copyWith({String? title, T? payload, bool? closeable, bool? reorderable}) { return MultitabEntry( id: id, title: title ?? this.title, diff --git a/lib/widgets/src/multitab_pane.dart b/lib/widgets/src/multitab_pane.dart index ff970712..f66e5fd2 100644 --- a/lib/widgets/src/multitab_pane.dart +++ b/lib/widgets/src/multitab_pane.dart @@ -76,23 +76,14 @@ class MultitabPane extends StatelessWidget { if (!keepAlive) { // Single-body mode: rebuild on every active change. Key by id // so a stable body widget tree gets a fresh State on switch. - return KeyedSubtree( - key: ValueKey('multitab-body-${active.id}'), - child: bodyBuilder(context, active), - ); + return KeyedSubtree(key: ValueKey('multitab-body-${active.id}'), child: bodyBuilder(context, active)); } // Keep-alive mode: every body stays mounted; switching is just // an IndexedStack index change. Bodies preserve their State. final activeIndex = entries.indexWhere((e) => e.id == active.id); return IndexedStack( index: activeIndex < 0 ? 0 : activeIndex, - children: [ - for (final entry in entries) - KeyedSubtree( - key: ValueKey('multitab-body-${entry.id}'), - child: bodyBuilder(context, entry), - ), - ], + children: [for (final entry in entries) KeyedSubtree(key: ValueKey('multitab-body-${entry.id}'), child: bodyBuilder(context, entry))], ); } } @@ -196,13 +187,7 @@ class _ReorderableTabState extends State<_ReorderableTab> { Widget build(BuildContext context) { final tokens = ClideTheme.of(context).surface; - final tabContent = _Tab( - entry: widget.entry, - active: widget.active, - onSelect: widget.onSelect, - onClose: widget.onClose, - tabHeight: widget.tabHeight, - ); + final tabContent = _Tab(entry: widget.entry, active: widget.active, onSelect: widget.onSelect, onClose: widget.onClose, tabHeight: widget.tabHeight); Widget result = DragTarget( onWillAcceptWithDetails: (d) { @@ -219,15 +204,13 @@ class _ReorderableTabState extends State<_ReorderableTab> { setState(() => _isDropTarget = false); widget.onReorderTo(d.data); }, - builder: (context, _, __) => Row( + builder: (context, _, _) => Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 2, height: widget.tabHeight, - child: ColoredBox( - color: _isDropTarget ? tokens.panelActiveBorder : const Color(0x00000000), - ), + child: ColoredBox(color: _isDropTarget ? tokens.panelActiveBorder : const Color(0x00000000)), ), tabContent, ], @@ -238,10 +221,7 @@ class _ReorderableTabState extends State<_ReorderableTab> { result = Draggable( data: widget.entry.id, axis: Axis.horizontal, - feedback: _DragFeedback( - title: widget.entry.title, - tabHeight: widget.tabHeight, - ), + feedback: _DragFeedback(title: widget.entry.title, tabHeight: widget.tabHeight), childWhenDragging: Opacity(opacity: 0.4, child: tabContent), child: result, ); @@ -268,25 +248,13 @@ class _DragFeedback extends StatelessWidget { color: tokens.panelHeader, border: Border.all(color: tokens.panelActiveBorder), ), - child: ClideText( - title, - fontSize: 12, - color: tokens.tabActiveForeground, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), + child: ClideText(title, fontSize: 12, color: tokens.tabActiveForeground, overflow: TextOverflow.ellipsis, maxLines: 1), ); } } class _Tab extends StatefulWidget { - const _Tab({ - required this.entry, - required this.active, - required this.onSelect, - required this.onClose, - required this.tabHeight, - }); + const _Tab({required this.entry, required this.active, required this.onSelect, required this.onClose, required this.tabHeight}); final MultitabEntry entry; final bool active; @@ -320,17 +288,14 @@ class _TabState extends State<_Tab> { excludeSemantics: true, child: ClideTappable( onTap: widget.onSelect, - builder: (context, _, __) => Container( + builder: (context, _, _) => Container( constraints: BoxConstraints(minWidth: 96, maxWidth: 200), height: widget.tabHeight, // Left: text inset (title breathing room). // Right: when there's a close button, match its uniform // icon margin so top/bottom/right are equal — see the // ui-design `geometry.md` "no double-edge padding" rule. - padding: EdgeInsets.only( - left: clideInsetText, - right: widget.onClose != null ? clideInsetIcon : clideInsetText, - ), + padding: EdgeInsets.only(left: clideInsetText, right: widget.onClose != null ? clideInsetIcon : clideInsetText), decoration: BoxDecoration( color: bg, border: Border( @@ -343,13 +308,7 @@ class _TabState extends State<_Tab> { children: [ // Left column: title, takes all remaining space. Expanded( - child: ClideText( - widget.entry.title, - fontSize: 12, - color: fg, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), + child: ClideText(widget.entry.title, fontSize: 12, color: fg, overflow: TextOverflow.ellipsis, maxLines: 1), ), // Right column: close icon, fixed natural width. if (widget.onClose != null) ...[ @@ -362,15 +321,8 @@ class _TabState extends State<_Tab> { width: clideIconHitTarget, height: clideIconHitTarget, alignment: Alignment.center, - decoration: BoxDecoration( - color: hovered ? tokens.listItemHoverBackground : null, - borderRadius: BorderRadius.circular(2), - ), - child: ClideIcon( - const CloseIcon(), - size: clideIconMicro, - color: hovered ? tokens.globalForeground : tokens.globalTextMuted, - ), + decoration: BoxDecoration(color: hovered ? tokens.listItemHoverBackground : null, borderRadius: BorderRadius.circular(2)), + child: ClideIcon(const CloseIcon(), size: clideIconMicro, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ), @@ -402,9 +354,7 @@ class _AddButton extends StatelessWidget { width: clideControlHeight, height: tabHeight, alignment: Alignment.center, - decoration: BoxDecoration( - color: hovered ? tokens.listItemHoverBackground : null, - ), + decoration: BoxDecoration(color: hovered ? tokens.listItemHoverBackground : null), child: ClideText('+', fontSize: clideIconStandard, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), diff --git a/lib/widgets/src/quick_open_overlay.dart b/lib/widgets/src/quick_open_overlay.dart index 74e565af..21a86abb 100644 --- a/lib/widgets/src/quick_open_overlay.dart +++ b/lib/widgets/src/quick_open_overlay.dart @@ -177,9 +177,7 @@ class _QuickOpenOverlayState extends State { color: tokens.dropdownBackground, border: Border.all(color: tokens.dropdownBorder), borderRadius: BorderRadius.circular(6), - boxShadow: [ - BoxShadow(color: tokens.shadowAmbient, blurRadius: 12, offset: const Offset(0, 4)), - ], + boxShadow: [BoxShadow(color: tokens.shadowAmbient, blurRadius: 12, offset: const Offset(0, 4))], ), child: Column( mainAxisSize: MainAxisSize.min, @@ -189,11 +187,7 @@ class _QuickOpenOverlayState extends State { child: EditableText( controller: _input, focusNode: _focus, - style: TextStyle( - fontFamily: clideMonoFamily, - fontSize: clideFontMono, - color: tokens.dropdownForeground, - ), + style: TextStyle(fontFamily: clideMonoFamily, fontSize: clideFontMono, color: tokens.dropdownForeground), cursorColor: tokens.globalFocus, backgroundCursorColor: tokens.globalFocus, maxLines: 1, @@ -246,18 +240,13 @@ class _Hint extends StatelessWidget { @override Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: ClideText(text, fontSize: clideFontCaption, color: tokens.globalTextMuted), - ); + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: ClideText(text, fontSize: clideFontCaption, color: tokens.globalTextMuted), + ); } class _QuickOpenItem extends StatefulWidget { - const _QuickOpenItem({ - super.key, - required this.path, - required this.highlighted, - required this.onTap, - }); + const _QuickOpenItem({super.key, required this.path, required this.highlighted, required this.onTap}); final String path; final bool highlighted; @@ -288,15 +277,12 @@ class _QuickOpenItemState extends State<_QuickOpenItem> { color: selected ? tokens.listItemSelectedBackground : _hover - ? tokens.listItemHoverBackground - : null, + ? tokens.listItemHoverBackground + : null, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( children: [ - ClideText( - name, - color: selected ? tokens.listItemSelectedForeground : tokens.listItemForeground, - ), + ClideText(name, color: selected ? tokens.listItemSelectedForeground : tokens.listItemForeground), const SizedBox(width: 8), Expanded( child: ClideText( diff --git a/pubspec.yaml b/pubspec.yaml index 8b1b6316..12a41596 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,8 +37,12 @@ coverage_floor: 95 # flutter_channel: stable environment: - sdk: ">=3.5.0 <4.0.0" - flutter: ">=3.19.0" + # Honest minimums (not aspirational floors). Drivers: alchemist 0.12 needs + # Flutter >=3.32; markdown 7.3.1 needs Dart ^3.9.0, and Dart 3.9 first ships + # in Flutter 3.35 — so 3.35 is the binding floor. Exact toolchain is pinned + # in .fvmrc (T-353). + sdk: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" dependencies: flutter: diff --git a/test/a11y/i18n_coverage_test.dart b/test/a11y/i18n_coverage_test.dart index e6c56cf0..8ac53f8e 100644 --- a/test/a11y/i18n_coverage_test.dart +++ b/test/a11y/i18n_coverage_test.dart @@ -16,29 +16,10 @@ void main() { /// (namespace, key) pairs referenced by Tier-0 built-ins. Extend when /// new keys land. const referenced = >{ - 'builtin.welcome': [ - 'title', - 'subtitle', - 'open-project', - 'open-project.hint', - 'tab.title', - ], - 'builtin.ipc-status': [ - 'connected', - 'connected.hint', - 'disconnected', - 'disconnected.hint', - ], - 'builtin.theme-picker': [ - 'modal.title', - 'modal.cancel', - 'modal.cancel.hint', - 'row.select.hint', - ], - 'builtin.default-layout': [ - 'command.reset', - 'preset.classic', - ], + 'builtin.welcome': ['title', 'subtitle', 'open-project', 'open-project.hint', 'tab.title'], + 'builtin.ipc-status': ['connected', 'connected.hint', 'disconnected', 'disconnected.hint'], + 'builtin.theme-picker': ['modal.title', 'modal.cancel', 'modal.cancel.hint', 'row.select.hint'], + 'builtin.default-layout': ['command.reset', 'preset.classic'], }; group('i18n coverage (Tier 0)', () { @@ -47,17 +28,9 @@ void main() { test('$ns catalog contains every referenced key', () async { final loader = AssetCatalogLoader(bundle: rootBundle); final catalog = await loader.load(ns, const Locale('en', 'US')); - expect( - catalog, - isNotEmpty, - reason: 'catalog for "$ns" failed to load (asset path wrong?)', - ); + expect(catalog, isNotEmpty, reason: 'catalog for "$ns" failed to load (asset path wrong?)'); for (final key in entry.value) { - expect( - catalog.containsKey(key), - isTrue, - reason: 'namespace "$ns" catalog is missing key "$key"', - ); + expect(catalog.containsKey(key), isTrue, reason: 'namespace "$ns" catalog is missing key "$key"'); } }); } diff --git a/test/a11y/keyboard_traversal_test.dart b/test/a11y/keyboard_traversal_test.dart index bc0d5cb3..5ed2415d 100644 --- a/test/a11y/keyboard_traversal_test.dart +++ b/test/a11y/keyboard_traversal_test.dart @@ -34,9 +34,7 @@ void main() { }); testWidgets('interactive widgets expose tap actions to a11y', (tester) async { - await tester.pumpWidget( - harness(f, ClideButton(label: 'Save', onPressed: () {})), - ); + await tester.pumpWidget(harness(f, ClideButton(label: 'Save', onPressed: () {}))); final handle = tester.ensureSemantics(); final data = tester.getSemantics(find.byType(ClideButton)).getSemanticsData(); expect(data.hasAction(SemanticsAction.tap), isTrue); diff --git a/test/a11y/semantic_coverage_test.dart b/test/a11y/semantic_coverage_test.dart index 56cab748..5c22df60 100644 --- a/test/a11y/semantic_coverage_test.dart +++ b/test/a11y/semantic_coverage_test.dart @@ -17,12 +17,7 @@ import 'package:flutter_test/flutter_test.dart'; /// catalog" gate. void main() { group('Tier-0 built-in extensions — contract-level coverage', () { - final extensions = [ - DefaultLayoutExtension(), - WelcomeExtension(), - IpcStatusExtension(), - ThemePickerExtension(), - ]; + final extensions = [DefaultLayoutExtension(), WelcomeExtension(), IpcStatusExtension(), ThemePickerExtension()]; for (final ext in extensions) { group(ext.id, () { diff --git a/test/app_collapse_toggle_test.dart b/test/app_collapse_toggle_test.dart index 159e5447..11025074 100644 --- a/test/app_collapse_toggle_test.dart +++ b/test/app_collapse_toggle_test.dart @@ -16,18 +16,21 @@ import 'helpers/kernel_fixture.dart'; Finder _icon(PhosphorIconPainter p) => find.byWidgetPredicate((w) => w is ClideIcon && w.painter == p); Widget _host(KernelFixture f, Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(size: Size(800, 200)), - child: Align(alignment: Alignment.topLeft, child: SizedBox(height: 26, child: child)), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(size: Size(800, 200)), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox(height: 26, child: child), ), ), - ); + ), + ), +); void main() { late KernelFixture f; @@ -54,13 +57,17 @@ void main() { testWidgets('the chevron flips live when the collapsed state changes', (tester) async { var collapsed = false; late StateSetter setOuter; - await tester.pumpWidget(_host( - f, - StatefulBuilder(builder: (ctx, setState) { - setOuter = setState; - return StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: collapsed, visible: true); - }), - )); + await tester.pumpWidget( + _host( + f, + StatefulBuilder( + builder: (ctx, setState) { + setOuter = setState; + return StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: collapsed, visible: true); + }, + ), + ), + ); await tester.pump(); expect(_icon(PhosphorIcons.byName('caret-line-left')), findsOneWidget); @@ -78,11 +85,13 @@ void main() { }); testWidgets('tapping fires the matching collapse command', (tester) async { - f.services.arrangement.applyPreset(const LayoutPresetContribution( - id: 'test', - displayName: 'test', - slots: [LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, visible: true)], - )); + f.services.arrangement.applyPreset( + const LayoutPresetContribution( + id: 'test', + displayName: 'test', + slots: [LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, visible: true)], + ), + ); f.services.extensions.register(DefaultLayoutExtension()); await f.services.extensions.activate('builtin.default-layout'); diff --git a/test/app_statusbar_test.dart b/test/app_statusbar_test.dart index 519bb76a..eaaaf782 100644 --- a/test/app_statusbar_test.dart +++ b/test/app_statusbar_test.dart @@ -16,21 +16,21 @@ import 'package:flutter_test/flutter_test.dart'; import 'helpers/kernel_fixture.dart'; Widget _bar(KernelFixture f, double width) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: MediaQueryData(size: Size(width, 200)), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox(width: width, height: 26, child: const StatusbarHost()), - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: MediaQueryData(size: Size(width, 200)), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox(width: width, height: 26, child: const StatusbarHost()), ), ), - ); + ), + ), +); void main() { late KernelFixture f; @@ -53,17 +53,8 @@ void main() { testWidgets('right group hugs the bar right edge at normal AND ultrawide widths (T-239)', (tester) async { // Mirrors the real surface: a left flex:1 item (like the Claude status // marquee, priority 50) + a right-group item (priority >= 100). - f.services.panels.contribute(StatusItemContribution( - id: 'test.left.flex', - priority: 50, - flex: 1, - build: (_) => const Text('LEFT', softWrap: false), - )); - f.services.panels.contribute(StatusItemContribution( - id: 'test.right', - priority: 110, - build: (_) => const Text('RIGHT', softWrap: false), - )); + f.services.panels.contribute(StatusItemContribution(id: 'test.left.flex', priority: 50, flex: 1, build: (_) => const Text('LEFT', softWrap: false))); + f.services.panels.contribute(StatusItemContribution(id: 'test.right', priority: 110, build: (_) => const Text('RIGHT', softWrap: false))); // 600 = normal, 3440 = ultrawide (where the float actually surfaced). for (final width in [600.0, 3440.0]) { @@ -77,11 +68,7 @@ void main() { }); testWidgets('right group hugs the edge with no left items (ultrawide)', (tester) async { - f.services.panels.contribute(StatusItemContribution( - id: 'test.right.only', - priority: 110, - build: (_) => const Text('R2', softWrap: false), - )); + f.services.panels.contribute(StatusItemContribution(id: 'test.right.only', priority: 110, build: (_) => const Text('R2', softWrap: false))); await pumpAt(tester, 3440.0); expect(tester.takeException(), isNull); expect(tester.getTopRight(find.text('R2')).dx, closeTo(3440 - 8, 1.0)); diff --git a/test/app_test.dart b/test/app_test.dart index 22d167f1..3805a422 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -283,13 +283,10 @@ void main() { }); testWidgets('switcher → Open Local Project falls back to the path dialog (no native picker)', (tester) async { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async { - if (call.method == 'pickDirectory') throw MissingPluginException(); - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async { + if (call.method == 'pickDirectory') throw MissingPluginException(); + return null; + }); addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), null)); final repo = Directory.current.path; diff --git a/test/builtin/claude/activity_cluster_test.dart b/test/builtin/claude/activity_cluster_test.dart index a47a04ee..c7d167f9 100644 --- a/test/builtin/claude/activity_cluster_test.dart +++ b/test/builtin/claude/activity_cluster_test.dart @@ -57,12 +57,7 @@ void main() { }); test('a failed result surfaces (sticky) and breaks the cluster', () { - final groups = groupConversation([ - _tool('1', 'Bash'), - _result('1', isError: true), - _tool('2', 'Bash'), - _result('2'), - ], FoldLevel.tools); + final groups = groupConversation([_tool('1', 'Bash'), _result('1', isError: true), _tool('2', 'Bash'), _result('2')], FoldLevel.tools); // [Folded([tool1]), Sticky(errorResult), Folded([tool2,result2])] expect(groups, hasLength(3)); expect((groups[0] as FoldedCluster).items, hasLength(1)); diff --git a/test/builtin/claude/agent_bootstrap_test.dart b/test/builtin/claude/agent_bootstrap_test.dart index 3f5216ad..337f35eb 100644 --- a/test/builtin/claude/agent_bootstrap_test.dart +++ b/test/builtin/claude/agent_bootstrap_test.dart @@ -37,43 +37,23 @@ void main() { group('agentEnvDelta (T-215)', () { test('always exports CLIDE_SOCK + CLIDE_WORKSPACE', () { - final d = agentEnvDelta( - workspaceRoot: '/repo', - socketPath: '/run/clide/abc.sock', - currentPath: '/usr/bin', - clideCliDir: null, - ); + final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/run/clide/abc.sock', currentPath: '/usr/bin', clideCliDir: null); expect(d['CLIDE_WORKSPACE'], '/repo'); expect(d['CLIDE_SOCK'], '/run/clide/abc.sock'); }); test('leaves PATH untouched when clide is already resolvable (clideCliDir null)', () { - final d = agentEnvDelta( - workspaceRoot: '/repo', - socketPath: '/s.sock', - currentPath: '/usr/bin', - clideCliDir: null, - ); + final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/s.sock', currentPath: '/usr/bin', clideCliDir: null); expect(d.containsKey('PATH'), isFalse); }); test('prepends the cli dir to PATH when given', () { - final d = agentEnvDelta( - workspaceRoot: '/repo', - socketPath: '/s.sock', - currentPath: '/usr/bin:/bin', - clideCliDir: '/home/dev/.local/bin', - ); + final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/s.sock', currentPath: '/usr/bin:/bin', clideCliDir: '/home/dev/.local/bin'); expect(d['PATH'], '/home/dev/.local/bin:/usr/bin:/bin'); }); test('sets PATH to just the cli dir when there is no current PATH', () { - final d = agentEnvDelta( - workspaceRoot: '/repo', - socketPath: '/s.sock', - currentPath: null, - clideCliDir: '/opt/clide/bin', - ); + final d = agentEnvDelta(workspaceRoot: '/repo', socketPath: '/s.sock', currentPath: null, clideCliDir: '/opt/clide/bin'); expect(d['PATH'], '/opt/clide/bin'); }); }); @@ -98,11 +78,7 @@ void main() { }); test('returns null when nothing holds clide', () { - final dir = resolveClideCliDir( - currentPath: '/usr/bin', - candidateDirs: const ['/a', '/b'], - isExecutableFile: (_) => false, - ); + final dir = resolveClideCliDir(currentPath: '/usr/bin', candidateDirs: const ['/a', '/b'], isExecutableFile: (_) => false); expect(dir, isNull); }); }); diff --git a/test/builtin/claude/claude_composer_test.dart b/test/builtin/claude/claude_composer_test.dart index 129281d7..91356898 100644 --- a/test/builtin/claude/claude_composer_test.dart +++ b/test/builtin/claude/claude_composer_test.dart @@ -21,10 +21,7 @@ void main() { }); test('multi-line input is wrapped in bracketed-paste markers', () { - expect( - encodeClaudeInput('line one\nline two'), - '\x1b[200~line one\nline two\x1b[201~\r', - ); + expect(encodeClaudeInput('line one\nline two'), '\x1b[200~line one\nline two\x1b[201~\r'); }); }); @@ -35,10 +32,7 @@ void main() { Future> pump(WidgetTester tester, {bool enabled = true}) async { final submitted = []; - await tester.pumpWidget(harness( - f, - ClaudeComposer(enabled: enabled, onSubmit: submitted.add), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(enabled: enabled, onSubmit: submitted.add))); return submitted; } @@ -96,15 +90,17 @@ void main() { // WidgetsApp provides DefaultTextEditingShortcuts in the real app; // the bare harness doesn't, so wrap explicitly to map Ctrl+V -> // PasteTextIntent, which the composer's Actions override intercepts. - await tester.pumpWidget(harness( - f, - DefaultTextEditingShortcuts( - child: ClaudeComposer( - onSubmit: (_) {}, - pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + await tester.pumpWidget( + harness( + f, + DefaultTextEditingShortcuts( + child: ClaudeComposer( + onSubmit: (_) {}, + pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + ), ), ), - )); + ); await pasteAttachment(tester); expect(find.text('notes.txt'), findsOneWidget); @@ -113,15 +109,17 @@ void main() { testWidgets('submit appends attachment @path tokens to the message', (tester) async { final submitted = []; - await tester.pumpWidget(harness( - f, - DefaultTextEditingShortcuts( - child: ClaudeComposer( - onSubmit: submitted.add, - pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + await tester.pumpWidget( + harness( + f, + DefaultTextEditingShortcuts( + child: ClaudeComposer( + onSubmit: submitted.add, + pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + ), ), ), - )); + ); await tester.enterText(find.byType(EditableText), 'look at this'); await pasteAttachment(tester); await tester.sendKeyEvent(LogicalKeyboardKey.enter); @@ -132,18 +130,9 @@ void main() { expect(find.text('notes.txt'), findsNothing); }); - Future> pumpWithCommands( - WidgetTester tester, - List commands, - ) async { + Future> pumpWithCommands(WidgetTester tester, List commands) async { final submitted = []; - await tester.pumpWidget(harness( - f, - ClaudeComposer( - onSubmit: submitted.add, - slashCommandsResolver: () => commands, - ), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: submitted.add, slashCommandsResolver: () => commands))); return submitted; } @@ -239,15 +228,17 @@ void main() { testWidgets('remove × cancels the attachment before send', (tester) async { final submitted = []; - await tester.pumpWidget(harness( - f, - DefaultTextEditingShortcuts( - child: ClaudeComposer( - onSubmit: submitted.add, - pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + await tester.pumpWidget( + harness( + f, + DefaultTextEditingShortcuts( + child: ClaudeComposer( + onSubmit: submitted.add, + pasteResolver: () async => const [ComposerAttachment(path: '/tmp/notes.txt', isImage: false)], + ), ), ), - )); + ); await pasteAttachment(tester); expect(find.text('notes.txt'), findsOneWidget); @@ -263,10 +254,7 @@ void main() { testWidgets('Escape interrupts when the typeahead is closed', (tester) async { var interrupts = 0; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onInterrupt: () => interrupts++), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onInterrupt: () => interrupts++))); tester.widget(find.byType(EditableText)).focusNode.requestFocus(); await tester.pump(); @@ -277,14 +265,7 @@ void main() { testWidgets('Escape closes the typeahead before it interrupts', (tester) async { var interrupts = 0; - await tester.pumpWidget(harness( - f, - ClaudeComposer( - onSubmit: (_) {}, - onInterrupt: () => interrupts++, - slashCommandsResolver: () => ['model'], - ), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onInterrupt: () => interrupts++, slashCommandsResolver: () => ['model']))); await tester.enterText(find.byType(EditableText), '/mo'); await tester.pump(); await tester.pump(); // ClideTypeahead inserts the popover post-frame @@ -305,10 +286,7 @@ void main() { testWidgets('the Stop button shows when busy and interrupts on tap', (tester) async { var interrupts = 0; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, busy: true, onInterrupt: () => interrupts++), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, busy: true, onInterrupt: () => interrupts++))); expect(find.text('Stop ⎋'), findsOneWidget); await tester.tap(find.text('Stop ⎋')); @@ -317,10 +295,7 @@ void main() { }); testWidgets('no Stop button when idle', (tester) async { - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onInterrupt: () {}), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onInterrupt: () {}))); expect(find.text('Stop ⎋'), findsNothing); }); @@ -353,10 +328,7 @@ void main() { testWidgets('clide-owned commands are reachable via the default resolver', (tester) async { // No slashCommandsResolver → default path; kClideOwnedCommands must be included. final submitted = []; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: submitted.add), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: submitted.add))); await tester.enterText(find.byType(EditableText), '/fo'); await tester.pump(); await tester.pump(); // ClideTypeahead inserts the popover post-frame @@ -375,10 +347,7 @@ void main() { testWidgets('Ctrl+M fires onCycleMode and is consumed', (tester) async { var cycles = 0; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => cycles++), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => cycles++))); await tester.tap(find.byType(EditableText)); await tester.pump(); @@ -394,10 +363,7 @@ void main() { testWidgets('plain m types normally (no modifier, no cycle)', (tester) async { var cycles = 0; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => cycles++), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => cycles++))); await tester.enterText(find.byType(EditableText), 'm'); await tester.pump(); expect(cycles, 0); @@ -425,10 +391,7 @@ void main() { final submitted = []; final node = FocusNode(); addTearDown(node.dispose); - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: submitted.add, focusNode: node), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: submitted.add, focusNode: node))); await tester.enterText(find.byType(EditableText), 'via external node'); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.enter); @@ -444,15 +407,8 @@ void main() { String text(WidgetTester tester) => tester.widget(find.byType(EditableText)).controller.text; - Future pumpWithHistory( - WidgetTester tester, { - required List history, - ValueChanged? onDraftChanged, - }) async { - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, history: history, onDraftChanged: onDraftChanged), - )); + Future pumpWithHistory(WidgetTester tester, {required List history, ValueChanged? onDraftChanged}) async { + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, history: history, onDraftChanged: onDraftChanged))); await tester.tap(find.byType(EditableText)); await tester.pump(); } @@ -528,16 +484,15 @@ void main() { tearDown(() => f.dispose()); testWidgets('seeds the field from initialValue on mount', (tester) async { - await tester.pumpWidget(harness( - f, - ClaudeComposer( - onSubmit: (_) {}, - initialValue: const TextEditingValue( - text: 'half-typed', - selection: TextSelection.collapsed(offset: 4), + await tester.pumpWidget( + harness( + f, + ClaudeComposer( + onSubmit: (_) {}, + initialValue: const TextEditingValue(text: 'half-typed', selection: TextSelection.collapsed(offset: 4)), ), ), - )); + ); final controller = tester.widget(find.byType(EditableText)).controller; expect(controller.text, 'half-typed'); expect(controller.selection.baseOffset, 4); // caret restored too @@ -545,10 +500,7 @@ void main() { testWidgets('reports draft changes (text + caret) via onDraftChanged', (tester) async { final drafts = []; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onDraftChanged: drafts.add), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onDraftChanged: drafts.add))); await tester.enterText(find.byType(EditableText), 'draft text'); await tester.pump(); @@ -557,10 +509,7 @@ void main() { testWidgets('reports an empty draft when submitting clears the field', (tester) async { final drafts = []; - await tester.pumpWidget(harness( - f, - ClaudeComposer(onSubmit: (_) {}, onDraftChanged: drafts.add), - )); + await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onDraftChanged: drafts.add))); await tester.enterText(find.byType(EditableText), 'send me'); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.enter); @@ -589,10 +538,7 @@ void main() { // Prompt resolved — composer remounts and restores the draft. showPrompt.value = false; await tester.pump(); - expect( - tester.widget(find.byType(EditableText)).controller.text, - 'survived the prompt', - ); + expect(tester.widget(find.byType(EditableText)).controller.text, 'survived the prompt'); }); }); } @@ -614,13 +560,8 @@ class _DraftSwapHostState extends State<_DraftSwapHost> { Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: widget.showPrompt, - builder: (context, prompt, _) => prompt - ? const SizedBox.shrink() - : ClaudeComposer( - onSubmit: (_) {}, - initialValue: _draft, - onDraftChanged: (v) => _draft = v, - ), + builder: (context, prompt, _) => + prompt ? const SizedBox.shrink() : ClaudeComposer(onSubmit: (_) {}, initialValue: _draft, onDraftChanged: (v) => _draft = v), ); } } diff --git a/test/builtin/claude/claude_config_test.dart b/test/builtin/claude/claude_config_test.dart index 5a07c45d..b95aecfd 100644 --- a/test/builtin/claude/claude_config_test.dart +++ b/test/builtin/claude/claude_config_test.dart @@ -49,27 +49,10 @@ void main() { await File('${scope.path}/settings.json').writeAsString(jsonEncode(json)); } - String initLine({ - String version = '2.1.150', - List slash = const ['clear', 'pql'], - List skills = const ['pql'], - }) => - '${jsonEncode({ - 'type': 'system', - 'subtype': 'init', - 'claude_code_version': version, - 'slash_commands': slash, - 'skills': skills, - 'model': 'claude-opus-4-7', - 'permissionMode': 'default', - })}\n'; + String initLine({String version = '2.1.150', List slash = const ['clear', 'pql'], List skills = const ['pql']}) => + '${jsonEncode({'type': 'system', 'subtype': 'init', 'claude_code_version': version, 'slash_commands': slash, 'skills': skills, 'model': 'claude-opus-4-7', 'permissionMode': 'default'})}\n'; - ClaudeConfig build({ - ClaudeVersionRunner? versionRunner, - ClaudeInitProbe? initProbe, - ClaudeConfigWatch? watch, - Duration debounce = Duration.zero, - }) => + ClaudeConfig build({ClaudeVersionRunner? versionRunner, ClaudeInitProbe? initProbe, ClaudeConfigWatch? watch, Duration debounce = Duration.zero}) => ClaudeConfig( globalDir: globalDir, cacheDir: cacheDir, @@ -111,14 +94,14 @@ void main() { 'keep': 1, 'permissions': { 'allow': ['Bash'], - 'deny': ['Write'] + 'deny': ['Write'], }, }); await writeSettings(localDir, { 'model': 'sonnet', 'permissions': { 'allow': ['Edit'], - 'ask': ['Read'] + 'ask': ['Read'], }, }); @@ -152,10 +135,12 @@ void main() { test('load stays on the fallback until ensureProbe runs (no eager turn)', () async { var probeCalls = 0; - final c = build(initProbe: () async { - probeCalls++; - return initLine(slash: ['clear', 'pql', 'whats-next']); - }); + final c = build( + initProbe: () async { + probeCalls++; + return initLine(slash: ['clear', 'pql', 'whats-next']); + }, + ); await c.load(); expect(probeCalls, 0, reason: 'load must never pay for a model turn'); expect(c.slashCommands, kFallbackSlashCommands); @@ -192,10 +177,12 @@ void main() { test('a different claude version misses the cache and re-probes', () async { var probeCalls = 0; - final c1 = build(initProbe: () async { - probeCalls++; - return initLine(); - }); + final c1 = build( + initProbe: () async { + probeCalls++; + return initLine(); + }, + ); await c1.load(); await c1.ensureProbe(); expect(probeCalls, 1); @@ -281,10 +268,12 @@ void main() { test('explicit refresh re-reads disk without re-resolving the version', () async { var versionCalls = 0; - final c = build(versionRunner: () async { - versionCalls++; - return '2.1.150 (Claude Code)\n'; - }); + final c = build( + versionRunner: () async { + versionCalls++; + return '2.1.150 (Claude Code)\n'; + }, + ); await c.load(); expect(versionCalls, 1); diff --git a/test/builtin/claude/claude_meta_sidebar_test.dart b/test/builtin/claude/claude_meta_sidebar_test.dart index e792a5c8..98415ac0 100644 --- a/test/builtin/claude/claude_meta_sidebar_test.dart +++ b/test/builtin/claude/claude_meta_sidebar_test.dart @@ -64,14 +64,7 @@ void main() { ClaudeConfig? config, SidebarTab initialTab = SidebarTab.activity, ClaudeSessionOrchestrator? orchestrator, - }) => - ClaudeMetaSidebar( - statsLoader: () async => stats, - pollInterval: Duration.zero, - config: config, - orchestrator: orchestrator, - initialTab: initialTab, - ); + }) => ClaudeMetaSidebar(statsLoader: () async => stats, pollInterval: Duration.zero, config: config, orchestrator: orchestrator, initialTab: initialTab); testWidgets('Activity tab shows today + lifetime stats on the table', (tester) async { await tester.pumpWidget(harness(f, sidebar(stats: stats))); @@ -145,15 +138,9 @@ void main() { await tester.pumpAndSettle(); expect(find.text('TODAY'), findsOneWidget); // starts on Activity - f.services.events.emit(const TeamMemberJoined( - team: 't', - agentId: 'a1', - name: 'Scout', - agentType: 'explorer', - paneId: '%1', - model: 'claude-opus-4-7', - color: 'blue', - )); + f.services.events.emit( + const TeamMemberJoined(team: 't', agentId: 'a1', name: 'Scout', agentType: 'explorer', paneId: '%1', model: 'claude-opus-4-7', color: 'blue'), + ); await tester.pump(); await tester.pump(); @@ -167,14 +154,7 @@ void main() { await tester.pumpWidget(harness(f, sidebar())); await tester.pumpAndSettle(); - f.services.events.emit(const TeamMemberJoined( - team: 't', - agentId: 'a1', - name: 'Scout', - agentType: 'explorer', - paneId: '%1', - color: 'blue', - )); + f.services.events.emit(const TeamMemberJoined(team: 't', agentId: 'a1', name: 'Scout', agentType: 'explorer', paneId: '%1', color: 'blue')); await tester.pump(); await tester.pump(); expect(find.text('Scout'), findsOneWidget); @@ -190,24 +170,14 @@ void main() { await tester.pumpWidget(harness(f, sidebar())); await tester.pumpAndSettle(); - f.services.events.emit(const TeamMemberJoined( - team: 't', - agentId: 'a1', - name: 'Scout', - agentType: 'explorer', - paneId: '%1', - color: 'blue', - )); + f.services.events.emit(const TeamMemberJoined(team: 't', agentId: 'a1', name: 'Scout', agentType: 'explorer', paneId: '%1', color: 'blue')); await tester.pump(); await tester.pump(); f.services.messages.publish( ClaudeConversation.publisher, ClaudeConversation.memberStatusChannel, - ClaudeConversation.memberStatusData( - 'a1', - const SessionStatus(model: 'claude-opus-4-7', permissionMode: 'acceptEdits', contextTokens: 21000), - ), + ClaudeConversation.memberStatusData('a1', const SessionStatus(model: 'claude-opus-4-7', permissionMode: 'acceptEdits', contextTokens: 21000)), ); await tester.pump(); await tester.pump(); @@ -226,25 +196,11 @@ void main() { group('T-171 roster controls', () { Future orchWithMember(WidgetTester tester, {String name = 'Scout', String agentId = 'a1'}) async { final orch = _fakeOrchestrator(); - await orch.spawn(SpawnSpec( - id: 'teammate:$name', - role: 'teammate', - sessionId: '$name-uuid', - cwd: '/repo', - team: true, - memberName: name, - )); + await orch.spawn(SpawnSpec(id: 'teammate:$name', role: 'teammate', sessionId: '$name-uuid', cwd: '/repo', team: true, memberName: name)); await tester.pumpWidget(harness(f, sidebar(orchestrator: orch, initialTab: SidebarTab.team))); - f.services.events.emit(TeamMemberJoined( - team: 't', - agentId: agentId, - name: name, - agentType: 'coder', - paneId: '%1', - color: 'blue', - )); + f.services.events.emit(TeamMemberJoined(team: 't', agentId: agentId, name: name, agentType: 'coder', paneId: '%1', color: 'blue')); await tester.pump(); await tester.pump(); return orch; @@ -345,24 +301,10 @@ void main() { final orch = _fakeOrchestrator(); // We cannot intercept the proc easily through the public API — verify // injectMessage wired up by checking the managed session is the right one. - await orch.spawn(SpawnSpec( - id: 'teammate:Alpha', - role: 'teammate', - sessionId: 'alpha-uuid', - cwd: '/repo', - team: true, - memberName: 'Alpha', - )); + await orch.spawn(SpawnSpec(id: 'teammate:Alpha', role: 'teammate', sessionId: 'alpha-uuid', cwd: '/repo', team: true, memberName: 'Alpha')); await tester.pumpWidget(harness(f, sidebar(orchestrator: orch, initialTab: SidebarTab.team))); - f.services.events.emit(const TeamMemberJoined( - team: 't', - agentId: 'a2', - name: 'Alpha', - agentType: 'coder', - paneId: '%2', - color: 'green', - )); + f.services.events.emit(const TeamMemberJoined(team: 't', agentId: 'a2', name: 'Alpha', agentType: 'coder', paneId: '%2', color: 'green')); await tester.pump(); await tester.pump(); @@ -391,32 +333,11 @@ void main() { group('T-171 task list', () { testWidgets('task list renders live from broker on changes stream', (tester) async { final orch = _fakeOrchestrator(); - await orch.spawn(SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'primary-uuid', - cwd: '/repo', - team: true, - memberName: 'lead', - )); - await orch.spawn(SpawnSpec( - id: 'teammate:tyre', - role: 'teammate', - sessionId: 'tyre-uuid', - cwd: '/repo', - team: true, - memberName: 'tyre', - )); + await orch.spawn(SpawnSpec(id: 'primary', role: 'primary', sessionId: 'primary-uuid', cwd: '/repo', team: true, memberName: 'lead')); + await orch.spawn(SpawnSpec(id: 'teammate:tyre', role: 'teammate', sessionId: 'tyre-uuid', cwd: '/repo', team: true, memberName: 'tyre')); await tester.pumpWidget(harness(f, sidebar(orchestrator: orch, initialTab: SidebarTab.team))); - f.services.events.emit(const TeamMemberJoined( - team: 't', - agentId: 'a1', - name: 'lead', - agentType: 'lead', - paneId: '%1', - color: 'blue', - )); + f.services.events.emit(const TeamMemberJoined(team: 't', agentId: 'a1', name: 'lead', agentType: 'lead', paneId: '%1', color: 'blue')); await tester.pump(); await tester.pump(); @@ -443,10 +364,16 @@ void main() { // shared canSizeOverlay harness the sidebar's scrollable otherwise gets a // degenerate viewport and clips the task section out of the semantics // tree, so the reassign button below the roster isn't findable by label. - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(orchestrator: orch, initialTab: SidebarTab.team)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(orchestrator: orch, initialTab: SidebarTab.team), + ), + ), + ); f.services.events.emit(const TeamMemberJoined(team: 't', agentId: 'a1', name: 'lead', agentType: 'lead', paneId: '%1', color: 'cyan')); await tester.pump(); await tester.pump(); @@ -462,9 +389,7 @@ void main() { // canSizeOverlay test harness gives the sidebar's ListView a degenerate // viewport that clips the lower task section out of the semantics *tree* // (the widget is built and tappable; only the semantics node is dropped). - final reassign = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Reassign task', - ); + final reassign = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Reassign task'); await tester.tap(reassign.first); await tester.pump(); await tester.pump(); @@ -493,29 +418,11 @@ void main() { return (orch, writes); } - Future<(ClaudeSessionOrchestrator, List)> spawnAndShow( - WidgetTester tester, { - String name = 'Scout', - String agentId = 'b1', - }) async { + Future<(ClaudeSessionOrchestrator, List)> spawnAndShow(WidgetTester tester, {String name = 'Scout', String agentId = 'b1'}) async { final (orch, writes) = orchCapturing(); - await orch.spawn(SpawnSpec( - id: 'teammate:$name', - role: 'teammate', - sessionId: '$name-uuid', - cwd: '/repo', - team: true, - memberName: name, - )); + await orch.spawn(SpawnSpec(id: 'teammate:$name', role: 'teammate', sessionId: '$name-uuid', cwd: '/repo', team: true, memberName: name)); await tester.pumpWidget(harness(f, sidebar(orchestrator: orch, initialTab: SidebarTab.team))); - f.services.events.emit(TeamMemberJoined( - team: 't', - agentId: agentId, - name: name, - agentType: 'coder', - paneId: '%1', - color: 'blue', - )); + f.services.events.emit(TeamMemberJoined(team: 't', agentId: agentId, name: name, agentType: 'coder', paneId: '%1', color: 'blue')); await tester.pump(); await tester.pump(); return (orch, writes); @@ -540,10 +447,7 @@ void main() { f.services.messages.publish( ClaudeConversation.publisher, ClaudeConversation.memberStatusChannel, - ClaudeConversation.memberStatusData( - 'b1', - const SessionStatus(permissionMode: 'acceptEdits'), - ), + ClaudeConversation.memberStatusData('b1', const SessionStatus(permissionMode: 'acceptEdits')), ); await tester.pump(); await tester.pump(); @@ -647,25 +551,11 @@ void main() { group('T-172 fork session button', () { Future orchWithMember(WidgetTester tester, {String name = 'Forker', String agentId = 'f1'}) async { final orch = _fakeOrchestrator(); - await orch.spawn(SpawnSpec( - id: 'teammate:$name', - role: 'teammate', - sessionId: '$name-uuid', - cwd: '/repo', - team: true, - memberName: name, - )); + await orch.spawn(SpawnSpec(id: 'teammate:$name', role: 'teammate', sessionId: '$name-uuid', cwd: '/repo', team: true, memberName: name)); await tester.pumpWidget(harness(f, sidebar(orchestrator: orch, initialTab: SidebarTab.team))); - f.services.events.emit(TeamMemberJoined( - team: 't', - agentId: agentId, - name: name, - agentType: 'coder', - paneId: '%1', - color: 'teal', - )); + f.services.events.emit(TeamMemberJoined(team: 't', agentId: agentId, name: name, agentType: 'coder', paneId: '%1', color: 'teal')); await tester.pump(); await tester.pump(); return orch; @@ -794,10 +684,7 @@ void main() { final config = await loadedConfig( tester, dir, - skills: [ - (name: 'git-commit', dir: 'git-commit'), - (name: 'pql', dir: 'pql'), - ], + skills: [(name: 'git-commit', dir: 'git-commit'), (name: 'pql', dir: 'pql')], commands: ['deploy', 'test'], agents: ['planner'], settings: { @@ -810,10 +697,16 @@ void main() { ); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -831,18 +724,20 @@ void main() { final config = await loadedConfig( tester, dir, - skills: [ - (name: 'git-commit', dir: 'git-commit'), - (name: 'pql', dir: 'pql'), - (name: 'deep-research', dir: 'deep-research'), - ], + skills: [(name: 'git-commit', dir: 'git-commit'), (name: 'pql', dir: 'pql'), (name: 'deep-research', dir: 'deep-research')], ); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -865,10 +760,16 @@ void main() { final config = await loadedConfig(tester, dir, agents: ['planner', 'coder']); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -885,10 +786,16 @@ void main() { final config = await loadedConfig(tester, dir, commands: ['deploy', 'test', 'lint']); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -916,10 +823,16 @@ void main() { ); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -947,10 +860,16 @@ void main() { ); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -973,10 +892,16 @@ void main() { final sub = f.services.messages.subscribe(publisher: 'builtin.markdown', channel: 'selection').listen(published.add); addTearDown(sub.cancel); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -1003,10 +928,16 @@ void main() { final sub = f.services.messages.subscribe(publisher: 'builtin.markdown', channel: 'selection').listen(published.add); addTearDown(sub.cancel); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -1031,10 +962,16 @@ void main() { final sub = f.services.messages.subscribe(publisher: 'builtin.markdown', channel: 'selection').listen(published.add); addTearDown(sub.cancel); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -1055,10 +992,16 @@ void main() { final config = await loadedConfig(tester, dir, skills: [(name: 'git-commit', dir: 'git-commit')]); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); @@ -1079,10 +1022,16 @@ void main() { final config = await loadedConfig(tester, dir); addTearDown(config.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 320, height: 700, child: sidebar(config: config, initialTab: SidebarTab.config)), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 320, + height: 700, + child: sidebar(config: config, initialTab: SidebarTab.config), + ), + ), + ); await tester.pump(); await tester.pump(); diff --git a/test/builtin/claude/claude_pane_test.dart b/test/builtin/claude/claude_pane_test.dart index 5784ab56..7a6aede0 100644 --- a/test/builtin/claude/claude_pane_test.dart +++ b/test/builtin/claude/claude_pane_test.dart @@ -76,28 +76,28 @@ void main() { }); Widget tree(ClaudePane pane) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 900, - height: 700, - child: DialogHost( - router: f.services.dialog, - child: Overlay(initialEntries: [OverlayEntry(builder: (_) => pane)]), - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 900, + height: 700, + child: DialogHost( + router: f.services.dialog, + child: Overlay(initialEntries: [OverlayEntry(builder: (_) => pane)]), ), ), ), ), - ); + ), + ), + ); // Pump the pane and release its project-wait gate so _spawn runs. The whole // chain (incl. the real transcript-probe I/O) runs in the real zone. @@ -205,14 +205,9 @@ void main() { await mount(tester, const ClaudePane(showChrome: false)); final proc = created.single; await act( - tester, - () => proc.feed({ - 'type': 'system', - 'subtype': 'init', - 'model': 'claude-opus-4-8', - 'permissionMode': 'plan', - 'session_id': primarySessionId('/repo-a'), - })); + tester, + () => proc.feed({'type': 'system', 'subtype': 'init', 'model': 'claude-opus-4-8', 'permissionMode': 'plan', 'session_id': primarySessionId('/repo-a')}), + ); // The init event flows through the session into the pane's status path // (the rendered slot lives in the status bar, absent from this harness). expect(orch.byId('primary')!.session.status.permissionMode, 'plan'); @@ -223,17 +218,18 @@ void main() { await mount(tester, const ClaudePane(showChrome: false)); final proc = created.single; await act( - tester, - () => proc.feed({ - 'type': 'control_request', - 'request_id': 'req-1', - 'request': { - 'subtype': 'can_use_tool', - 'tool_name': 'Bash', - 'tool_use_id': 'tu-1', - 'input': {'command': 'ls'}, - }, - })); + tester, + () => proc.feed({ + 'type': 'control_request', + 'request_id': 'req-1', + 'request': { + 'subtype': 'can_use_tool', + 'tool_name': 'Bash', + 'tool_use_id': 'tu-1', + 'input': {'command': 'ls'}, + }, + }), + ); expect(find.byType(ClaudeComposer), findsNothing, reason: 'prompt takes the composer slot (D-78)'); }); diff --git a/test/builtin/claude/claude_session_host_test.dart b/test/builtin/claude/claude_session_host_test.dart index dd9c4971..6e398e5f 100644 --- a/test/builtin/claude/claude_session_host_test.dart +++ b/test/builtin/claude/claude_session_host_test.dart @@ -15,29 +15,29 @@ import 'package:flutter_test/flutter_test.dart'; import '../../helpers/kernel_fixture.dart'; Widget _host(KernelFixture f, Key key) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - // Bounded size so the reorderable tab strip's Draggable has a - // real width and the Overlay below isn't asked to self-size. - width: 800, - height: 600, - child: Overlay( - initialEntries: [OverlayEntry(builder: (_) => ClaudeSessionHost(key: key))], - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + // Bounded size so the reorderable tab strip's Draggable has a + // real width and the Overlay below isn't asked to self-size. + width: 800, + height: 600, + child: Overlay( + initialEntries: [OverlayEntry(builder: (_) => ClaudeSessionHost(key: key))], ), ), ), ), - ); + ), + ), +); void main() { late KernelFixture f; diff --git a/test/builtin/claude/claude_status_test.dart b/test/builtin/claude/claude_status_test.dart index defa9aac..04526168 100644 --- a/test/builtin/claude/claude_status_test.dart +++ b/test/builtin/claude/claude_status_test.dart @@ -67,13 +67,7 @@ void main() { }); test('full status line with all fields (T-168)', () { - const s = SessionStatus( - model: 'claude-opus-4-7', - permissionMode: 'default', - contextTokens: 21000, - contextWindow: 1000000, - cost: 0.05, - ); + const s = SessionStatus(model: 'claude-opus-4-7', permissionMode: 'default', contextTokens: 21000, contextWindow: 1000000, cost: 0.05); final line = formatStatusLine(s); expect(line, contains('opus 4.7')); expect(line, contains('default')); @@ -98,12 +92,7 @@ void main() { group('statusSegmentsAroundMode (T-226)', () { test('splits model (leading) from ctx/cost/rate (trailing), mode excluded', () { - const s = SessionStatus( - model: 'claude-opus-4-7', - permissionMode: 'plan', - contextTokens: 21000, - cost: 0.05, - ); + const s = SessionStatus(model: 'claude-opus-4-7', permissionMode: 'plan', contextTokens: 21000, cost: 0.05); final seg = statusSegmentsAroundMode(s); expect(seg.leading, 'opus 4.7'); expect(seg.trailing, contains('21k ctx')); diff --git a/test/builtin/claude/claude_task_dock_test.dart b/test/builtin/claude/claude_task_dock_test.dart index 833d7560..fc390ec2 100644 --- a/test/builtin/claude/claude_task_dock_test.dart +++ b/test/builtin/claude/claude_task_dock_test.dart @@ -24,10 +24,15 @@ void main() { ]; Future pump(WidgetTester tester, List items) async { - await tester.pumpWidget(harness( - f, - Align(alignment: Alignment.topLeft, child: SizedBox(width: 400, child: ClaudeTaskDock(tasks: items))), - )); + await tester.pumpWidget( + harness( + f, + Align( + alignment: Alignment.topLeft, + child: SizedBox(width: 400, child: ClaudeTaskDock(tasks: items)), + ), + ), + ); await tester.pump(); } diff --git a/test/builtin/claude/clipboard_paste_test.dart b/test/builtin/claude/clipboard_paste_test.dart index baaf1a42..ef2b381d 100644 --- a/test/builtin/claude/clipboard_paste_test.dart +++ b/test/builtin/claude/clipboard_paste_test.dart @@ -53,9 +53,7 @@ void main() { }); test('files take precedence over an image', () async { - final result = await resolveClipboardAttachment( - _FakeSource(files: ['/x/y'], image: Uint8List.fromList([1, 2])), - ); + final result = await resolveClipboardAttachment(_FakeSource(files: ['/x/y'], image: Uint8List.fromList([1, 2]))); expect(result.map((a) => a.path), ['/x/y']); }); diff --git a/test/builtin/claude/conversation_card_test.dart b/test/builtin/claude/conversation_card_test.dart index 31e4a9e4..8ea70199 100644 --- a/test/builtin/claude/conversation_card_test.dart +++ b/test/builtin/claude/conversation_card_test.dart @@ -39,15 +39,17 @@ void main() { } testWidgets('copy button is always in the tree (Opacity 0 before hover)', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - accent: Color(0xFFFFFFFF), - label: 'you', - copyText: 'the raw message', - body: Text('the raw message', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + accent: Color(0xFFFFFFFF), + label: 'you', + copyText: 'the raw message', + body: Text('the raw message', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); // The text widget is in the tree (always), but the Opacity hides it. @@ -56,15 +58,17 @@ void main() { }); testWidgets('copy button (on hover) writes the copyText to the clipboard', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - accent: Color(0xFFFFFFFF), - label: 'you', - copyText: 'the raw message', - body: Text('the raw message', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + accent: Color(0xFFFFFFFF), + label: 'you', + copyText: 'the raw message', + body: Text('the raw message', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); await hoverCard(tester); @@ -78,15 +82,17 @@ void main() { testWidgets('copy button: keyboard focus + ActivateIntent writes to clipboard without hovering', (tester) async { // T-174: actions must be keyboard-reachable even when the card is not hovered. - await tester.pumpWidget(harness( - f, - const ConversationCard( - accent: Color(0xFFFFFFFF), - label: 'you', - copyText: 'keyboard written', - body: Text('keyboard written', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + accent: Color(0xFFFFFFFF), + label: 'you', + copyText: 'keyboard written', + body: Text('keyboard written', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); // Locate the ClideTappable whose FocusNode we want: the one wrapping the @@ -96,9 +102,7 @@ void main() { expect(copyTextFinder, findsOneWidget); // Walk up to find the Focus that ClideTappable installed, then request focus. - final focusWidget = tester.widget( - find.ancestor(of: copyTextFinder, matching: find.byType(Focus)).first, - ); + final focusWidget = tester.widget(find.ancestor(of: copyTextFinder, matching: find.byType(Focus)).first); focusWidget.focusNode!.requestFocus(); await tester.pump(); // focus resolves; the focus listener calls setState await tester.pump(); // the scheduled rebuild paints the lifted opacity @@ -117,15 +121,17 @@ void main() { testWidgets('copy button carries a Semantics button label', (tester) async { final handle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - const ConversationCard( - accent: Color(0xFFFFFFFF), - label: 'you', - copyText: 'msg', - body: Text('msg', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + accent: Color(0xFFFFFFFF), + label: 'you', + copyText: 'msg', + body: Text('msg', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); // Semantics label 'copy' must be present even before hover, so AT can // discover the button without the user mousing over first. @@ -134,17 +140,19 @@ void main() { }); testWidgets('collapsible card hides its body until expanded', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFFFFFF), - label: 'result', - collapsible: true, - collapsedByDefault: true, - body: Text('tool output here', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFFFFFF), + label: 'result', + collapsible: true, + collapsedByDefault: true, + body: Text('tool output here', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.text('tool output here'), findsNothing); // collapsed @@ -155,14 +163,16 @@ void main() { }); testWidgets('a non-collapsible card always shows its body and no caret', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - accent: Color(0xFFFFFFFF), - label: 'claude', - body: Text('always visible', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + accent: Color(0xFFFFFFFF), + label: 'claude', + body: Text('always visible', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.text('always visible'), findsOneWidget); expect(find.bySemanticsLabel('Expand'), findsNothing); @@ -170,15 +180,17 @@ void main() { }); testWidgets('custom actions are always in the tree (Opacity 0 before hover)', (tester) async { - await tester.pumpWidget(harness( - f, - ConversationCard( - accent: const Color(0xFFFFFFFF), - label: 'claude', - body: const Text('body', textDirection: TextDirection.ltr), - actions: [MessageAction(label: 'fork', onInvoke: () {})], + await tester.pumpWidget( + harness( + f, + ConversationCard( + accent: const Color(0xFFFFFFFF), + label: 'claude', + body: const Text('body', textDirection: TextDirection.ltr), + actions: [MessageAction(label: 'fork', onInvoke: () {})], + ), ), - )); + ); await tester.pump(); // 'fork' is in the tree (always), but the Opacity hides it. @@ -188,15 +200,17 @@ void main() { testWidgets('custom actions appear on hover and invoke', (tester) async { var forked = false; - await tester.pumpWidget(harness( - f, - ConversationCard( - accent: const Color(0xFFFFFFFF), - label: 'claude', - body: const Text('body', textDirection: TextDirection.ltr), - actions: [MessageAction(label: 'fork', onInvoke: () => forked = true)], + await tester.pumpWidget( + harness( + f, + ConversationCard( + accent: const Color(0xFFFFFFFF), + label: 'claude', + body: const Text('body', textDirection: TextDirection.ltr), + actions: [MessageAction(label: 'fork', onInvoke: () => forked = true)], + ), ), - )); + ); await tester.pump(); await hoverCard(tester); @@ -212,19 +226,21 @@ void main() { final showExtra = ValueNotifier(true); addTearDown(showExtra.dispose); - await tester.pumpWidget(harness( - f, - ValueListenableBuilder( - valueListenable: showExtra, - builder: (_, show, __) => ConversationCard( - accent: const Color(0xFFFFFFFF), - label: 'claude', - copyText: show ? 'text' : null, - body: const Text('body', textDirection: TextDirection.ltr), - actions: show ? [MessageAction(label: 'fork', onInvoke: () {})] : [], + await tester.pumpWidget( + harness( + f, + ValueListenableBuilder( + valueListenable: showExtra, + builder: (_, show, _) => ConversationCard( + accent: const Color(0xFFFFFFFF), + label: 'claude', + copyText: show ? 'text' : null, + body: const Text('body', textDirection: TextDirection.ltr), + actions: show ? [MessageAction(label: 'fork', onInvoke: () {})] : [], + ), ), ), - )); + ); await tester.pump(); // Initially: copy + fork. expect(find.text('copy'), findsOneWidget); @@ -243,17 +259,19 @@ void main() { testWidgets('collapsible card: Semantics.onTap on caret also toggles collapse', (tester) async { // The caret Semantics node has its own onTap (for accessibility-tree callers). // Invoke it via the Semantics.onTap callback directly. - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFFFFFF), - label: 'result', - collapsible: true, - collapsedByDefault: true, - body: Text('tool output here', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFFFFFF), + label: 'result', + collapsible: true, + collapsedByDefault: true, + body: Text('tool output here', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.text('tool output here'), findsNothing); // collapsed @@ -269,15 +287,17 @@ void main() { }); testWidgets('bordered variant renders with a border container', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFF0000), - label: 'result', - body: Text('bordered body', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFF0000), + label: 'result', + body: Text('bordered body', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.text('bordered body'), findsOneWidget); expect(find.text('result'), findsOneWidget); @@ -285,16 +305,18 @@ void main() { testWidgets('status: success renders a "succeeded" semantics mark in the header (T-262)', (tester) async { final handle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFFFFFF), - label: 'Bash', - status: ConversationCardStatus.success, - body: Text('body', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFFFFFF), + label: 'Bash', + status: ConversationCardStatus.success, + body: Text('body', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.bySemanticsLabel('succeeded'), findsOneWidget); expect(find.bySemanticsLabel('failed'), findsNothing); @@ -303,16 +325,18 @@ void main() { testWidgets('status: error renders a "failed" semantics mark; none renders nothing (T-262)', (tester) async { final handle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFFFFFF), - label: 'Bash', - status: ConversationCardStatus.error, - body: Text('body', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFFFFFF), + label: 'Bash', + status: ConversationCardStatus.error, + body: Text('body', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.bySemanticsLabel('failed'), findsOneWidget); expect(find.bySemanticsLabel('succeeded'), findsNothing); @@ -320,18 +344,25 @@ void main() { }); testWidgets('extraSegments render below the body with their sub-label when expanded (T-262)', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFFFFFFFF), - label: 'Read', - collapsible: true, - collapsedByDefault: true, - body: Text('the call', textDirection: TextDirection.ltr), - extraSegments: [CardSegment(label: 'result', child: Text('the output', textDirection: TextDirection.ltr))], + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bordered, + accent: Color(0xFFFFFFFF), + label: 'Read', + collapsible: true, + collapsedByDefault: true, + body: Text('the call', textDirection: TextDirection.ltr), + extraSegments: [ + CardSegment( + label: 'result', + child: Text('the output', textDirection: TextDirection.ltr), + ), + ], + ), ), - )); + ); await tester.pump(); // Collapsed: neither the body nor the segment shows. expect(find.text('the call'), findsNothing); @@ -346,15 +377,17 @@ void main() { }); testWidgets('bare variant renders content without frame decoration', (tester) async { - await tester.pumpWidget(harness( - f, - const ConversationCard( - variant: ConversationCardVariant.bare, - accent: Color(0xFF00FF00), - label: 'bare', - body: Text('bare body', textDirection: TextDirection.ltr), + await tester.pumpWidget( + harness( + f, + const ConversationCard( + variant: ConversationCardVariant.bare, + accent: Color(0xFF00FF00), + label: 'bare', + body: Text('bare body', textDirection: TextDirection.ltr), + ), ), - )); + ); await tester.pump(); expect(find.text('bare body'), findsOneWidget); }); diff --git a/test/builtin/claude/conversation_scroll_test.dart b/test/builtin/claude/conversation_scroll_test.dart index bcdd33fb..5e817789 100644 --- a/test/builtin/claude/conversation_scroll_test.dart +++ b/test/builtin/claude/conversation_scroll_test.dart @@ -34,24 +34,26 @@ void main() { final stream = StreamController.broadcast(); final c = ConversationController(stream: stream.stream); addTearDown(c.dispose); - await tester.pumpWidget(harness( - f, - SizedBox( - width: 600, - height: 600, - child: Column( - children: [ - Expanded(child: ConversationView(controller: c)), - // Stand-in for the interaction zone (composer / prompt) whose height - // changes; ValueListenableBuilder rebuilds just the box. - ValueListenableBuilder( - valueListenable: bottomH, - builder: (_, h, __) => SizedBox(height: h, width: 600), - ), - ], + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 600, + height: 600, + child: Column( + children: [ + Expanded(child: ConversationView(controller: c)), + // Stand-in for the interaction zone (composer / prompt) whose height + // changes; ValueListenableBuilder rebuilds just the box. + ValueListenableBuilder( + valueListenable: bottomH, + builder: (_, h, _) => SizedBox(height: h, width: 600), + ), + ], + ), ), ), - )); + ); for (var i = 0; i < 40; i++) { stream.add(_asst('conversation line number $i', i)); } diff --git a/test/builtin/claude/conversation_view_test.dart b/test/builtin/claude/conversation_view_test.dart index 92782877..0d942cde 100644 --- a/test/builtin/claude/conversation_view_test.dart +++ b/test/builtin/claude/conversation_view_test.dart @@ -148,11 +148,14 @@ void main() { setUp(() async => f = await KernelFixture.create()); tearDown(() => f.dispose()); - Future pumpWith(WidgetTester tester, List items, - {Set hiddenToolUseIds = const {}, - Map toolUseOutcomes = const {}, - Set quietErrorToolUseIds = const {}, - FoldLevel foldLevel = FoldLevel.none}) async { + Future pumpWith( + WidgetTester tester, + List items, { + Set hiddenToolUseIds = const {}, + Map toolUseOutcomes = const {}, + Set quietErrorToolUseIds = const {}, + FoldLevel foldLevel = FoldLevel.none, + }) async { tester.view.physicalSize = const Size(900, 700); tester.view.devicePixelRatio = 1.0; addTearDown(() { @@ -164,20 +167,23 @@ void main() { addTearDown(c.dispose); // Disable animations so an in-flight run's ClideSpinner (a perpetual // animation) renders static and pumpAndSettle can settle (T-296). - await tester.pumpWidget(harness( - f, - Builder( - builder: (ctx) => MediaQuery( - data: MediaQuery.of(ctx).copyWith(disableAnimations: true), - child: ConversationView( + await tester.pumpWidget( + harness( + f, + Builder( + builder: (ctx) => MediaQuery( + data: MediaQuery.of(ctx).copyWith(disableAnimations: true), + child: ConversationView( controller: c, hiddenToolUseIds: hiddenToolUseIds, toolUseOutcomes: toolUseOutcomes, quietErrorToolUseIds: quietErrorToolUseIds, - foldLevel: foldLevel), + foldLevel: foldLevel, + ), + ), ), ), - )); + ); for (final it in items) { stream.add(it); } @@ -198,17 +204,19 @@ void main() { final stream = StreamController.broadcast(); final c = ConversationController(stream: stream.stream); addTearDown(c.dispose); - await tester.pumpWidget(harness( - f, - Builder( - // The in-flight Bash collapser shows a live spinner (T-305); disable - // animations so it renders static and pumpAndSettle can settle. - builder: (ctx) => MediaQuery( - data: MediaQuery.of(ctx).copyWith(disableAnimations: true), - child: ConversationView(controller: c, foldLevel: FoldLevel.none), + await tester.pumpWidget( + harness( + f, + Builder( + // The in-flight Bash collapser shows a live spinner (T-305); disable + // animations so it renders static and pumpAndSettle can settle. + builder: (ctx) => MediaQuery( + data: MediaQuery.of(ctx).copyWith(disableAnimations: true), + child: ConversationView(controller: c, foldLevel: FoldLevel.none), + ), ), ), - )); + ); stream.add(AssistantToolUse(uuid: 'A', timestamp: _t, isSidechain: false, toolUseId: 'A', name: 'Bash', input: const {'command': 'echo a'})); stream.add(AssistantThinkingMessage(uuid: 'B', timestamp: _t, isSidechain: false, thinking: 'thinking body')); await tester.pumpAndSettle(); @@ -226,15 +234,17 @@ void main() { final stream = StreamController.broadcast(); final c = ConversationController(stream: stream.stream); addTearDown(c.dispose); - await tester.pumpWidget(harness( - f, - Builder( - builder: (ctx) => MediaQuery( - data: MediaQuery.of(ctx).copyWith(disableAnimations: true), - child: ConversationView(controller: c, foldLevel: FoldLevel.tools), + await tester.pumpWidget( + harness( + f, + Builder( + builder: (ctx) => MediaQuery( + data: MediaQuery.of(ctx).copyWith(disableAnimations: true), + child: ConversationView(controller: c, foldLevel: FoldLevel.tools), + ), ), ), - )); + ); stream.add(AssistantToolUse(uuid: 'A', timestamp: _t, isSidechain: false, toolUseId: 'A', name: 'Bash', input: const {'command': 'echo a'})); stream.add(AssistantToolUse(uuid: 'B', timestamp: _t, isSidechain: false, toolUseId: 'B', name: 'Read', input: const {'file_path': '/a'})); await tester.pumpAndSettle(); @@ -286,15 +296,7 @@ void main() { AssistantToolUse edit(String id, String path) => AssistantToolUse(uuid: id, timestamp: _t, isSidechain: false, toolUseId: id, name: 'Edit', input: {'file_path': path}); ToolResultMessage ok(String id) => ToolResultMessage(uuid: 'r$id', timestamp: _t, isSidechain: false, toolUseId: id, content: 'done', isError: false); - await pumpWith( - tester, - [ - edit('e1', '/lib/x.dart'), - ok('e1'), - edit('e2', '/lib/x.dart'), - ok('e2'), - ], - foldLevel: FoldLevel.tools); + await pumpWith(tester, [edit('e1', '/lib/x.dart'), ok('e1'), edit('e2', '/lib/x.dart'), ok('e2')], foldLevel: FoldLevel.tools); // One bundled card labelled "2 edits" with an aggregate status indicator. expect(find.text('2 edits'), findsOneWidget); expect(find.byType(ClideStatusIndicator), findsOneWidget); @@ -309,15 +311,12 @@ void main() { }); testWidgets('meta items fold into a collapsed activity card; tap expands (T-230)', (tester) async { - await pumpWith( - tester, - [ - _tool('Bash', const {'command': 'echo hi'}), - // A second, distinct in-flight tool call (T-262 folds a success - // result into its call card, so two *calls* are what make 2 steps). - AssistantToolUse(uuid: 'tu2', timestamp: _t, isSidechain: false, toolUseId: 'x2', name: 'Read', input: const {'file_path': '/a'}), - ], - foldLevel: FoldLevel.tools); + await pumpWith(tester, [ + _tool('Bash', const {'command': 'echo hi'}), + // A second, distinct in-flight tool call (T-262 folds a success + // result into its call card, so two *calls* are what make 2 steps). + AssistantToolUse(uuid: 'tu2', timestamp: _t, isSidechain: false, toolUseId: 'x2', name: 'Read', input: const {'file_path': '/a'}), + ], foldLevel: FoldLevel.tools); // Collapsed by default: one card with a step count, not the raw rows. expect(find.text('2 steps'), findsOneWidget); expect(find.bySemanticsLabel('Activity, 2 steps, collapsed'), findsOneWidget); @@ -328,13 +327,10 @@ void main() { }); testWidgets('a folded success result is not a separate step — merged into its call (T-262 note D)', (tester) async { - await pumpWith( - tester, - [ - _tool('Bash', const {'command': 'echo hi'}), - _result('hi there'), - ], - foldLevel: FoldLevel.tools); + await pumpWith(tester, [ + _tool('Bash', const {'command': 'echo hi'}), + _result('hi there'), + ], foldLevel: FoldLevel.tools); // The call + its success result is ONE unit now: 1 step, not 2. expect(find.text('1 step'), findsOneWidget); expect(find.text('2 steps'), findsNothing); @@ -360,13 +356,10 @@ void main() { }); testWidgets('a failed result surfaces first-class, not folded (T-230)', (tester) async { - await pumpWith( - tester, - [ - _tool('Bash', const {'command': 'boom'}), - _result('error output', isError: true), - ], - foldLevel: FoldLevel.tools); + await pumpWith(tester, [ + _tool('Bash', const {'command': 'boom'}), + _result('error output', isError: true), + ], foldLevel: FoldLevel.tools); // The tool call folds (1 step); the error result is sticky → no 2-step card. expect(find.text('1 step'), findsOneWidget); expect(find.textContaining('error output'), findsWidgets); @@ -376,13 +369,7 @@ void main() { final stream = StreamController.broadcast(); final c = ConversationController(stream: stream.stream); addTearDown(c.dispose); - await tester.pumpWidget(harness( - f, - ConversationView( - controller: c, - emptyState: const ClideText('CUSTOM EMPTY'), - ), - )); + await tester.pumpWidget(harness(f, ConversationView(controller: c, emptyState: const ClideText('CUSTOM EMPTY')))); expect(find.text('CUSTOM EMPTY'), findsOneWidget); expect(find.text('Waiting for Claude…'), findsNothing); }); @@ -421,7 +408,13 @@ void main() { testWidgets('a sidechain prompt folds into its Agent card; never labelled "you" (T-263)', (tester) async { await pumpWith(tester, [ AssistantToolUse( - uuid: 'agt-msg', timestamp: _t, isSidechain: false, toolUseId: 'task1', name: 'Task', input: const {'description': 'explore the codebase'}), + uuid: 'agt-msg', + timestamp: _t, + isSidechain: false, + toolUseId: 'task1', + name: 'Task', + input: const {'description': 'explore the codebase'}, + ), UserMessage(uuid: 'p1', timestamp: _t, isSidechain: true, text: 'find all the widgets'), ]); // Never the blue "you", and no standalone block (folded → suppressed). @@ -458,9 +451,7 @@ void main() { testWidgets('an orphan sidechain prompt renders as muted "agent prompt", never "you" (T-263)', (tester) async { // No Agent tool-use to attach to → stays standalone, but relabelled. - await pumpWith(tester, [ - UserMessage(uuid: 'orphan', timestamp: _t, isSidechain: true, text: 'orphaned agent instructions'), - ]); + await pumpWith(tester, [UserMessage(uuid: 'orphan', timestamp: _t, isSidechain: true, text: 'orphaned agent instructions')]); expect(find.text('you'), findsNothing); expect(find.text('agent prompt'), findsOneWidget); }); @@ -490,7 +481,14 @@ void main() { UserMessage(uuid: 'p', timestamp: _t, isSidechain: true, parentUuid: 'mA', text: 'go explore'), // A sidechain tool call — part of the run, chained off the prompt. AssistantToolUse( - uuid: 's1', timestamp: _t, isSidechain: true, parentUuid: 'p', toolUseId: 'sb', name: 'Bash', input: const {'command': 'grep widgets'}), + uuid: 's1', + timestamp: _t, + isSidechain: true, + parentUuid: 'p', + toolUseId: 'sb', + name: 'Bash', + input: const {'command': 'grep widgets'}, + ), ]); expect(find.text('Task'), findsOneWidget); // The run is a nested holder titled "agent run", collapsed by default — @@ -550,17 +548,13 @@ void main() { testWidgets('sidechain assistant prose is attributed to "agent", not "claude" (T-265)', (tester) async { // An orphan sidechain prose (no resolvable Agent) renders inline, still // attributed to the agent — never the main-thread coral "claude". - await pumpWith(tester, [ - AssistantTextMessage(uuid: 's', timestamp: _t, isSidechain: true, text: 'sub-agent says hi'), - ]); + await pumpWith(tester, [AssistantTextMessage(uuid: 's', timestamp: _t, isSidechain: true, text: 'sub-agent says hi')]); expect(find.text('agent'), findsOneWidget); expect(find.text('claude'), findsNothing); }); testWidgets('sidechain thinking is attributed to "agent thinking" (T-265)', (tester) async { - await pumpWith(tester, [ - AssistantThinkingMessage(uuid: 's', timestamp: _t, isSidechain: true, thinking: 'hmm let me think'), - ]); + await pumpWith(tester, [AssistantThinkingMessage(uuid: 's', timestamp: _t, isSidechain: true, thinking: 'hmm let me think')]); expect(find.text('agent thinking'), findsOneWidget); expect(find.text('thinking'), findsNothing); }); @@ -578,7 +572,7 @@ void main() { tester, [ _tool('Write', {'file_path': '/tmp/x'}), - _result('done') + _result('done'), ], hiddenToolUseIds: {'x1'}, // _tool + _result both use toolUseId 'x1' ); @@ -593,7 +587,7 @@ void main() { tester, [ _tool('Write', {'file_path': '/tmp/x'}), - _result('done') + _result('done'), ], hiddenToolUseIds: {'x1'}, toolUseOutcomes: {'x1': true}, // approved @@ -619,7 +613,7 @@ void main() { testWidgets('tool-use body: Bash shows the command in the collapsed summary (T-168)', (tester) async { await pumpWith(tester, [ - _tool('Bash', {'command': 'ls -la'}) + _tool('Bash', {'command': 'ls -la'}), ]); // Collapser starts collapsed — the command appears as the echoed summary. expect(find.text('ls -la'), findsOneWidget); @@ -632,7 +626,7 @@ void main() { testWidgets('tool-use body: Read/Grep/LS shows a compact path label (T-168)', (tester) async { await pumpWith(tester, [ - _tool('Read', {'file_path': '/foo/bar.dart'}) + _tool('Read', {'file_path': '/foo/bar.dart'}), ]); // The path label appears (collapsed summary or body). expect(find.text('/foo/bar.dart'), findsOneWidget); @@ -665,7 +659,7 @@ void main() { final handle = tester.ensureSemantics(); // In-flight: a call with no result yet → no check, no folded result. await pumpWith(tester, [ - _tool('Bash', {'command': 'ls'}) + _tool('Bash', {'command': 'ls'}), ]); expect(find.bySemanticsLabel('succeeded'), findsNothing); expect(find.bySemanticsLabel('failed'), findsNothing); @@ -734,16 +728,7 @@ void main() { testWidgets('result without a paired tool_use uses plain "result" label (T-168)', (tester) async { // Orphan result (no matching tool_use in the controller). - await pumpWith(tester, [ - ToolResultMessage( - uuid: 'r-orphan', - timestamp: _t, - isSidechain: false, - toolUseId: 'unknown-id', - content: 'ok', - isError: false, - ), - ]); + await pumpWith(tester, [ToolResultMessage(uuid: 'r-orphan', timestamp: _t, isSidechain: false, toolUseId: 'unknown-id', content: 'ok', isError: false)]); expect(find.text('result'), findsOneWidget); }); @@ -811,14 +796,7 @@ void main() { tearDown(() => f.dispose()); testWidgets('shows role, workspace, status, and a hint', (tester) async { - await tester.pumpWidget(harness( - f, - const ClaudeBanner( - role: 'primary', - workspace: '/work/space', - statusLine: 'tmux · clide-claude-x', - ), - )); + await tester.pumpWidget(harness(f, const ClaudeBanner(role: 'primary', workspace: '/work/space', statusLine: 'tmux · clide-claude-x'))); await tester.pump(); expect(find.text('Claude'), findsOneWidget); expect(find.text('primary'), findsOneWidget); diff --git a/test/builtin/claude/pane_context_status_test.dart b/test/builtin/claude/pane_context_status_test.dart index 9f7062a9..d5da301b 100644 --- a/test/builtin/claude/pane_context_status_test.dart +++ b/test/builtin/claude/pane_context_status_test.dart @@ -24,30 +24,30 @@ import '../../helpers/widget_harness.dart'; /// the minimum theme/kernel tree under a tight, [Align]ed [SizedBox] so the /// `Flexible` gets a real bounded width from the surrounding Row. Widget _narrowRow(KernelFixture f, double width, Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: width, - height: 24, - child: Row( - children: [ - Flexible(flex: 1, fit: FlexFit.loose, child: child), - const SizedBox(width: 20), // simulated right-side items - ], - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: width, + height: 24, + child: Row( + children: [ + Flexible(flex: 1, fit: FlexFit.loose, child: child), + const SizedBox(width: 20), // simulated right-side items + ], ), ), ), ), - ); + ), + ), +); void main() { late KernelFixture f; @@ -76,11 +76,7 @@ void main() { testWidgets('no RenderFlex overflow at narrow width — ClideMarquee is bounded', (tester) async { // A long status line similar to what T-154 added: // "opus 4.7 · default · 21k ctx · 10 skills" — roughly 280 px of text. - const longStatus = Text( - 'opus 4.7 · default · 21k ctx · 10 skills', - textDirection: TextDirection.ltr, - softWrap: false, - ); + const longStatus = Text('opus 4.7 · default · 21k ctx · 10 skills', textDirection: TextDirection.ltr, softWrap: false); // Pump at 200 px wide — narrower than the status content so the marquee // must receive a bounded viewport < content width and start scrolling. diff --git a/test/builtin/claude/permission_mode_control_test.dart b/test/builtin/claude/permission_mode_control_test.dart index 0e84ab7b..ec3bd29e 100644 --- a/test/builtin/claude/permission_mode_control_test.dart +++ b/test/builtin/claude/permission_mode_control_test.dart @@ -18,10 +18,15 @@ void main() { tearDown(() async => f.dispose()); Future pump(WidgetTester tester, String mode, ValueChanged onSelect) { - return tester.pumpWidget(harness( - f, - Align(alignment: Alignment.center, child: PermissionModeControl(mode: mode, onSelect: onSelect)), - )); + return tester.pumpWidget( + harness( + f, + Align( + alignment: Alignment.center, + child: PermissionModeControl(mode: mode, onSelect: onSelect), + ), + ), + ); } testWidgets('opens a menu of the safe trio + a bypass row; selecting sets the mode', (tester) async { @@ -70,16 +75,9 @@ void main() { tearDown(() async => f.dispose()); testWidgets('the mode control and the Stop row coexist while busy', (tester) async { - await tester.pumpWidget(harness( - f, - ClaudeComposer( - onSubmit: (_) {}, - busy: true, - onInterrupt: () {}, - permissionMode: 'default', - onSetPermissionMode: (_) {}, - ), - )); + await tester.pumpWidget( + harness(f, ClaudeComposer(onSubmit: (_) {}, busy: true, onInterrupt: () {}, permissionMode: 'default', onSetPermissionMode: (_) {})), + ); await tester.pump(); // Stop affordance (busy row) and the trailing mode control are both present. expect(find.textContaining('Stop'), findsOneWidget); diff --git a/test/builtin/claude/prompt_card_test.dart b/test/builtin/claude/prompt_card_test.dart index 75380c32..19f13cae 100644 --- a/test/builtin/claude/prompt_card_test.dart +++ b/test/builtin/claude/prompt_card_test.dart @@ -9,60 +9,60 @@ import '../../helpers/kernel_fixture.dart'; import '../../helpers/widget_harness.dart'; ToolPrompt permissionPrompt({List suggestions = const []}) => ToolPrompt( - promptId: 'req-1', - toolName: 'Write', - displayName: 'Write', - description: 'banana.txt', - input: const {'file_path': '/tmp/banana.txt', 'content': 'banana'}, - permissionSuggestions: suggestions, - ); + promptId: 'req-1', + toolName: 'Write', + displayName: 'Write', + description: 'banana.txt', + input: const {'file_path': '/tmp/banana.txt', 'content': 'banana'}, + permissionSuggestions: suggestions, +); ToolPrompt questionPrompt({bool multi = false}) => ToolPrompt( - promptId: 'req-q', - toolName: 'AskUserQuestion', - displayName: 'AskUserQuestion', - input: { - 'questions': [ - { - 'question': 'Do you prefer cats or dogs?', - 'header': 'Pet', - 'multiSelect': multi, - 'options': [ - {'label': 'Cats', 'description': 'cat person'}, - {'label': 'Dogs', 'description': 'dog person'}, - ], - }, + promptId: 'req-q', + toolName: 'AskUserQuestion', + displayName: 'AskUserQuestion', + input: { + 'questions': [ + { + 'question': 'Do you prefer cats or dogs?', + 'header': 'Pet', + 'multiSelect': multi, + 'options': [ + {'label': 'Cats', 'description': 'cat person'}, + {'label': 'Dogs', 'description': 'dog person'}, ], }, - ); + ], + }, +); ToolPrompt twoQuestionPrompt() => const ToolPrompt( - promptId: 'req-2q', - toolName: 'AskUserQuestion', - displayName: 'AskUserQuestion', - input: { - 'questions': [ - { - 'question': 'Which pet?', - 'header': 'Pet', - 'multiSelect': false, - 'options': [ - {'label': 'Cats', 'description': ''}, - {'label': 'Dogs', 'description': ''}, - ], - }, - { - 'question': 'How eaten?', - 'header': 'Eaten', - 'multiSelect': false, - 'options': [ - {'label': 'Fresh', 'description': ''}, - {'label': 'Smoothie', 'description': ''}, - ], - }, + promptId: 'req-2q', + toolName: 'AskUserQuestion', + displayName: 'AskUserQuestion', + input: { + 'questions': [ + { + 'question': 'Which pet?', + 'header': 'Pet', + 'multiSelect': false, + 'options': [ + {'label': 'Cats', 'description': ''}, + {'label': 'Dogs', 'description': ''}, ], }, - ); + { + 'question': 'How eaten?', + 'header': 'Eaten', + 'multiSelect': false, + 'options': [ + {'label': 'Fresh', 'description': ''}, + {'label': 'Smoothie', 'description': ''}, + ], + }, + ], + }, +); void main() { late KernelFixture f; @@ -72,16 +72,18 @@ void main() { testWidgets('permission card: Allow returns AllowTool echoing the input', (tester) async { ToolDecision? decision; String? id; - await tester.pumpWidget(harness( - f, - ToolPromptCard( - prompt: permissionPrompt(), - onResolve: (p, d) { - id = p; - decision = d; - }, + await tester.pumpWidget( + harness( + f, + ToolPromptCard( + prompt: permissionPrompt(), + onResolve: (p, d) { + id = p; + decision = d; + }, + ), ), - )); + ); await tester.pump(); expect(find.text('permission · Write'), findsOneWidget); @@ -97,7 +99,7 @@ void main() { }); testWidgets('permission card shows the command being permitted', (tester) async { - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, _) {}))); await tester.pump(); expect(find.byType(ClideCodeBlock), findsOneWidget); }); @@ -110,7 +112,7 @@ void main() { description: 'Read IDE lock files', input: {'command': 'cat ~/.claude/ide/97632.lock', 'description': 'Read IDE lock files'}, ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); final block = tester.widget(find.byType(ClideCodeBlock)); @@ -129,7 +131,7 @@ void main() { description: 'long task', input: {'command': 'sleep 30', 'run_in_background': true, 'timeout': 60000}, ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); expect(find.text('background · timeout 60000ms'), findsOneWidget); }); @@ -145,7 +147,7 @@ void main() { description: '/tmp/clide-ux-test.txt', input: {'file_path': '/tmp/clide-ux-test.txt', 'content': 'hello'}, ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); // The path should appear exactly once (in _pathLine, inside the body). @@ -160,7 +162,7 @@ void main() { description: 'banana.txt', input: {'file_path': '/tmp/banana.txt', 'content': 'banana'}, ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); expect(find.text('banana.txt'), findsOneWidget); @@ -168,13 +170,8 @@ void main() { }); testWidgets('permission card: unknown tool falls back to JSON', (tester) async { - const prompt = ToolPrompt( - promptId: 'req-x', - toolName: 'NovelTool', - displayName: 'NovelTool', - input: {'foo': 'bar'}, - ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + const prompt = ToolPrompt(promptId: 'req-x', toolName: 'NovelTool', displayName: 'NovelTool', input: {'foo': 'bar'}); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); final block = tester.widget(find.byType(ClideCodeBlock)); expect(block.language, 'json'); @@ -183,10 +180,7 @@ void main() { testWidgets('permission card: Deny returns DenyTool with a message', (tester) async { ToolDecision? decision; - await tester.pumpWidget(harness( - f, - ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, d) => decision = d), - )); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, d) => decision = d))); await tester.pump(); await tester.tap(find.text('2. Deny')); @@ -197,7 +191,7 @@ void main() { }); testWidgets('permission: no "don\'t ask again" button without a suggestion', (tester) async { - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: permissionPrompt(), onResolve: (_, _) {}))); await tester.pump(); expect(find.text("2. Allow & don't ask again"), findsNothing); }); @@ -205,12 +199,17 @@ void main() { testWidgets('permission: "don\'t ask again" shows with a suggestion and returns updatedPermissions', (tester) async { ToolDecision? decision; const sugg = [ - {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'} + {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'}, ]; - await tester.pumpWidget(harness( - f, - ToolPromptCard(prompt: permissionPrompt(suggestions: sugg), onResolve: (_, d) => decision = d), - )); + await tester.pumpWidget( + harness( + f, + ToolPromptCard( + prompt: permissionPrompt(suggestions: sugg), + onResolve: (_, d) => decision = d, + ), + ), + ); await tester.pump(); expect(find.text("2. Allow & don't ask again"), findsOneWidget); @@ -272,10 +271,7 @@ void main() { testWidgets('question card: Submit is gated until an option is picked, then returns answers', (tester) async { ToolDecision? decision; - await tester.pumpWidget(harness( - f, - ToolPromptCard(prompt: questionPrompt(), onResolve: (_, d) => decision = d), - )); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: questionPrompt(), onResolve: (_, d) => decision = d))); await tester.pump(); expect(find.text('Do you prefer cats or dogs?'), findsOneWidget); @@ -297,10 +293,7 @@ void main() { testWidgets('question card: multi-select joins chosen labels comma-separated', (tester) async { ToolDecision? decision; - await tester.pumpWidget(harness( - f, - ToolPromptCard(prompt: questionPrompt(multi: true), onResolve: (_, d) => decision = d), - )); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: questionPrompt(multi: true), onResolve: (_, d) => decision = d))); await tester.pump(); await tester.tap(find.textContaining('Cats')); @@ -396,13 +389,9 @@ void main() { promptId: 'req-e', toolName: 'Edit', displayName: 'Edit', - input: { - 'file_path': '/tmp/foo.dart', - 'old_string': 'void main() {}', - 'new_string': 'void main() => run();', - }, + input: {'file_path': '/tmp/foo.dart', 'old_string': 'void main() {}', 'new_string': 'void main() => run();'}, ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); // Two code blocks: before + after. expect(find.byType(ClideCodeBlock), findsNWidgets(2)); @@ -413,13 +402,8 @@ void main() { group('permission card: Read/Grep show compact path via shared helper', () { testWidgets('Read shows the file path label', (tester) async { - const prompt = ToolPrompt( - promptId: 'req-r', - toolName: 'Read', - displayName: 'Read', - input: {'file_path': '/docs/readme.md'}, - ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + const prompt = ToolPrompt(promptId: 'req-r', toolName: 'Read', displayName: 'Read', input: {'file_path': '/docs/readme.md'}); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); expect(find.text('/docs/readme.md'), findsOneWidget); // No code blocks — just a text label for Read. @@ -428,13 +412,8 @@ void main() { testWidgets('Grep shows pattern quoted alongside any path', (tester) async { // Grep with both path and pattern: the label shows file_path + quoted pattern. - const prompt = ToolPrompt( - promptId: 'req-grep', - toolName: 'Grep', - displayName: 'Grep', - input: {'pattern': 'TODO', 'path': '/src'}, - ); - await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, __) {}))); + const prompt = ToolPrompt(promptId: 'req-grep', toolName: 'Grep', displayName: 'Grep', input: {'pattern': 'TODO', 'path': '/src'}); + await tester.pumpWidget(harness(f, ToolPromptCard(prompt: prompt, onResolve: (_, _) {}))); await tester.pump(); // The combined label contains both the path and the quoted pattern. expect(find.textContaining('"TODO"'), findsOneWidget); @@ -449,13 +428,15 @@ void main() { final notifier = ValueNotifier(questionPrompt()); addTearDown(notifier.dispose); - await tester.pumpWidget(harness( - f, - ValueListenableBuilder( - valueListenable: notifier, - builder: (_, p, __) => ToolPromptCard(prompt: p, onResolve: (_, d) => decision = d), + await tester.pumpWidget( + harness( + f, + ValueListenableBuilder( + valueListenable: notifier, + builder: (_, p, _) => ToolPromptCard(prompt: p, onResolve: (_, d) => decision = d), + ), ), - )); + ); await tester.pump(); expect(find.text('Do you prefer cats or dogs?'), findsOneWidget); @@ -530,7 +511,7 @@ void main() { }); const sugg = [ - {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'} + {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'}, ]; testWidgets('with a remember suggestion: 2 = Allow & remember', (tester) async { diff --git a/test/builtin/claude/running_indicator_test.dart b/test/builtin/claude/running_indicator_test.dart index 76885595..867e7b24 100644 --- a/test/builtin/claude/running_indicator_test.dart +++ b/test/builtin/claude/running_indicator_test.dart @@ -22,9 +22,9 @@ void main() { tearDown(() => f.dispose()); Widget wrap({bool reducedMotion = false}) => MediaQuery( - data: MediaQueryData(disableAnimations: reducedMotion), - child: const RunningIndicator(shuffle: false), - ); + data: MediaQueryData(disableAnimations: reducedMotion), + child: const RunningIndicator(shuffle: false), + ); testWidgets('animates the ellipsis and rotates the verb', (tester) async { await tester.pumpWidget(harness(f, wrap())); diff --git a/test/builtin/claude/session_index_test.dart b/test/builtin/claude/session_index_test.dart index 83ce6727..dc826da8 100644 --- a/test/builtin/claude/session_index_test.dart +++ b/test/builtin/claude/session_index_test.dart @@ -5,39 +5,39 @@ import 'package:clide/builtin/claude/src/session_index.dart'; import 'package:test/test.dart'; String userLine(String text) => jsonEncode({ - 'type': 'user', - 'message': {'role': 'user', 'content': text}, - }); + 'type': 'user', + 'message': {'role': 'user', 'content': text}, +}); String userBlocksLine(String text) => jsonEncode({ - 'type': 'user', - 'message': { - 'role': 'user', - 'content': [ - {'type': 'text', 'text': text}, - ], - }, - }); + 'type': 'user', + 'message': { + 'role': 'user', + 'content': [ + {'type': 'text', 'text': text}, + ], + }, +}); String toolResultLine() => jsonEncode({ - 'type': 'user', - 'message': { - 'role': 'user', - 'content': [ - {'type': 'tool_result', 'content': 'output'}, - ], - }, - }); + 'type': 'user', + 'message': { + 'role': 'user', + 'content': [ + {'type': 'tool_result', 'content': 'output'}, + ], + }, +}); String assistantLine(String text) => jsonEncode({ - 'type': 'assistant', - 'message': { - 'role': 'assistant', - 'content': [ - {'type': 'text', 'text': text}, - ], - }, - }); + 'type': 'assistant', + 'message': { + 'role': 'assistant', + 'content': [ + {'type': 'text', 'text': text}, + ], + }, +}); void main() { group('userText', () { @@ -69,12 +69,7 @@ void main() { }); test('summarises each session with first … last bookends', () async { - await writeSession('aaaa', [ - userLine('start the swallow'), - assistantLine('ok'), - toolResultLine(), - userLine('now the peacock'), - ]); + await writeSession('aaaa', [userLine('start the swallow'), assistantLine('ok'), toolResultLine(), userLine('now the peacock')]); final sessions = await listSessions(dir); expect(sessions, hasLength(1)); expect(sessions.single.id, 'aaaa'); diff --git a/test/builtin/claude/session_lifecycle_test.dart b/test/builtin/claude/session_lifecycle_test.dart index cb9f8a77..93926474 100644 --- a/test/builtin/claude/session_lifecycle_test.dart +++ b/test/builtin/claude/session_lifecycle_test.dart @@ -47,14 +47,8 @@ ClaudeSessionOrchestrator _orch(List<_FakeProc> created) { ); } -SpawnSpec _spec(String id, {bool resume = false, String? transcriptPath, String cwd = '/repo'}) => SpawnSpec( - id: id, - role: id, - sessionId: '$id-uuid', - cwd: cwd, - resume: resume, - transcriptPath: transcriptPath, - ); +SpawnSpec _spec(String id, {bool resume = false, String? transcriptPath, String cwd = '/repo'}) => + SpawnSpec(id: id, role: id, sessionId: '$id-uuid', cwd: cwd, resume: resume, transcriptPath: transcriptPath); // --------------------------------------------------------------------------- // Tests @@ -195,11 +189,7 @@ void main() { '"message":{"role":"user","content":"hello from the past"}}\n', ); - final managed = await orch.spawn(_spec( - 'primary', - resume: true, - transcriptPath: file.path, - )); + final managed = await orch.spawn(_spec('primary', resume: true, transcriptPath: file.path)); expect(managed.conversation.items, hasLength(1)); await tmp.delete(recursive: true); @@ -210,13 +200,7 @@ void main() { await orch.close('primary'); // /resume picked a past session id; re-spawn with resume:true. - final picked = SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'picked-past-uuid', - cwd: '/repo', - resume: true, - ); + final picked = SpawnSpec(id: 'primary', role: 'primary', sessionId: 'picked-past-uuid', cwd: '/repo', resume: true); final managed = await orch.spawn(picked); expect(managed.sessionId, 'picked-past-uuid'); expect(created, hasLength(2)); @@ -278,22 +262,8 @@ void main() { }); test('kill-all includes team sessions', () async { - await orch.spawn(SpawnSpec( - id: 'primary', - role: 'lead', - sessionId: 'primary-uuid', - cwd: '/repo', - team: true, - memberName: 'lead', - )); - await orch.spawn(SpawnSpec( - id: 'teammate:tyre', - role: 'teammate', - sessionId: 'tyre-uuid', - cwd: '/repo', - team: true, - memberName: 'tyre', - )); + await orch.spawn(SpawnSpec(id: 'primary', role: 'lead', sessionId: 'primary-uuid', cwd: '/repo', team: true, memberName: 'lead')); + await orch.spawn(SpawnSpec(id: 'teammate:tyre', role: 'teammate', sessionId: 'tyre-uuid', cwd: '/repo', team: true, memberName: 'tyre')); expect(orch.sessions, hasLength(2)); // Broker has 2 agent members + 1 virtual 'user' member (T-180). final agentMembers = orch.broker.members.where((m) => m.id != 'user'); diff --git a/test/builtin/claude/session_orchestrator_test.dart b/test/builtin/claude/session_orchestrator_test.dart index 232e7595..fc752fb6 100644 --- a/test/builtin/claude/session_orchestrator_test.dart +++ b/test/builtin/claude/session_orchestrator_test.dart @@ -26,11 +26,13 @@ void main() { setUp(() { created = []; - orch = ClaudeSessionOrchestrator(processFactory: ({required sessionArgs, required cwd, env}) async { - final p = _FakeProc(); - created.add(p); - return p; - }); + orch = ClaudeSessionOrchestrator( + processFactory: ({required sessionArgs, required cwd, env}) async { + final p = _FakeProc(); + created.add(p); + return p; + }, + ); }); SpawnSpec spec(String id, {bool visible = true}) => SpawnSpec(id: id, role: id, sessionId: '$id-uuid', cwd: '/repo', visible: visible); @@ -44,21 +46,9 @@ void main() { }); test('folds the real claude session id from the init event into the session (T-185)', () async { - final m = await orch.spawn(SpawnSpec( - id: 'fork-x', - role: 'teammate', - sessionId: 'placeholder-uuid', - cwd: '/repo', - forkSourceSessionId: 'source-uuid', - )); + final m = await orch.spawn(SpawnSpec(id: 'fork-x', role: 'teammate', sessionId: 'placeholder-uuid', cwd: '/repo', forkSourceSessionId: 'source-uuid')); expect(m.sessionId, 'placeholder-uuid'); // starts as the placeholder - created.last.emit(jsonEncode({ - 'type': 'system', - 'subtype': 'init', - 'session_id': 'real-fork-id', - 'model': 'claude-opus-4-8', - 'permissionMode': 'default', - })); + created.last.emit(jsonEncode({'type': 'system', 'subtype': 'init', 'session_id': 'real-fork-id', 'model': 'claude-opus-4-8', 'permissionMode': 'default'})); await Future.delayed(Duration.zero); expect(m.sessionId, 'real-fork-id'); // updated to the branch's real id }); @@ -161,14 +151,9 @@ void main() { '{"type":"assistant","uuid":"a1","timestamp":"2026-05-26T00:00:01Z","isSidechain":false,"message":{"role":"assistant","content":[{"type":"text","text":"hi back"}]}}\n', ); - final managed = await orch.spawn(SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'primary-uuid', - cwd: '/repo', - resume: true, - transcriptPath: file.path, - )); + final managed = await orch.spawn( + SpawnSpec(id: 'primary', role: 'primary', sessionId: 'primary-uuid', cwd: '/repo', resume: true, transcriptPath: file.path), + ); final items = managed.conversation.items; expect(items, hasLength(2)); expect(items.first, isA()); @@ -179,26 +164,23 @@ void main() { }); test('non-resume spawn does not read the transcript', () async { - final managed = await orch.spawn(SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'primary-uuid', - cwd: '/repo', - // resume:false → transcriptPath ignored even if set - transcriptPath: '/does/not/exist.jsonl', - )); + final managed = await orch.spawn( + SpawnSpec( + id: 'primary', + role: 'primary', + sessionId: 'primary-uuid', + cwd: '/repo', + // resume:false → transcriptPath ignored even if set + transcriptPath: '/does/not/exist.jsonl', + ), + ); expect(managed.conversation.items, isEmpty); }); test('missing transcript file is tolerated (best-effort hydration)', () async { - final managed = await orch.spawn(SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'primary-uuid', - cwd: '/repo', - resume: true, - transcriptPath: '/does/not/exist.jsonl', - )); + final managed = await orch.spawn( + SpawnSpec(id: 'primary', role: 'primary', sessionId: 'primary-uuid', cwd: '/repo', resume: true, transcriptPath: '/does/not/exist.jsonl'), + ); expect(managed.conversation.items, isEmpty); }); }); @@ -218,13 +200,8 @@ void main() { ); }); - SpawnSpec forkSpec(String id, String sourceSessionId) => SpawnSpec( - id: id, - role: 'fork', - sessionId: '$id-placeholder', - cwd: '/repo', - forkSourceSessionId: sourceSessionId, - ); + SpawnSpec forkSpec(String id, String sourceSessionId) => + SpawnSpec(id: id, role: 'fork', sessionId: '$id-placeholder', cwd: '/repo', forkSourceSessionId: sourceSessionId); test('fork spawn passes --resume --fork-session instead of --session-id', () async { const sourceId = 'bbbb2222-2222-4222-8222-222222222222'; @@ -288,12 +265,7 @@ void main() { }); test('ManagedSession.cwd reflects the spec cwd', () async { - final managed = await orch.spawn(SpawnSpec( - id: 'primary', - role: 'primary', - sessionId: 'primary-uuid', - cwd: '/my/project', - )); + final managed = await orch.spawn(SpawnSpec(id: 'primary', role: 'primary', sessionId: 'primary-uuid', cwd: '/my/project')); expect(managed.cwd, '/my/project'); }); }); diff --git a/test/builtin/claude/session_picker_test.dart b/test/builtin/claude/session_picker_test.dart index e7bf7670..3741d2ec 100644 --- a/test/builtin/claude/session_picker_test.dart +++ b/test/builtin/claude/session_picker_test.dart @@ -27,16 +27,13 @@ void main() { tearDown(() => f.dispose()); List two() => [ - SessionSummary(id: 'aaa', modified: DateTime.now(), firstUser: 'first a', lastUser: 'last a'), - SessionSummary(id: 'bbb', modified: DateTime.now(), firstUser: 'first b', lastUser: 'last b'), - ]; + SessionSummary(id: 'aaa', modified: DateTime.now(), firstUser: 'first a', lastUser: 'last a'), + SessionSummary(id: 'bbb', modified: DateTime.now(), firstUser: 'first b', lastUser: 'last b'), + ]; testWidgets('renders first … last labels and picks with arrow + Enter', (tester) async { String? picked; - await tester.pumpWidget(harness( - f, - SessionPickerDialog(sessions: two(), onPick: (id) => picked = id, onCancel: () {}), - )); + await tester.pumpWidget(harness(f, SessionPickerDialog(sessions: two(), onPick: (id) => picked = id, onCancel: () {}))); await tester.pump(); expect(find.text('first a … last a'), findsOneWidget); expect(find.text('first b … last b'), findsOneWidget); @@ -49,10 +46,7 @@ void main() { testWidgets('Escape cancels', (tester) async { var cancelled = false; - await tester.pumpWidget(harness( - f, - SessionPickerDialog(sessions: two(), onPick: (_) {}, onCancel: () => cancelled = true), - )); + await tester.pumpWidget(harness(f, SessionPickerDialog(sessions: two(), onPick: (_) {}, onCancel: () => cancelled = true))); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.escape); expect(cancelled, isTrue); @@ -60,20 +54,14 @@ void main() { testWidgets('tap picks a row', (tester) async { String? picked; - await tester.pumpWidget(harness( - f, - SessionPickerDialog(sessions: two(), onPick: (id) => picked = id, onCancel: () {}), - )); + await tester.pumpWidget(harness(f, SessionPickerDialog(sessions: two(), onPick: (id) => picked = id, onCancel: () {}))); await tester.pump(); await tester.tap(find.text('first b … last b')); expect(picked, 'bbb'); }); testWidgets('empty list shows a message', (tester) async { - await tester.pumpWidget(harness( - f, - SessionPickerDialog(sessions: const [], onPick: (_) {}, onCancel: () {}), - )); + await tester.pumpWidget(harness(f, SessionPickerDialog(sessions: const [], onPick: (_) {}, onCancel: () {}))); await tester.pump(); expect(find.text('No sessions found for this workspace.'), findsOneWidget); }); diff --git a/test/builtin/claude/session_storage_test.dart b/test/builtin/claude/session_storage_test.dart index b1a4bb9b..dc1fa50f 100644 --- a/test/builtin/claude/session_storage_test.dart +++ b/test/builtin/claude/session_storage_test.dart @@ -17,15 +17,9 @@ void main() { testWidgets('shows the total and a two-click delete that calls the deleter', (tester) async { final deleted = []; - await tester.pumpWidget(harness( - f, - SessionStorageDialog( - dir: Directory.systemTemp, - sessions: [session('aaa', 2048)], - onClose: () {}, - deleter: (d, id) async => deleted.add(id), - ), - )); + await tester.pumpWidget( + harness(f, SessionStorageDialog(dir: Directory.systemTemp, sessions: [session('aaa', 2048)], onClose: () {}, deleter: (d, id) async => deleted.add(id))), + ); await tester.pump(); expect(find.text('Session storage · 2 KB total'), findsOneWidget); @@ -44,30 +38,16 @@ void main() { testWidgets('Escape closes', (tester) async { var closed = false; - await tester.pumpWidget(harness( - f, - SessionStorageDialog( - dir: Directory.systemTemp, - sessions: [session('aaa', 1024)], - onClose: () => closed = true, - deleter: (_, __) async {}, - ), - )); + await tester.pumpWidget( + harness(f, SessionStorageDialog(dir: Directory.systemTemp, sessions: [session('aaa', 1024)], onClose: () => closed = true, deleter: (_, _) async {})), + ); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.escape); expect(closed, isTrue); }); testWidgets('empty list shows a message', (tester) async { - await tester.pumpWidget(harness( - f, - SessionStorageDialog( - dir: Directory.systemTemp, - sessions: const [], - onClose: () {}, - deleter: (_, __) async {}, - ), - )); + await tester.pumpWidget(harness(f, SessionStorageDialog(dir: Directory.systemTemp, sessions: const [], onClose: () {}, deleter: (_, _) async {}))); await tester.pump(); expect(find.text('No sessions found for this workspace.'), findsOneWidget); }); diff --git a/test/builtin/claude/stream_json_session_test.dart b/test/builtin/claude/stream_json_session_test.dart index 460537ba..306c71c3 100644 --- a/test/builtin/claude/stream_json_session_test.dart +++ b/test/builtin/claude/stream_json_session_test.dart @@ -28,12 +28,12 @@ class _FakeMcpServer implements McpServer { final List calls = []; @override List> get tools => [ - { - 'name': 'ping', - 'description': 'p', - 'inputSchema': {'type': 'object', 'properties': {}}, - }, - ]; + { + 'name': 'ping', + 'description': 'p', + 'inputSchema': {'type': 'object', 'properties': {}}, + }, + ]; @override Future> callTool(String name, Map arguments) async { calls.add(name); @@ -47,114 +47,101 @@ class _FakeMcpServer implements McpServer { } String mcpMessage(String rid, Map message, {String server = 'clide-team'}) => jsonEncode({ - 'type': 'control_request', - 'request_id': rid, - 'request': {'subtype': 'mcp_message', 'server_name': server, 'message': message}, - }); + 'type': 'control_request', + 'request_id': rid, + 'request': {'subtype': 'mcp_message', 'server_name': server, 'message': message}, +}); String assistantText(String text) => jsonEncode({ - 'type': 'assistant', - 'uuid': 'a1', - 'message': { - 'model': 'claude-opus-4-7', - 'role': 'assistant', - 'content': [ - {'type': 'text', 'text': text}, - ], - 'usage': {'input_tokens': 100, 'cache_read_input_tokens': 50, 'cache_creation_input_tokens': 0}, - }, - }); + 'type': 'assistant', + 'uuid': 'a1', + 'message': { + 'model': 'claude-opus-4-7', + 'role': 'assistant', + 'content': [ + {'type': 'text', 'text': text}, + ], + 'usage': {'input_tokens': 100, 'cache_read_input_tokens': 50, 'cache_creation_input_tokens': 0}, + }, +}); String assistantToolUse() => jsonEncode({ - 'type': 'assistant', - 'uuid': 'a2', - 'message': { - 'role': 'assistant', - 'content': [ - { - 'type': 'tool_use', - 'id': 't1', - 'name': 'Bash', - 'input': {'command': 'ls'} - }, - ], + 'type': 'assistant', + 'uuid': 'a2', + 'message': { + 'role': 'assistant', + 'content': [ + { + 'type': 'tool_use', + 'id': 't1', + 'name': 'Bash', + 'input': {'command': 'ls'}, }, - }); + ], + }, +}); -String initEvent() => jsonEncode({ - 'type': 'system', - 'subtype': 'init', - 'model': 'claude-opus-4-7', - 'permissionMode': 'default', - }); +String initEvent() => jsonEncode({'type': 'system', 'subtype': 'init', 'model': 'claude-opus-4-7', 'permissionMode': 'default'}); -String resultEvent({double? cost, Map? modelUsage}) => jsonEncode({ - 'type': 'result', - 'result': '', - 'usage': {}, - if (cost != null) 'total_cost_usd': cost, - if (modelUsage != null) 'modelUsage': modelUsage, - }); +String resultEvent({double? cost, Map? modelUsage}) => + jsonEncode({'type': 'result', 'result': '', 'usage': {}, 'total_cost_usd': ?cost, 'modelUsage': ?modelUsage}); String rateLimitEvent({String? status, String? resetsAt}) => jsonEncode({ - 'type': 'rate_limit_event', - 'rate_limit_info': { - if (status != null) 'status': status, - if (resetsAt != null) 'resetsAt': resetsAt, - }, - }); + 'type': 'rate_limit_event', + 'rate_limit_info': {'status': ?status, 'resetsAt': ?resetsAt}, +}); // Real `--include-partial-messages` wire shape (captured from claude 2.1.150, // interactive stream-json mode — T-184): partials arrive as `stream_event` // envelopes wrapping Anthropic streaming deltas, NOT `assistant`+`partial:true`. String streamMessageStart(String messageId) => jsonEncode({ - 'type': 'stream_event', - 'event': { - 'type': 'message_start', - 'message': {'id': messageId, 'role': 'assistant', 'content': []}, - }, - }); + 'type': 'stream_event', + 'event': { + 'type': 'message_start', + 'message': {'id': messageId, 'role': 'assistant', 'content': []}, + }, +}); String streamTextDelta(String text, {int index = 0}) => jsonEncode({ - 'type': 'stream_event', - 'event': { - 'type': 'content_block_delta', - 'index': index, - 'delta': {'type': 'text_delta', 'text': text}, - }, - }); + 'type': 'stream_event', + 'event': { + 'type': 'content_block_delta', + 'index': index, + 'delta': {'type': 'text_delta', 'text': text}, + }, +}); String streamMessageStop() => jsonEncode({ - 'type': 'stream_event', - 'event': {'type': 'message_stop'}, - }); + 'type': 'stream_event', + 'event': {'type': 'message_stop'}, +}); // The final per-block `assistant` event carrying a message id (so the session // can pair it with a streamed placeholder). String assistantTextWithId(String messageId, String text, {String uuid = 'final-uuid'}) => jsonEncode({ - 'type': 'assistant', - 'uuid': uuid, - 'message': { - 'id': messageId, - 'role': 'assistant', - 'content': [ - {'type': 'text', 'text': text}, - ], - }, - }); + 'type': 'assistant', + 'uuid': uuid, + 'message': { + 'id': messageId, + 'role': 'assistant', + 'content': [ + {'type': 'text', 'text': text}, + ], + }, +}); String canUseTool(String rid, {String tool = 'Write', Map? input}) => jsonEncode({ - 'type': 'control_request', - 'request_id': rid, - 'request': { - 'subtype': 'can_use_tool', - 'tool_name': tool, - 'display_name': tool, - 'description': 'banana.txt', - 'input': input ?? {'file_path': '/tmp/banana.txt', 'content': 'banana'}, - 'tool_use_id': 'toolu_1', - }, - }); + 'type': 'control_request', + 'request_id': rid, + 'request': { + 'subtype': 'can_use_tool', + 'tool_name': tool, + 'display_name': tool, + 'description': 'banana.txt', + 'input': input ?? {'file_path': '/tmp/banana.txt', 'content': 'banana'}, + 'tool_use_id': 'toolu_1', + }, +}); void main() { late _FakeProc proc; @@ -209,13 +196,7 @@ void main() { test('captures the claude session id from the first event carrying it (T-185)', () async { final ids = []; session.sessionIdResolved.listen(ids.add); - proc.emit(jsonEncode({ - 'type': 'system', - 'subtype': 'init', - 'session_id': 'sess-abc', - 'model': 'claude-opus-4-7', - 'permissionMode': 'default', - })); + proc.emit(jsonEncode({'type': 'system', 'subtype': 'init', 'session_id': 'sess-abc', 'model': 'claude-opus-4-7', 'permissionMode': 'default'})); await Future.delayed(Duration.zero); expect(session.claudeSessionId, 'sess-abc'); expect(ids, ['sess-abc']); @@ -229,12 +210,14 @@ void main() { }); test('result event with modelUsage populates contextWindow', () async { - proc.emit(resultEvent( - cost: 0.01, - modelUsage: { - 'claude-opus-4-7': {'contextWindow': 1000000, 'maxOutputTokens': 8192}, - }, - )); + proc.emit( + resultEvent( + cost: 0.01, + modelUsage: { + 'claude-opus-4-7': {'contextWindow': 1000000, 'maxOutputTokens': 8192}, + }, + ), + ); await Future.delayed(Duration.zero); expect(statuses.last.contextWindow, 1000000); }); @@ -275,10 +258,12 @@ void main() { test('rate_limit_event with a numeric (epoch) resetsAt does not crash', () async { // Claude sends resetsAt as a unix-epoch number, not a string — the // old `as String?` cast threw 'int is not a subtype of String?'. - proc.emit(jsonEncode({ - 'type': 'rate_limit_event', - 'rate_limit_info': {'status': 'rate_limited', 'resetsAt': 1780000000}, - })); + proc.emit( + jsonEncode({ + 'type': 'rate_limit_event', + 'rate_limit_info': {'status': 'rate_limited', 'resetsAt': 1780000000}, + }), + ); await Future.delayed(Duration.zero); expect(statuses.last.rateLimitInfo, contains('rate limited')); expect(statuses.last.rateLimitInfo, contains('resets')); @@ -361,31 +346,35 @@ void main() { }); test('a synthetic user message (skill/command inject) is flagged injected', () async { - proc.emit(jsonEncode({ - 'type': 'user', - 'isSynthetic': true, - 'message': { - 'role': 'user', - 'content': [ - {'type': 'text', 'text': 'Base directory for this skill: /x'} - ], - }, - })); + proc.emit( + jsonEncode({ + 'type': 'user', + 'isSynthetic': true, + 'message': { + 'role': 'user', + 'content': [ + {'type': 'text', 'text': 'Base directory for this skill: /x'}, + ], + }, + }), + ); await Future.delayed(Duration.zero); final u = items.whereType().single; expect(u.injected, isTrue); }); test('a plain user text event is not flagged injected', () async { - proc.emit(jsonEncode({ - 'type': 'user', - 'message': { - 'role': 'user', - 'content': [ - {'type': 'text', 'text': 'hello'} - ], - }, - })); + proc.emit( + jsonEncode({ + 'type': 'user', + 'message': { + 'role': 'user', + 'content': [ + {'type': 'text', 'text': 'hello'}, + ], + }, + }), + ); await Future.delayed(Duration.zero); expect(items.whereType().single.injected, isFalse); }); @@ -428,18 +417,20 @@ void main() { }); test('a permission request carries its permission_suggestions', () async { - proc.emit(jsonEncode({ - 'type': 'control_request', - 'request_id': 'rs', - 'request': { - 'subtype': 'can_use_tool', - 'tool_name': 'Write', - 'input': {'file_path': '/tmp/x'}, - 'permission_suggestions': [ - {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'} - ], - }, - })); + proc.emit( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'rs', + 'request': { + 'subtype': 'can_use_tool', + 'tool_name': 'Write', + 'input': {'file_path': '/tmp/x'}, + 'permission_suggestions': [ + {'type': 'setMode', 'mode': 'acceptEdits', 'destination': 'session'}, + ], + }, + }), + ); await Future.delayed(Duration.zero); expect(session.pendingPrompt!.permissionSuggestions, hasLength(1)); }); @@ -448,12 +439,14 @@ void main() { proc.emit(canUseTool('rp')); await Future.delayed(Duration.zero); session.resolvePrompt( - 'rp', - AllowTool(const { - 'x': 1 - }, updatedPermissions: const [ - {'type': 'setMode'} - ])); + 'rp', + AllowTool( + const {'x': 1}, + updatedPermissions: const [ + {'type': 'setMode'}, + ], + ), + ); final decision = ((jsonDecode(proc.writes.single) as Map)['response'] as Map)['response'] as Map; expect(decision['behavior'], 'allow'); expect(decision['updatedPermissions'], hasLength(1)); @@ -496,21 +489,24 @@ void main() { }); test('resolving an AskUserQuestion leaves an answered echo in the log', () async { - proc.emit(jsonEncode({ - 'type': 'control_request', - 'request_id': 'aq', - 'request': { - 'subtype': 'can_use_tool', - 'tool_name': 'AskUserQuestion', - 'input': {'questions': []}, - }, - })); + proc.emit( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'aq', + 'request': { + 'subtype': 'can_use_tool', + 'tool_name': 'AskUserQuestion', + 'input': {'questions': []}, + }, + }), + ); await Future.delayed(Duration.zero); session.resolvePrompt( - 'aq', - AllowTool(const { - 'answers': {'Pet': 'Dogs'} - })); + 'aq', + AllowTool(const { + 'answers': {'Pet': 'Dogs'}, + }), + ); await Future.delayed(Duration.zero); final echo = items.whereType().toList(); @@ -541,11 +537,13 @@ void main() { }); test('an unsupported control_request is answered with an error (no hang)', () async { - proc.emit(jsonEncode({ - 'type': 'control_request', - 'request_id': 'req-5', - 'request': {'subtype': 'mystery_subtype'}, - })); + proc.emit( + jsonEncode({ + 'type': 'control_request', + 'request_id': 'req-5', + 'request': {'subtype': 'mystery_subtype'}, + }), + ); await Future.delayed(Duration.zero); expect(items, isEmpty); @@ -646,19 +644,19 @@ void main() { } test('declares its sdkMcpServers in the initialize handshake', () { - final init = mproc.writes.map((w) => jsonDecode(w) as Map).firstWhere( - (m) => (m['request'] as Map?)?['subtype'] == 'initialize', - ); + final init = mproc.writes.map((w) => jsonDecode(w) as Map).firstWhere((m) => (m['request'] as Map?)?['subtype'] == 'initialize'); expect((init['request'] as Map)['sdkMcpServers'], ['clide-team']); }); test('answers mcp initialize with our serverInfo', () async { - mproc.emit(mcpMessage('m1', { - 'method': 'initialize', - 'params': {'protocolVersion': '2025-11-25'}, - 'jsonrpc': '2.0', - 'id': 0, - })); + mproc.emit( + mcpMessage('m1', { + 'method': 'initialize', + 'params': {'protocolVersion': '2025-11-25'}, + 'jsonrpc': '2.0', + 'id': 0, + }), + ); await Future.delayed(Duration.zero); final r = mcpResponseOf(mproc.writes.last); expect((r['result'] as Map)['serverInfo'], {'name': 'clide-team', 'version': '9.9.9'}); @@ -673,12 +671,14 @@ void main() { }); test('routes tools/call to the server and returns its result', () async { - mproc.emit(mcpMessage('m3', { - 'method': 'tools/call', - 'params': {'name': 'ping', 'arguments': {}}, - 'jsonrpc': '2.0', - 'id': 2, - })); + mproc.emit( + mcpMessage('m3', { + 'method': 'tools/call', + 'params': {'name': 'ping', 'arguments': {}}, + 'jsonrpc': '2.0', + 'id': 2, + }), + ); await Future.delayed(Duration.zero); expect(server.calls, ['ping']); final r = mcpResponseOf(mproc.writes.last); diff --git a/test/builtin/claude/task_list_test.dart b/test/builtin/claude/task_list_test.dart index 747d6b06..f63cc899 100644 --- a/test/builtin/claude/task_list_test.dart +++ b/test/builtin/claude/task_list_test.dart @@ -19,7 +19,7 @@ void main() { {'content': 'a', 'status': 'pending'}, {'content': 'b', 'status': 'in_progress'}, {'content': 'c', 'status': 'completed'}, - ]) + ]), ]); expect(tasks, const [ TaskItem(text: 'a', status: TaskStatus.pending), @@ -31,10 +31,10 @@ void main() { test('the latest TodoWrite wins — a snapshot, not an append log', () { final tasks = taskListFrom([ _todo([ - {'content': 'old', 'status': 'pending'} + {'content': 'old', 'status': 'pending'}, ], id: '1'), _todo([ - {'content': 'new', 'status': 'in_progress'} + {'content': 'new', 'status': 'in_progress'}, ], id: '2'), ]); expect(tasks, const [TaskItem(text: 'new', status: TaskStatus.inProgress)]); @@ -45,7 +45,7 @@ void main() { _todo([ {'activeForm': 'doing it', 'status': 'in_progress'}, {'status': 'weird'}, - ]) + ]), ]); expect(tasks[0].text, 'doing it'); expect(tasks[1].text, ''); @@ -56,7 +56,7 @@ void main() { expect(taskListFrom(const []), isEmpty); expect( taskListFrom([ - AssistantToolUse(uuid: 'b', timestamp: _t, isSidechain: false, toolUseId: 'b', name: 'Bash', input: const {'command': 'ls'}) + AssistantToolUse(uuid: 'b', timestamp: _t, isSidechain: false, toolUseId: 'b', name: 'Bash', input: const {'command': 'ls'}), ]), isEmpty, ); diff --git a/test/builtin/claude/team_chat_sidebar_test.dart b/test/builtin/claude/team_chat_sidebar_test.dart index 20aa2920..32a723c6 100644 --- a/test/builtin/claude/team_chat_sidebar_test.dart +++ b/test/builtin/claude/team_chat_sidebar_test.dart @@ -18,7 +18,7 @@ void main() { setUp(() async { f = await KernelFixture.create(); - broker = TeamBroker(deliver: (_, __) {}); + broker = TeamBroker(deliver: (_, _) {}); broker.addMember(const TeamMemberRef(id: 'primary', name: 'lead', role: 'lead')); broker.addMember(const TeamMemberRef(id: 'teammate:tyre', name: 'tyre', role: 'teammate')); model = TeamChatModel(broker: broker); @@ -36,17 +36,13 @@ void main() { group('TeamChatSidebar', () { Widget sidebar({VoidCallback? onPopOut}) => harness( - f, - SizedBox( - width: 220, - height: 400, - child: TeamChatSidebar( - model: model, - broker: broker, - onPopOut: onPopOut ?? () {}, - ), - ), - ); + f, + SizedBox( + width: 220, + height: 400, + child: TeamChatSidebar(model: model, broker: broker, onPopOut: onPopOut ?? () {}), + ), + ); testWidgets('renders MESSAGES header', (tester) async { await tester.pumpWidget(sidebar()); @@ -131,18 +127,16 @@ void main() { var popped = false; // Use a tall harness so the MESSAGES header (and its pop-out icon) is // always in view and tappable. - await tester.pumpWidget(harness( - f, - SizedBox( - width: 300, - height: 800, - child: TeamChatSidebar( - model: model, - broker: broker, - onPopOut: () => popped = true, + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 300, + height: 800, + child: TeamChatSidebar(model: model, broker: broker, onPopOut: () => popped = true), ), ), - )); + ); await tester.pump(); // The pop-out icon is wired via Semantics(label: 'Open full chat pane'). @@ -188,18 +182,13 @@ void main() { await tester.pumpAndSettle(); // Focus the field and set text with a selection so cursor is at end. - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // Set value with explicit cursor position at end. - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: 3), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: 3)); await tester.pump(); await tester.pump(); @@ -211,18 +200,13 @@ void main() { await tester.pumpWidget(sidebar()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // No match — _updateSuggestions called with null. - field.controller.value = const TextEditingValue( - text: '@zzz', - selection: TextSelection.collapsed(offset: 4), - ); + field.controller.value = const TextEditingValue(text: '@zzz', selection: TextSelection.collapsed(offset: 4)); await tester.pump(); await tester.pump(); @@ -236,25 +220,17 @@ void main() { await tester.pumpWidget(sidebar()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // Set @ty (matches tyre) → _showOverlay called. - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: 3), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: 3)); await tester.pump(); // Clear → _removeOverlay called. - field.controller.value = const TextEditingValue( - text: '', - selection: TextSelection.collapsed(offset: 0), - ); + field.controller.value = const TextEditingValue(text: '', selection: TextSelection.collapsed(offset: 0)); await tester.pump(); await tester.pump(); @@ -266,18 +242,13 @@ void main() { await tester.pumpWidget(sidebar()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // Set a matching @-prefix to activate the overlay path. - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: 3), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: 3)); await tester.pump(); // Send Escape — _handleKeyEvent should return KeyEventResult.handled. @@ -294,18 +265,13 @@ void main() { await tester.pumpWidget(sidebar()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-sidebar'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // A value with no selection (baseOffset == -1) triggers the cursor < 0 guard. - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: -1), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: -1)); await tester.pump(); await tester.pump(); @@ -337,13 +303,13 @@ void main() { group('TeamChatPane', () { Widget pane() => harness( - f, - SizedBox( - width: 400, - height: 600, - child: TeamChatPane(model: model, broker: broker), - ), - ); + f, + SizedBox( + width: 400, + height: 600, + child: TeamChatPane(model: model, broker: broker), + ), + ); testWidgets('renders Team Chat header', (tester) async { await tester.pumpWidget(pane()); @@ -408,18 +374,13 @@ void main() { await tester.pumpWidget(pane()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // Set @ty to activate overlay path. - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: 3), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: 3)); await tester.pump(); // Escape dismisses. @@ -434,18 +395,13 @@ void main() { await tester.pumpWidget(pane()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); // @le → matches lead. - field.controller.value = const TextEditingValue( - text: '@le', - selection: TextSelection.collapsed(offset: 3), - ); + field.controller.value = const TextEditingValue(text: '@le', selection: TextSelection.collapsed(offset: 3)); await tester.pump(); await tester.pump(); @@ -457,17 +413,12 @@ void main() { await tester.pumpWidget(pane()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); - field.controller.value = const TextEditingValue( - text: '@zzz', - selection: TextSelection.collapsed(offset: 4), - ); + field.controller.value = const TextEditingValue(text: '@zzz', selection: TextSelection.collapsed(offset: 4)); await tester.pump(); await tester.pump(); @@ -479,17 +430,12 @@ void main() { await tester.pumpWidget(pane()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); final field = tester.widget(chatField); field.focusNode.requestFocus(); await tester.pump(); - field.controller.value = const TextEditingValue( - text: '@ty', - selection: TextSelection.collapsed(offset: -1), - ); + field.controller.value = const TextEditingValue(text: '@ty', selection: TextSelection.collapsed(offset: -1)); await tester.pump(); await tester.pump(); @@ -500,9 +446,7 @@ void main() { await tester.pumpWidget(pane()); await tester.pumpAndSettle(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); await tester.enterText(chatField, ' '); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); @@ -519,9 +463,7 @@ void main() { await tester.tap(interruptArea); await tester.pump(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); await tester.enterText(chatField, 'urgent message'); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); @@ -538,9 +480,7 @@ void main() { await tester.tap(find.text('Interrupt')); await tester.pump(); - final chatField = find.byWidgetPredicate( - (w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane', - ); + final chatField = find.byWidgetPredicate((w) => w is EditableText && w.focusNode.debugLabel == 'team-chat-pane'); await tester.enterText(chatField, '@lead do this now'); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); @@ -549,26 +489,26 @@ void main() { }); testWidgets('sidebar and pane share the same model (both surfaces update)', (tester) async { - await tester.pumpWidget(harness( - f, - SizedBox( - width: 800, - height: 600, - child: Row( - children: [ - SizedBox( - width: 220, - child: TeamChatSidebar( - model: model, - broker: broker, - onPopOut: () {}, + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 800, + height: 600, + child: Row( + children: [ + SizedBox( + width: 220, + child: TeamChatSidebar(model: model, broker: broker, onPopOut: () {}), ), - ), - Expanded(child: TeamChatPane(model: model, broker: broker)), - ], + Expanded( + child: TeamChatPane(model: model, broker: broker), + ), + ], + ), ), ), - )); + ); await tester.pump(); // Posting from the model shows up in both surfaces. diff --git a/test/builtin/claude/team_panel_host_test.dart b/test/builtin/claude/team_panel_host_test.dart index fb52a735..66795604 100644 --- a/test/builtin/claude/team_panel_host_test.dart +++ b/test/builtin/claude/team_panel_host_test.dart @@ -16,15 +16,8 @@ import '../../helpers/widget_harness.dart'; Widget _lead() => const Center(child: Text('LEAD', textDirection: TextDirection.ltr)); -TeamMemberJoined _joined(String name, String pane, {String? color}) => TeamMemberJoined( - team: 'myteam', - agentId: '$name@myteam', - name: name, - agentType: 'researcher', - paneId: pane, - model: 'sonnet', - color: color, - ); +TeamMemberJoined _joined(String name, String pane, {String? color}) => + TeamMemberJoined(team: 'myteam', agentId: '$name@myteam', name: name, agentType: 'researcher', paneId: pane, model: 'sonnet', color: color); void main() { late KernelFixture f; diff --git a/test/builtin/claude/ticket_pick_up_test.dart b/test/builtin/claude/ticket_pick_up_test.dart index 88d7e794..29027115 100644 --- a/test/builtin/claude/ticket_pick_up_test.dart +++ b/test/builtin/claude/ticket_pick_up_test.dart @@ -81,7 +81,12 @@ void main() { test('already started: injects but does not move the status backwards (T-339)', () async { await orch.spawn(SpawnSpec(id: 'primary', role: 'primary', sessionId: 'p-uuid', cwd: '/repo')); - final accepted = await applyTicketPickUp(payload(status: 'in_progress'), orchestrator: orch, ipc: ipc, messages: messages); + final accepted = await applyTicketPickUp( + payload(status: 'in_progress'), + orchestrator: orch, + ipc: ipc, + messages: messages, + ); await Future.delayed(Duration.zero); expect(accepted, isTrue); // prompt still delivered @@ -91,7 +96,12 @@ void main() { test('a backlog ticket is also startable', () async { await orch.spawn(SpawnSpec(id: 'primary', role: 'primary', sessionId: 'p-uuid', cwd: '/repo')); - await applyTicketPickUp(payload(status: 'backlog'), orchestrator: orch, ipc: ipc, messages: messages); + await applyTicketPickUp( + payload(status: 'backlog'), + orchestrator: orch, + ipc: ipc, + messages: messages, + ); expect(statusCalls, hasLength(1)); }); diff --git a/test/builtin/claude/transcript_publisher_test.dart b/test/builtin/claude/transcript_publisher_test.dart index 200759d7..8c2d9f1b 100644 --- a/test/builtin/claude/transcript_publisher_test.dart +++ b/test/builtin/claude/transcript_publisher_test.dart @@ -12,29 +12,29 @@ import 'package:clide/kernel/src/events/message_bus.dart'; import 'package:test/test.dart'; Map _userLine(String uuid, String text) => { - 'type': 'user', - 'uuid': uuid, - 'parentUuid': '', - 'isSidechain': false, - 'version': '2.1.143', - 'timestamp': '2026-05-16T08:53:06.708Z', - 'message': {'role': 'user', 'content': text}, - }; + 'type': 'user', + 'uuid': uuid, + 'parentUuid': '', + 'isSidechain': false, + 'version': '2.1.143', + 'timestamp': '2026-05-16T08:53:06.708Z', + 'message': {'role': 'user', 'content': text}, +}; Map _asstLine(String uuid, String text) => { - 'type': 'assistant', - 'uuid': uuid, - 'parentUuid': '', - 'isSidechain': false, - 'version': '2.1.143', - 'timestamp': '2026-05-16T08:53:07.708Z', - 'message': { - 'role': 'assistant', - 'content': [ - {'type': 'text', 'text': text} - ], - }, - }; + 'type': 'assistant', + 'uuid': uuid, + 'parentUuid': '', + 'isSidechain': false, + 'version': '2.1.143', + 'timestamp': '2026-05-16T08:53:07.708Z', + 'message': { + 'role': 'assistant', + 'content': [ + {'type': 'text', 'text': text}, + ], + }, +}; void main() { group('TranscriptPublisher', () { @@ -49,9 +49,7 @@ void main() { test('republishes reader items onto the bus (lead channel + item key)', tags: ['serial'], () async { final dir = Directory('${base.path}/${workspace.replaceAll('/', '-')}'); await dir.create(recursive: true); - File('${dir.path}/session-abc.jsonl').writeAsStringSync( - '${[_userLine('u1', 'hello'), _asstLine('a1', 'hi there')].map(jsonEncode).join('\n')}\n', - ); + File('${dir.path}/session-abc.jsonl').writeAsStringSync('${[_userLine('u1', 'hello'), _asstLine('a1', 'hi there')].map(jsonEncode).join('\n')}\n'); final bus = MessageBus(); addTearDown(bus.dispose); @@ -59,11 +57,7 @@ void main() { // Subscribe before the publisher starts the reader's first poll. final sub = bus.subscribe(publisher: ClaudeConversation.publisher, channel: ClaudeConversation.leadChannel).listen(received.add); - final reader = TranscriptReader( - workspace, - projectsBase: base.path, - pollInterval: const Duration(milliseconds: 20), - ); + final reader = TranscriptReader(workspace, projectsBase: base.path, pollInterval: const Duration(milliseconds: 20)); final pub = TranscriptPublisher(messages: bus, reader: reader); await Future.delayed(const Duration(milliseconds: 200)); @@ -87,12 +81,7 @@ void main() { 'coder@team-x', const SessionStatus(model: 'claude-opus-4-7', permissionMode: 'plan', contextTokens: 21000), ); - expect(full, { - 'agentId': 'coder@team-x', - 'model': 'claude-opus-4-7', - 'permissionMode': 'plan', - 'contextTokens': 21000, - }); + expect(full, {'agentId': 'coder@team-x', 'model': 'claude-opus-4-7', 'permissionMode': 'plan', 'contextTokens': 21000}); // Absent fields are omitted (only agentId is always present). expect(ClaudeConversation.memberStatusData('a', const SessionStatus()), {'agentId': 'a'}); }); diff --git a/test/builtin/claude/transcript_reader_test.dart b/test/builtin/claude/transcript_reader_test.dart index 4e56196e..68a6c73c 100644 --- a/test/builtin/claude/transcript_reader_test.dart +++ b/test/builtin/claude/transcript_reader_test.dart @@ -16,27 +16,18 @@ import 'package:test/test.dart'; /// Write [lines] to [file], each JSON-encoded, newline-terminated. void writeLines(File file, List> lines) { - file.writeAsStringSync( - '${lines.map(jsonEncode).join('\n')}\n', - mode: FileMode.writeOnly, - ); + file.writeAsStringSync('${lines.map(jsonEncode).join('\n')}\n', mode: FileMode.writeOnly); } /// Append [lines] to [file]. void appendLines(File file, List> lines) { - file.writeAsStringSync( - '${lines.map(jsonEncode).join('\n')}\n', - mode: FileMode.append, - ); + file.writeAsStringSync('${lines.map(jsonEncode).join('\n')}\n', mode: FileMode.append); } /// Poll [ready] until it returns true or [timeout] elapses. Streaming /// assertions use this instead of a fixed delay so they don't flake under /// load (the reader polls on a timer and may parse off-isolate). -Future pumpUntil( - bool Function() ready, { - Duration timeout = const Duration(seconds: 5), -}) async { +Future pumpUntil(bool Function() ready, {Duration timeout = const Duration(seconds: 5)}) async { final deadline = DateTime.now().add(timeout); while (!ready() && DateTime.now().isBefore(deadline)) { await Future.delayed(const Duration(milliseconds: 10)); @@ -53,24 +44,12 @@ Map envelope({ String version = '2.1.143', String timestamp = '2026-05-16T08:53:06.708Z', }) { - return { - 'type': type, - 'uuid': uuid, - 'parentUuid': parentUuid, - 'isSidechain': isSidechain, - 'version': version, - 'timestamp': timestamp, - if (message != null) 'message': message, - }; + return {'type': type, 'uuid': uuid, 'parentUuid': parentUuid, 'isSidechain': isSidechain, 'version': version, 'timestamp': timestamp, 'message': ?message}; } /// Build a `user` envelope whose content is a plain string. Map userText(String uuid, String text) { - return envelope( - type: 'user', - uuid: uuid, - message: {'role': 'user', 'content': text}, - ); + return envelope(type: 'user', uuid: uuid, message: {'role': 'user', 'content': text}); } /// Build a `user` envelope whose content is an array of text parts. @@ -81,43 +60,28 @@ Map userTextArray(String uuid, List parts) { message: { 'role': 'user', 'content': [ - for (final p in parts) {'type': 'text', 'text': p} + for (final p in parts) {'type': 'text', 'text': p}, ], }, ); } /// Build a `user` envelope containing a tool_result. -Map userToolResult( - String uuid, { - required String toolUseId, - required String content, - bool isError = false, -}) { +Map userToolResult(String uuid, {required String toolUseId, required String content, bool isError = false}) { return envelope( type: 'user', uuid: uuid, message: { 'role': 'user', 'content': [ - { - 'type': 'tool_result', - 'tool_use_id': toolUseId, - 'content': content, - 'is_error': isError, - } + {'type': 'tool_result', 'tool_use_id': toolUseId, 'content': content, 'is_error': isError}, ], }, ); } /// Build an `assistant` envelope with a tool_use content block. -Map assistantToolUse( - String uuid, { - required String id, - required String name, - required Map input, -}) { +Map assistantToolUse(String uuid, {required String id, required String name, required Map input}) { return envelope( type: 'assistant', uuid: uuid, @@ -169,10 +133,7 @@ Map skipRecord(String type, String uuid) { /// Parses [lines] synchronously via the real [TranscriptReader.parseLine] and /// returns every emitted [ConversationItem]. -List parseAll( - List> lines, { - void Function(String)? onWarn, -}) { +List parseAll(List> lines, {void Function(String)? onWarn}) { final reader = TranscriptReader('/fake', onWarn: onWarn); return [for (final l in lines) ...reader.parseLine(jsonEncode(l))]; } @@ -185,12 +146,7 @@ void main() { // ------------------------------------------------------------------------- group('ConversationItem model', () { test('UserMessage holds text and metadata', () { - final item = UserMessage( - uuid: 'u1', - timestamp: _epoch, - isSidechain: false, - text: 'hello', - ); + final item = UserMessage(uuid: 'u1', timestamp: _epoch, isSidechain: false, text: 'hello'); expect(item.uuid, 'u1'); expect(item.text, 'hello'); expect(item.isSidechain, isFalse); @@ -198,48 +154,24 @@ void main() { }); test('AssistantTextMessage holds text', () { - final item = AssistantTextMessage( - uuid: 'a1', - timestamp: _epoch, - isSidechain: false, - text: 'world', - ); + final item = AssistantTextMessage(uuid: 'a1', timestamp: _epoch, isSidechain: false, text: 'world'); expect(item.text, 'world'); }); test('AssistantThinkingMessage holds thinking', () { - final item = AssistantThinkingMessage( - uuid: 't1', - timestamp: _epoch, - isSidechain: false, - thinking: 'pondering', - ); + final item = AssistantThinkingMessage(uuid: 't1', timestamp: _epoch, isSidechain: false, thinking: 'pondering'); expect(item.thinking, 'pondering'); }); test('AssistantToolUse holds name and input', () { - final item = AssistantToolUse( - uuid: 'tu1', - timestamp: _epoch, - isSidechain: false, - toolUseId: 'toolu_001', - name: 'Bash', - input: const {'command': 'ls'}, - ); + final item = AssistantToolUse(uuid: 'tu1', timestamp: _epoch, isSidechain: false, toolUseId: 'toolu_001', name: 'Bash', input: const {'command': 'ls'}); expect(item.name, 'Bash'); expect(item.input['command'], 'ls'); expect(item.toString(), contains('Bash')); }); test('ToolResultMessage holds content and error flag', () { - final item = ToolResultMessage( - uuid: 'r1', - timestamp: _epoch, - isSidechain: false, - toolUseId: 'toolu_001', - content: 'ok', - isError: false, - ); + final item = ToolResultMessage(uuid: 'r1', timestamp: _epoch, isSidechain: false, toolUseId: 'toolu_001', content: 'ok', isError: false); expect(item.content, 'ok'); expect(item.isError, isFalse); }); @@ -264,14 +196,7 @@ void main() { // ------------------------------------------------------------------------- group('TranscriptReader — parse: skip types', () { test('skips attachment, system, last-prompt, permission-mode, file-history-snapshot, queue-operation', () { - final skipTypes = [ - 'attachment', - 'system', - 'last-prompt', - 'permission-mode', - 'file-history-snapshot', - 'queue-operation', - ]; + final skipTypes = ['attachment', 'system', 'last-prompt', 'permission-mode', 'file-history-snapshot', 'queue-operation']; for (final t in skipTypes) { final items = parseAll([skipRecord(t, 'skip-$t')]); expect(items, isEmpty, reason: 'type "$t" should be skipped'); @@ -290,7 +215,7 @@ void main() { test('array content with text parts emits joined UserMessage', () { final items = parseAll([ - userTextArray('u2', ['foo', 'bar']) + userTextArray('u2', ['foo', 'bar']), ]); expect(items, hasLength(1)); final msg = items.first as UserMessage; @@ -298,9 +223,7 @@ void main() { }); test('array content with tool_result emits ToolResultMessage', () { - final items = parseAll([ - userToolResult('u3', toolUseId: 'toolu_abc', content: '{"ok":true}'), - ]); + final items = parseAll([userToolResult('u3', toolUseId: 'toolu_abc', content: '{"ok":true}')]); expect(items, hasLength(1)); final res = items.first as ToolResultMessage; expect(res.toolUseId, 'toolu_abc'); @@ -309,9 +232,7 @@ void main() { }); test('tool_result with is_error=true sets isError', () { - final items = parseAll([ - userToolResult('u4', toolUseId: 'toolu_xyz', content: 'boom', isError: true), - ]); + final items = parseAll([userToolResult('u4', toolUseId: 'toolu_xyz', content: 'boom', isError: true)]); final res = items.first as ToolResultMessage; expect(res.isError, isTrue); }); @@ -324,12 +245,7 @@ void main() { 'role': 'user', 'content': [ {'type': 'text', 'text': 'see result'}, - { - 'type': 'tool_result', - 'tool_use_id': 'toolu_mixed', - 'content': 'done', - 'is_error': false, - }, + {'type': 'tool_result', 'tool_use_id': 'toolu_mixed', 'content': 'done', 'is_error': false}, ], }, ); @@ -352,23 +268,13 @@ void main() { }); test('isSidechain flag is preserved', () { - final raw = envelope( - type: 'user', - uuid: 'u7', - isSidechain: true, - message: {'role': 'user', 'content': 'side'}, - ); + final raw = envelope(type: 'user', uuid: 'u7', isSidechain: true, message: {'role': 'user', 'content': 'side'}); final items = parseAll([raw]); expect(items.first.isSidechain, isTrue); }); test('parentUuid is parsed; empty parentUuid normalises to null (T-263)', () { - final withParent = envelope( - type: 'user', - uuid: 'u8', - parentUuid: 'msg-A', - message: {'role': 'user', 'content': 'a sidechain prompt'}, - ); + final withParent = envelope(type: 'user', uuid: 'u8', parentUuid: 'msg-A', message: {'role': 'user', 'content': 'a sidechain prompt'}); final withoutParent = envelope( type: 'user', uuid: 'u9', @@ -382,22 +288,14 @@ void main() { test('stream-json parent_tool_use_id marks a sidechain message (T-338)', () { // The stream-json wire tags sub-agent messages with parent_tool_use_id and // NO isSidechain flag — we must treat it as a sidechain item anyway. - final raw = envelope( - type: 'user', - uuid: 'sp', - message: {'role': 'user', 'content': 'go explore the codebase'}, - )..['parent_tool_use_id'] = 'toolu_task1'; + final raw = envelope(type: 'user', uuid: 'sp', message: {'role': 'user', 'content': 'go explore the codebase'})..['parent_tool_use_id'] = 'toolu_task1'; final items = parseAll([raw]); expect(items.first.isSidechain, isTrue); expect(items.first.parentToolUseId, 'toolu_task1'); }); test('empty parent_tool_use_id normalises to null and stays main-thread (T-338)', () { - final raw = envelope( - type: 'user', - uuid: 'mt', - message: {'role': 'user', 'content': 'a normal turn'}, - )..['parent_tool_use_id'] = ''; + final raw = envelope(type: 'user', uuid: 'mt', message: {'role': 'user', 'content': 'a normal turn'})..['parent_tool_use_id'] = ''; final items = parseAll([raw]); expect(items.first.isSidechain, isFalse); expect(items.first.parentToolUseId, isNull); @@ -436,12 +334,7 @@ void main() { test('tool_use block emits AssistantToolUse', () { final items = parseAll([ - assistantToolUse( - 'a4', - id: 'toolu_001', - name: 'Bash', - input: {'command': 'ls -la', 'description': 'List files'}, - ), + assistantToolUse('a4', id: 'toolu_001', name: 'Bash', input: {'command': 'ls -la', 'description': 'List files'}), ]); expect(items, hasLength(1)); final tu = items.first as AssistantToolUse; @@ -463,7 +356,7 @@ void main() { 'type': 'tool_use', 'id': 'toolu_002', 'name': 'Read', - 'input': {'file_path': '/foo'} + 'input': {'file_path': '/foo'}, }, ], }, @@ -519,21 +412,13 @@ void main() { group('TranscriptReader — version drift-guard', () { test('known versions (1.x, 2.x) produce no warning', () { final warnings = []; - parseAll( - [userText('u1', 'hello'), assistantText('a1', 'world')], - onWarn: warnings.add, - ); + parseAll([userText('u1', 'hello'), assistantText('a1', 'world')], onWarn: warnings.add); expect(warnings, isEmpty); }); test('unknown major version warns and still emits parseable items', () { final warnings = []; - final raw = envelope( - type: 'user', - uuid: 'u1', - version: '99.0.1', - message: {'role': 'user', 'content': 'future format'}, - ); + final raw = envelope(type: 'user', uuid: 'u1', version: '99.0.1', message: {'role': 'user', 'content': 'future format'}); final items = parseAll([raw], onWarn: warnings.add); expect(warnings, hasLength(1)); expect(warnings.first, contains('99')); @@ -567,10 +452,7 @@ void main() { }); test('malformed JSON line is skipped without throwing', () { - expect( - TranscriptReader('/fake').parseLine('not json!!!'), - isEmpty, - ); + expect(TranscriptReader('/fake').parseLine('not json!!!'), isEmpty); }); }); @@ -588,14 +470,9 @@ void main() { 'role': 'assistant', 'model': 'claude-opus-4-7', 'content': [ - {'type': 'text', 'text': 'hi'} + {'type': 'text', 'text': 'hi'}, ], - 'usage': { - 'input_tokens': 2, - 'cache_read_input_tokens': 1000, - 'cache_creation_input_tokens': 500, - 'output_tokens': 99, - }, + 'usage': {'input_tokens': 2, 'cache_read_input_tokens': 1000, 'cache_creation_input_tokens': 500, 'output_tokens': 99}, }, }), ].join('\n'); @@ -672,16 +549,9 @@ void main() { await projectDir.create(recursive: true); final sessionFile = File('${projectDir.path}/session-abc.jsonl'); - writeLines(sessionFile, [ - userText('u1', 'first message'), - assistantText('a1', 'first reply'), - ]); + writeLines(sessionFile, [userText('u1', 'first message'), assistantText('a1', 'first reply')]); - final reader = TranscriptReader( - workspace, - projectsBase: tempBase.path, - pollInterval: const Duration(milliseconds: 20), - ); + final reader = TranscriptReader(workspace, projectsBase: tempBase.path, pollInterval: const Duration(milliseconds: 20)); final collected = []; final sub = reader.stream.listen(collected.add); @@ -701,11 +571,7 @@ void main() { writeLines(sessionFile, [userText('u1', 'initial')]); - final reader = TranscriptReader( - workspace, - projectsBase: tempBase.path, - pollInterval: const Duration(milliseconds: 20), - ); + final reader = TranscriptReader(workspace, projectsBase: tempBase.path, pollInterval: const Duration(milliseconds: 20)); final collected = []; final sub = reader.stream.listen(collected.add); @@ -740,11 +606,7 @@ void main() { writeLines(sessionFile, [userText('u1', 'old session')]); await sessionFile.setLastModified(DateTime.utc(2020)); - final reader = TranscriptReader( - workspace, - projectsBase: tempBase.path, - pollInterval: const Duration(milliseconds: 20), - ); + final reader = TranscriptReader(workspace, projectsBase: tempBase.path, pollInterval: const Duration(milliseconds: 20)); final collected = []; final sub = reader.stream.listen(collected.add); @@ -778,11 +640,7 @@ void main() { skipRecord('system', 'skip3'), ]); - final reader = TranscriptReader( - workspace, - projectsBase: tempBase.path, - pollInterval: const Duration(milliseconds: 20), - ); + final reader = TranscriptReader(workspace, projectsBase: tempBase.path, pollInterval: const Duration(milliseconds: 20)); final collected = []; final sub = reader.stream.listen(collected.add); @@ -802,18 +660,11 @@ void main() { // A long pre-existing transcript — the kind that froze the UI when // parsed in full on attach. - writeLines(sessionFile, [ - for (var i = 0; i < 200; i++) assistantText('a$i', 'reply number $i'), - ]); + writeLines(sessionFile, [for (var i = 0; i < 200; i++) assistantText('a$i', 'reply number $i')]); // Cap the initial read well below the file size so only the last // records are within the tail window. - final reader = TranscriptReader( - workspace, - projectsBase: tempBase.path, - pollInterval: const Duration(milliseconds: 20), - initialTailBytes: 256, - ); + final reader = TranscriptReader(workspace, projectsBase: tempBase.path, pollInterval: const Duration(milliseconds: 20), initialTailBytes: 256); final collected = []; final sub = reader.stream.listen(collected.add); @@ -826,10 +677,7 @@ void main() { expect(collected, isNotEmpty); expect(collected.length, lessThan(200)); // The most recent record is always intact at the end of the file. - expect( - collected.whereType().last.text, - 'reply number 199', - ); + expect(collected.whereType().last.text, 'reply number 199'); }); test('explicit file: tails that exact file, ignoring newest-discovery', () async { @@ -840,12 +688,7 @@ void main() { final target = File('${dir.path}/agent-abc.jsonl'); writeLines(target, [assistantText('a1', 'from the explicit file')]); - final reader = TranscriptReader( - '/unused/workspace', - projectsBase: '/nonexistent', - pollInterval: const Duration(milliseconds: 20), - file: target.path, - ); + final reader = TranscriptReader('/unused/workspace', projectsBase: '/nonexistent', pollInterval: const Duration(milliseconds: 20), file: target.path); final collected = []; final sub = reader.stream.listen(collected.add); @@ -864,12 +707,7 @@ void main() { addTearDown(() => dir.delete(recursive: true)); final target = File('${dir.path}/agent-late.jsonl'); - final reader = TranscriptReader( - '/unused', - projectsBase: '/nonexistent', - pollInterval: const Duration(milliseconds: 20), - file: target.path, - ); + final reader = TranscriptReader('/unused', projectsBase: '/nonexistent', pollInterval: const Duration(milliseconds: 20), file: target.path); final collected = []; final sub = reader.stream.listen(collected.add); @@ -901,7 +739,7 @@ void main() { 'role': 'assistant', 'model': 'claude-sonnet-4-6', 'content': [ - {'type': 'text', 'text': 'hi'} + {'type': 'text', 'text': 'hi'}, ], 'usage': {'input_tokens': 5, 'cache_read_input_tokens': 200, 'cache_creation_input_tokens': 0, 'output_tokens': 10}, }, @@ -955,18 +793,14 @@ List> _snapshotFixture() { 'type': 'tool_use', 'id': toolUseId, 'name': 'Bash', - 'input': {'command': 'ls -la'} + 'input': {'command': 'ls -la'}, }, ], }, ), // tool result (user turn) - userToolResult( - 'u-002', - toolUseId: toolUseId, - content: 'total 4\n-rw-r--r-- file.dart', - ), + userToolResult('u-002', toolUseId: toolUseId, content: 'total 4\n-rw-r--r-- file.dart'), // assistant text reply assistantText('a-002', 'The directory contains file.dart.'), diff --git a/test/builtin/cli_install/cli_install_extension_test.dart b/test/builtin/cli_install/cli_install_extension_test.dart index 8d4bdff0..1f530011 100644 --- a/test/builtin/cli_install/cli_install_extension_test.dart +++ b/test/builtin/cli_install/cli_install_extension_test.dart @@ -47,22 +47,26 @@ void main() { }); test('contributes the install command', () async { - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': '${tmp.path}/bin'}, - bundledClientCandidates: const [], - installDir: '${tmp.path}/bin', - )); + await boot( + CliInstaller( + resolvedExecutable: '${tmp.path}/gui/clide', + env: {'PATH': '${tmp.path}/bin'}, + bundledClientCandidates: const [], + installDir: '${tmp.path}/bin', + ), + ); expect(f.services.commands.get('clide.installCli'), isNotNull); }); test('warns on activation when clide is missing from PATH', () async { - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': '${tmp.path}/empty'}, - bundledClientCandidates: const [], - installDir: '${tmp.path}/bin', - )); + await boot( + CliInstaller( + resolvedExecutable: '${tmp.path}/gui/clide', + env: {'PATH': '${tmp.path}/empty'}, + bundledClientCandidates: const [], + installDir: '${tmp.path}/bin', + ), + ); final notes = f.services.notify.active; expect(notes, isNotEmpty); expect(notes.first.level, NotificationLevel.warning); @@ -73,12 +77,7 @@ void main() { final gui = touchExec('${tmp.path}/gui/clide').path; final binDir = Directory('${tmp.path}/bin')..createSync(); Link('${binDir.path}/clide').createSync(gui); // PATH clide → the GUI - await boot(CliInstaller( - resolvedExecutable: gui, - env: {'PATH': binDir.path}, - bundledClientCandidates: const [], - installDir: binDir.path, - )); + await boot(CliInstaller(resolvedExecutable: gui, env: {'PATH': binDir.path}, bundledClientCandidates: const [], installDir: binDir.path)); final notes = f.services.notify.active; expect(notes, isNotEmpty); expect(notes.first.level, NotificationLevel.warning); @@ -89,12 +88,9 @@ void main() { final dev = touchExec('${tmp.path}/native/linux-x64/clide').path; final binDir = Directory('${tmp.path}/bin')..createSync(); Link('${binDir.path}/clide').createSync(dev); - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir.path}, - bundledClientCandidates: const [], - installDir: binDir.path, - )); + await boot( + CliInstaller(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir.path}, bundledClientCandidates: const [], installDir: binDir.path), + ); final notes = f.services.notify.active; expect(notes, isNotEmpty); expect(notes.first.level, NotificationLevel.info); @@ -104,47 +100,35 @@ void main() { test('does not warn when clide is already installed', () async { final binDir = Directory('${tmp.path}/bin')..createSync(); touchExec('${binDir.path}/clide'); - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir.path}, - bundledClientCandidates: const [], - installDir: binDir.path, - )); + await boot( + CliInstaller(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir.path}, bundledClientCandidates: const [], installDir: binDir.path), + ); expect(f.services.notify.active, isEmpty); }); test('running the command installs the client and reports success', () async { final src = touchExec('${tmp.path}/bundle/clide-cli'); final binDir = '${tmp.path}/bin'; - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir}, - bundledClientCandidates: [src.path], - installDir: binDir, - )); + await boot(CliInstaller(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir}, bundledClientCandidates: [src.path], installDir: binDir)); final r = await f.services.commands.execute('clide.installCli'); expect(r.ok, isTrue); expect(r.data['installed'], '$binDir/clide'); expect(File('$binDir/clide').existsSync(), isTrue); - expect( - f.services.notify.active.any((n) => n.level == NotificationLevel.success && n.title == 'clide CLI installed'), - isTrue, - ); + expect(f.services.notify.active.any((n) => n.level == NotificationLevel.success && n.title == 'clide CLI installed'), isTrue); }); test('running the command surfaces a tool error when nothing to install', () async { - await boot(CliInstaller( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': '${tmp.path}/bin'}, - bundledClientCandidates: const [], - installDir: '${tmp.path}/bin', - )); + await boot( + CliInstaller( + resolvedExecutable: '${tmp.path}/gui/clide', + env: {'PATH': '${tmp.path}/bin'}, + bundledClientCandidates: const [], + installDir: '${tmp.path}/bin', + ), + ); final r = await f.services.commands.execute('clide.installCli'); expect(r.ok, isFalse); expect(r.error!.kind, IpcErrorKind.toolError); - expect( - f.services.notify.active.any((n) => n.level == NotificationLevel.error), - isTrue, - ); + expect(f.services.notify.active.any((n) => n.level == NotificationLevel.error), isTrue); }); } diff --git a/test/builtin/decisions/decision_reader_test.dart b/test/builtin/decisions/decision_reader_test.dart index 844b1526..387a6916 100644 --- a/test/builtin/decisions/decision_reader_test.dart +++ b/test/builtin/decisions/decision_reader_test.dart @@ -30,12 +30,7 @@ import '../../helpers/widget_harness.dart'; /// [Slots.contextPanel] slot into the arrangement so that /// [setVisible]/[setCollapsed] have a state entry to mutate. Future _bootExtension(KernelFixture f) async { - f.services.panels.registerSlot( - const SlotDefinition( - id: Slots.contextPanel, - position: SlotPosition.right, - ), - ); + f.services.panels.registerSlot(const SlotDefinition(id: Slots.contextPanel, position: SlotPosition.right)); // Seed the ARRANGEMENT with the context-panel slot (visible:false) so the // extension's setVisible/setCollapsed reveal actually round-trips — in // production the default-layout preset does this; LayoutArrangement.setVisible @@ -44,9 +39,7 @@ Future _bootExtension(KernelFixture f) async { const LayoutPresetContribution( id: 'test.preset', displayName: 'test', - slots: [ - LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, visible: false), - ], + slots: [LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, visible: false)], ), ); f.services.extensions.register(DecisionsExtension()); @@ -63,19 +56,19 @@ void _select(KernelFixture f, String id) { // --------------------------------------------------------------------------- IpcResponse _decisionResponse(String id, {String? filePath}) => IpcResponse.ok( - id: '', - data: { - 'id': id, - 'title': 'Decision $id', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'active', - 'date': '2026-01-01', - 'body': 'Body of $id.', - 'refs': [], - 'file_path': filePath ?? 'governance/decisions/architecture.md', - }, - ); + id: '', + data: { + 'id': id, + 'title': 'Decision $id', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'active', + 'date': '2026-01-01', + 'body': 'Body of $id.', + 'refs': [], + 'file_path': filePath ?? 'governance/decisions/architecture.md', + }, +); // --------------------------------------------------------------------------- // Extension-level unit tests (no Flutter widgets, no IPC) @@ -181,17 +174,15 @@ void main() { // After deactivation contributions are removed, so no decisions.detail // tab at all — but the panel activation path must not fire either. - f.services.panels.registerSlot( - const SlotDefinition( - id: Slots.contextPanel, - position: SlotPosition.right, - ), - ); + f.services.panels.registerSlot(const SlotDefinition(id: Slots.contextPanel, position: SlotPosition.right)); f.services.messages.publish('builtin.decisions', 'selection', {'id': 'D-99'}); await Future.delayed(Duration.zero); - expect(f.services.panels.activeTabIn(Slots.contextPanel), isNot('decisions.detail'), - reason: 'deactivated extension must not respond to selection messages'); + expect( + f.services.panels.activeTabIn(Slots.contextPanel), + isNot('decisions.detail'), + reason: 'deactivated extension must not respond to selection messages', + ); }); }); @@ -219,9 +210,7 @@ void main() { tester.view.resetPhysicalSize(); tester.view.resetDevicePixelRatio(); }); - await tester.pumpWidget( - harness(f, DecisionDetailView(initialId: initialId)), - ); + await tester.pumpWidget(harness(f, DecisionDetailView(initialId: initialId))); await pumpAsync(tester); } @@ -334,15 +323,12 @@ void main() { testWidgets('IPC error leaves _decision null — shows placeholder', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (_) async => IpcResponse.err( - id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'read failed', - ), - )); + 'pql.decisions.read', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'read failed'), + ), + ); await pumpView(tester, initialId: 'D-99'); @@ -352,21 +338,22 @@ void main() { testWidgets('decision with status open shows status badge', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'Q-1', - 'title': 'Open question', - 'type': 'question', - 'domain': 'architecture', - 'status': 'open', - 'date': '2026-01-01', - 'body': '', - 'refs': [], - 'file_path': 'governance/questions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'Q-1', + 'title': 'Open question', + 'type': 'question', + 'domain': 'architecture', + 'status': 'open', + 'date': '2026-01-01', + 'body': '', + 'refs': [], + 'file_path': 'governance/questions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'Q-1'); @@ -375,21 +362,22 @@ void main() { testWidgets('decision with status resolved shows status badge', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'Q-2', - 'title': 'Resolved question', - 'type': 'question', - 'domain': 'architecture', - 'status': 'resolved', - 'date': '2026-01-01', - 'body': '', - 'refs': [], - 'file_path': 'governance/questions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'Q-2', + 'title': 'Resolved question', + 'type': 'question', + 'domain': 'architecture', + 'status': 'resolved', + 'date': '2026-01-01', + 'body': '', + 'refs': [], + 'file_path': 'governance/questions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'Q-2'); @@ -398,21 +386,22 @@ void main() { testWidgets('decision with unknown status shows status badge in muted color', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'D-20', - 'title': 'Deprecated decision', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'deprecated', - 'date': '2026-01-01', - 'body': '', - 'refs': [], - 'file_path': 'governance/decisions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'D-20', + 'title': 'Deprecated decision', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'deprecated', + 'date': '2026-01-01', + 'body': '', + 'refs': [], + 'file_path': 'governance/decisions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'D-20'); @@ -421,23 +410,24 @@ void main() { testWidgets('decision with refs using source_id renders ref card', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'D-30', - 'title': 'Decision with source ref', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'active', - 'date': '2026-01-01', - 'body': '', - 'refs': [ - {'source_id': 'D-5', 'ref_type': 'amends'}, - ], - 'file_path': 'governance/decisions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'D-30', + 'title': 'Decision with source ref', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'active', + 'date': '2026-01-01', + 'body': '', + 'refs': [ + {'source_id': 'D-5', 'ref_type': 'amends'}, + ], + 'file_path': 'governance/decisions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'D-30'); @@ -450,23 +440,24 @@ void main() { // the T-prefix routing in _navigateToRecord is only reachable via // ClideMarkdown.onRecordTap (markdown body links). f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'D-40', - 'title': 'Decision with ticket ref', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'active', - 'date': '2026-01-01', - 'body': '', - 'refs': [ - {'target_id': 'T-123', 'ref_type': 'tracked-by'}, - ], - 'file_path': 'governance/decisions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'D-40', + 'title': 'Decision with ticket ref', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'active', + 'date': '2026-01-01', + 'body': '', + 'refs': [ + {'target_id': 'T-123', 'ref_type': 'tracked-by'}, + ], + 'file_path': 'governance/decisions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'D-40'); expect(find.text('T-123'), findsOneWidget); @@ -484,21 +475,22 @@ void main() { testWidgets('decision with non-empty body renders body section', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'D-50', - 'title': 'Decision with body', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'active', - 'date': '2026-01-15', - 'body': 'This is the decision body text.', - 'refs': [], - 'file_path': 'governance/decisions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'D-50', + 'title': 'Decision with body', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'active', + 'date': '2026-01-15', + 'body': 'This is the decision body text.', + 'refs': [], + 'file_path': 'governance/decisions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'D-50'); @@ -509,20 +501,21 @@ void main() { testWidgets('decision without date omits date row', (tester) async { f.ipc.stub( - 'pql.decisions.read', - (args) async => IpcResponse.ok( - id: '', - data: { - 'id': 'D-60', - 'title': 'No date decision', - 'type': 'confirmed', - 'domain': 'architecture', - 'status': 'active', - 'body': '', - 'refs': [], - 'file_path': 'governance/decisions/architecture.md', - }, - )); + 'pql.decisions.read', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'D-60', + 'title': 'No date decision', + 'type': 'confirmed', + 'domain': 'architecture', + 'status': 'active', + 'body': '', + 'refs': [], + 'file_path': 'governance/decisions/architecture.md', + }, + ), + ); await pumpView(tester, initialId: 'D-60'); @@ -570,24 +563,14 @@ void main() { testWidgets('back disabled on initial load', (tester) async { await pumpView(tester, initialId: 'D-1'); - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && w.properties.enabled == false), findsOneWidget); }); testWidgets('back enabled after two selections', (tester) async { await pumpView(tester, initialId: 'D-1'); await open(tester, 'D-2'); - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ), - findsWidgets, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)), findsWidgets); }); testWidgets('back navigates to previous decision', (tester) async { @@ -596,9 +579,7 @@ void main() { // Title appears in pane header subtitle + body card. expect(find.text('Decision D-2'), findsWidgets); - final backBtn = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backBtn = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backBtn.first); await pumpAsync(tester); @@ -614,9 +595,7 @@ void main() { addTearDown(sub.cancel); // Go back — re-emits on 'load', NOT 'selection'. - final backBtn = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backBtn = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backBtn.first); await pumpAsync(tester); @@ -627,12 +606,7 @@ void main() { await pumpView(tester, initialId: 'D-1'); await open(tester, 'D-2'); - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false), findsOneWidget); }); testWidgets('forward navigates after back', (tester) async { @@ -640,17 +614,13 @@ void main() { await open(tester, 'D-2'); // Go back to D-1. - final backBtn = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backBtn = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backBtn.first); await pumpAsync(tester); expect(find.text('Decision D-1'), findsWidgets); // Go forward to D-2. - final fwdBtn = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true), - ); + final fwdBtn = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true)); await tester.tap(fwdBtn.first); await pumpAsync(tester); expect(find.text('Decision D-2'), findsWidgets); @@ -661,21 +631,14 @@ void main() { await open(tester, 'D-2'); // Go back to D-1. - final backBtn = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backBtn = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backBtn.first); await pumpAsync(tester); // Open D-3 — truncates D-2 forward history. await open(tester, 'D-3'); - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false), findsOneWidget); }); }); @@ -714,37 +677,23 @@ void main() { testWidgets('pin jump affordance not visible before pin set', (tester) async { await pumpView(tester, initialId: 'D-1'); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsNothing); }); testWidgets('pin current shows jump-to-pin affordance', (tester) async { await pumpView(tester, initialId: 'D-1'); - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsOneWidget); }); testWidgets('jump to pin loads the pinned decision', (tester) async { await pumpView(tester, initialId: 'D-1'); // Pin D-1. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); // Navigate to D-2. @@ -753,11 +702,7 @@ void main() { expect(find.text('Decision D-2'), findsWidgets); // Jump to pin. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Jump to pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin').first); await pumpAsync(tester); expect(find.text('Decision D-1'), findsWidgets); @@ -767,28 +712,14 @@ void main() { await pumpView(tester, initialId: 'D-1'); // Pin D-1 → the jump-to-pin button appears in the navigator. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsOneWidget); // Tapping the toggle again (now 'Unpin') clears the pin. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Unpin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Unpin').first); await pumpAsync(tester); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsNothing); }); }); @@ -818,10 +749,7 @@ void main() { testWidgets('edit pencil not visible when no decision loaded', (tester) async { await pumpView(tester); // Placeholder state — no chrome. - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), findsNothing); }); testWidgets('edit pencil fires editor.open with file_path from decision', (tester) async { @@ -838,16 +766,9 @@ void main() { await pumpView(tester, initialId: 'D-1'); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), findsOneWidget); - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Edit in editor', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor').first); await pumpAsync(tester); expect(editorOpenArgs, hasLength(1)); @@ -874,10 +795,7 @@ void main() { await pumpView(tester, initialId: 'D-70'); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), findsNothing); }); }); } diff --git a/test/builtin/decisions/decisions_view_test.dart b/test/builtin/decisions/decisions_view_test.dart index ef413257..15acb397 100644 --- a/test/builtin/decisions/decisions_view_test.dart +++ b/test/builtin/decisions/decisions_view_test.dart @@ -26,28 +26,17 @@ import '../../helpers/widget_harness.dart'; IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data); IpcResponse _err(String msg) => IpcResponse.err( - id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: msg, - ), - ); + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: msg), +); -Map _decision({ - required String id, - required String title, - String type = 'confirmed', - String domain = 'architecture', - String? status, -}) => - { - 'id': id, - 'title': title, - 'type': type, - 'domain': domain, - if (status != null) 'status': status, - }; +Map _decision({required String id, required String title, String type = 'confirmed', String domain = 'architecture', String? status}) => { + 'id': id, + 'title': title, + 'type': type, + 'domain': domain, + 'status': ?status, +}; /// Register both sync and list stubs, returning the provided list of decisions. void _stubDecisions(KernelFixture f, List> decisions) { @@ -122,11 +111,7 @@ void main() { 'pql.decisions.list', (_) async => IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: 'failed to load decisions', - ), + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'failed to load decisions'), ), ); @@ -155,9 +140,7 @@ void main() { group('DecisionsView — grouping into sections', () { testWidgets('confirmed decisions appear in CONFIRMED accordion', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Architecture choice'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Architecture choice')]); await pumpView(tester); // Accordion label renders as 'CONFIRMED · N'. @@ -168,9 +151,7 @@ void main() { }); testWidgets('question decisions appear in QUESTIONS accordion header', (tester) async { - _stubDecisions(f, [ - _decision(id: 'Q-1', title: 'Open question', type: 'question', domain: 'tooling'), - ]); + _stubDecisions(f, [_decision(id: 'Q-1', title: 'Open question', type: 'question', domain: 'tooling')]); await pumpView(tester); // The header always renders even when the section is collapsed. @@ -183,9 +164,7 @@ void main() { }); testWidgets('rejected decisions appear in REJECTED accordion header', (tester) async { - _stubDecisions(f, [ - _decision(id: 'R-1', title: 'Rejected idea', type: 'rejected', domain: 'ui'), - ]); + _stubDecisions(f, [_decision(id: 'R-1', title: 'Rejected idea', type: 'rejected', domain: 'ui')]); await pumpView(tester); expect(find.textContaining('REJECTED'), findsOneWidget); @@ -212,9 +191,7 @@ void main() { testWidgets('card with resolved status shows resolved badge (via filter)', (tester) async { // Use a confirmed decision with resolved status so the card is // visible without needing to expand the section manually. - _stubDecisions(f, [ - _decision(id: 'D-2', title: 'Resolved decision', type: 'confirmed', domain: 'arch', status: 'resolved'), - ]); + _stubDecisions(f, [_decision(id: 'D-2', title: 'Resolved decision', type: 'confirmed', domain: 'arch', status: 'resolved')]); await pumpView(tester); // CONFIRMED is pinned-expanded; card is visible. @@ -222,9 +199,7 @@ void main() { }); testWidgets('domain label is shown on card', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-5', title: 'Some D', domain: 'architecture'), - ]); + _stubDecisions(f, [_decision(id: 'D-5', title: 'Some D', domain: 'architecture')]); await pumpView(tester); expect(find.text('architecture'), findsOneWidget); @@ -233,9 +208,7 @@ void main() { group('DecisionsView — card tap publishes selection', () { testWidgets('tapping a confirmed card publishes builtin.decisions/selection', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-3', title: 'Click me'), - ]); + _stubDecisions(f, [_decision(id: 'D-3', title: 'Click me')]); await pumpView(tester); final received = []; @@ -250,9 +223,7 @@ void main() { }); testWidgets('tapping a question card publishes the correct id', (tester) async { - _stubDecisions(f, [ - _decision(id: 'Q-4', title: 'Q card', type: 'question', domain: 'tooling'), - ]); + _stubDecisions(f, [_decision(id: 'Q-4', title: 'Q card', type: 'question', domain: 'tooling')]); await pumpView(tester); // QUESTIONS section is collapsed by default — tap header to expand it. @@ -273,10 +244,7 @@ void main() { group('DecisionsView — filter', () { testWidgets('filter box narrows results by id', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Architecture choice'), - _decision(id: 'D-2', title: 'Build tool selection'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Architecture choice'), _decision(id: 'D-2', title: 'Build tool selection')]); await pumpView(tester); expect(find.text('D-1'), findsOneWidget); @@ -295,10 +263,7 @@ void main() { }); testWidgets('filter box narrows results by title', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Architecture choice'), - _decision(id: 'D-2', title: 'Build tool selection'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Architecture choice'), _decision(id: 'D-2', title: 'Build tool selection')]); await pumpView(tester); final filterBox = find.byWidgetPredicate((w) => w is EditableText); @@ -311,10 +276,7 @@ void main() { }); testWidgets('filter by domain shows matching card', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Arch', domain: 'architecture'), - _decision(id: 'D-2', title: 'Tool', domain: 'tooling'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Arch', domain: 'architecture'), _decision(id: 'D-2', title: 'Tool', domain: 'tooling')]); await pumpView(tester); final filterBox = find.byWidgetPredicate((w) => w is EditableText); @@ -327,9 +289,7 @@ void main() { }); testWidgets('filter with no matches yields empty sections', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Something'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Something')]); await pumpView(tester); final filterBox = find.byWidgetPredicate((w) => w is EditableText); @@ -344,9 +304,7 @@ void main() { group('DecisionsView — section toggle', () { testWidgets('toggling confirmed section removes confirmed from pinned', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'Arch choice'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'Arch choice')]); await pumpView(tester); // CONFIRMED is pinned by default and should be expanded (D-1 visible). @@ -362,9 +320,7 @@ void main() { }); testWidgets('toggling a non-pinned section expands it', (tester) async { - _stubDecisions(f, [ - _decision(id: 'R-1', title: 'Rejected one', type: 'rejected', domain: 'ui'), - ]); + _stubDecisions(f, [_decision(id: 'R-1', title: 'Rejected one', type: 'rejected', domain: 'ui')]); await pumpView(tester); // REJECTED starts unexpanded (not in _pinned). @@ -381,10 +337,7 @@ void main() { group('DecisionsView — focus message', () { testWidgets('receiving a focus message updates _focusedId and rebuilds', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'First'), - _decision(id: 'D-2', title: 'Second'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'First'), _decision(id: 'D-2', title: 'Second')]); await pumpView(tester); // Both cards visible. @@ -400,9 +353,7 @@ void main() { }); testWidgets('focus message with null id is ignored', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'First'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'First')]); await pumpView(tester); f.services.messages.publish('builtin.decisions', 'focus', {'id': null}); @@ -412,9 +363,7 @@ void main() { }); testWidgets('focus message with same id as already focused is ignored', (tester) async { - _stubDecisions(f, [ - _decision(id: 'D-1', title: 'First'), - ]); + _stubDecisions(f, [_decision(id: 'D-1', title: 'First')]); await pumpView(tester); f.services.messages.publish('builtin.decisions', 'focus', {'id': 'D-1'}); @@ -435,9 +384,7 @@ void main() { f.ipc.stub('pql.decisions.list', (_) async { listCallCount++; return _ok({ - 'decisions': [ - _decision(id: 'D-$listCallCount', title: 'Call $listCallCount'), - ], + 'decisions': [_decision(id: 'D-$listCallCount', title: 'Call $listCallCount')], }); }); @@ -446,9 +393,7 @@ void main() { // Tap the refresh icon button — it has tooltip 'Refresh decisions'. // Find ClideTappable widgets and tap the one with the refresh tooltip. - final refreshTappable = find.byWidgetPredicate( - (w) => w is ClideTappable && w.tooltip == 'Refresh decisions', - ); + final refreshTappable = find.byWidgetPredicate((w) => w is ClideTappable && w.tooltip == 'Refresh decisions'); await tester.tap(refreshTappable); await pumpAsync(tester); @@ -463,9 +408,7 @@ void main() { f.ipc.stub('pql.decisions.list', (_) async { listCallCount++; return _ok({ - 'decisions': [ - _decision(id: 'D-$listCallCount', title: 'Version $listCallCount'), - ], + 'decisions': [_decision(id: 'D-$listCallCount', title: 'Version $listCallCount')], }); }); @@ -473,12 +416,7 @@ void main() { expect(listCallCount, 1); // Emit a files.changed event for a decisions path. - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'decisions/architecture.md'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'decisions/architecture.md'}, ts: DateTime.now().toUtc())); await pumpAsync(tester); expect(listCallCount, greaterThanOrEqualTo(2)); @@ -495,12 +433,7 @@ void main() { await pumpView(tester); final countAfterLoad = listCallCount; - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'lib/main.dart'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'lib/main.dart'}, ts: DateTime.now().toUtc())); await pumpAsync(tester); // Count must not have incremented. diff --git a/test/builtin/default_layout/focus_exit_vim_test.dart b/test/builtin/default_layout/focus_exit_vim_test.dart index 3142b6d4..a2cb5fa9 100644 --- a/test/builtin/default_layout/focus_exit_vim_test.dart +++ b/test/builtin/default_layout/focus_exit_vim_test.dart @@ -37,13 +37,16 @@ void main() { // Real layering: vim.yaml preset (low) under a contributions layer // carrying the extension's actual focusMode.exit escape binding. final preset = KeymapLayer.fromYaml(File('assets/keymaps/vim.yaml').readAsStringSync()); - final contributions = KeymapLayer(name: 'contributions', bindings: [ - KeymapBinding.chord( - KeyChord.parse(exitCmd.defaultBinding!), - intent: InvokeCommandIntent(exitCmd.command), - when: WhenExpr.tryParse(exitCmd.bindingWhen), - ), - ]); + final contributions = KeymapLayer( + name: 'contributions', + bindings: [ + KeymapBinding.chord( + KeyChord.parse(exitCmd.defaultBinding!), + intent: InvokeCommandIntent(exitCmd.command), + when: WhenExpr.tryParse(exitCmd.bindingWhen), + ), + ], + ); // Keymap flattens layers in reverse, so contributions outrank preset — // matching KeymapService._rebuildActive's ordering. km = Keymap([preset, contributions]); diff --git a/test/builtin/default_layout/widget_test.dart b/test/builtin/default_layout/widget_test.dart index 1b110ece..d9c2069b 100644 --- a/test/builtin/default_layout/widget_test.dart +++ b/test/builtin/default_layout/widget_test.dart @@ -40,10 +40,7 @@ void main() { test('declares a layout preset contribution', () { final ext = DefaultLayoutExtension(); - expect( - ext.contributions.whereType(), - hasLength(1), - ); + expect(ext.contributions.whereType(), hasLength(1)); }); test('all commands return not-activated errors before activate', () async { diff --git a/test/builtin/diff/diff_controller_test.dart b/test/builtin/diff/diff_controller_test.dart index 02490746..b955ebb0 100644 --- a/test/builtin/diff/diff_controller_test.dart +++ b/test/builtin/diff/diff_controller_test.dart @@ -81,7 +81,13 @@ void main() { }); test('a git.diff error surfaces on the controller', () async { - ipc.stub('git.diff', (_) async => IpcResponse.err(id: '', error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'))); + ipc.stub( + 'git.diff', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), + ), + ); await c.load(); expect(c.error, 'boom'); }); diff --git a/test/builtin/diff/diff_view_test.dart b/test/builtin/diff/diff_view_test.dart index 390cdc1b..6a689fea 100644 --- a/test/builtin/diff/diff_view_test.dart +++ b/test/builtin/diff/diff_view_test.dart @@ -19,22 +19,22 @@ Map _file(String path) => {'path': path, 'hunks': const []}; /// A file with rename metadata and a hunk carrying one of each line kind, so /// the view exercises `_FileDiff` meta, `_HunkView`, and `_DiffLineRow`. Map _richFile() => { - 'path': 'lib/c.dart', - 'renamed': true, - 'oldPath': 'lib/old.dart', - 'additions': 2, - 'removals': 1, - 'hunks': [ - { - 'header': '@@ -1,2 +1,3 @@', - 'lines': [ - {'kind': 'context', 'text': 'kept', 'oldLineNo': 1, 'newLineNo': 1}, - {'kind': 'addition', 'text': 'new line', 'newLineNo': 2}, - {'kind': 'removal', 'text': 'gone line', 'oldLineNo': 2}, - ], - }, + 'path': 'lib/c.dart', + 'renamed': true, + 'oldPath': 'lib/old.dart', + 'additions': 2, + 'removals': 1, + 'hunks': [ + { + 'header': '@@ -1,2 +1,3 @@', + 'lines': [ + {'kind': 'context', 'text': 'kept', 'oldLineNo': 1, 'newLineNo': 1}, + {'kind': 'addition', 'text': 'new line', 'newLineNo': 2}, + {'kind': 'removal', 'text': 'gone line', 'oldLineNo': 2}, ], - }; + }, + ], +}; bool _textIs(Object? w, String s) => w is ClideText && w.data == s; bool _textHas(Object? w, String s) => w is ClideText && w.data.contains(s); @@ -52,10 +52,11 @@ void main() { bus = DaemonBus(); ipc = FakeDaemonClient(log: Logger(), events: bus); ipc.stub( - 'git.diff', - (_) async => _ok({ - 'diffs': [_file('lib/a.dart'), _file('lib/b.dart')] - })); + 'git.diff', + (_) async => _ok({ + 'diffs': [_file('lib/a.dart'), _file('lib/b.dart')], + }), + ); c = DiffController(ipc: ipc, events: bus); }); @@ -94,10 +95,11 @@ void main() { testWidgets('renders hunk header, each diff line kind, and rename meta', (tester) async { ipc.stub( - 'git.diff', - (_) async => _ok({ - 'diffs': [_richFile()] - })); + 'git.diff', + (_) async => _ok({ + 'diffs': [_richFile()], + }), + ); await c.load(); await tester.pumpWidget(harness(f, DiffView(controller: c))); await tester.pumpAndSettle(); @@ -111,13 +113,14 @@ void main() { testWidgets('renders new/deleted/binary metadata and skips hunks for binary', (tester) async { ipc.stub( - 'git.diff', - (_) async => _ok({ - 'diffs': [ - {'path': 'img.png', 'new': true, 'binary': true, 'hunks': const []}, - {'path': 'gone.txt', 'deleted': true, 'hunks': const []}, - ] - })); + 'git.diff', + (_) async => _ok({ + 'diffs': [ + {'path': 'img.png', 'new': true, 'binary': true, 'hunks': const []}, + {'path': 'gone.txt', 'deleted': true, 'hunks': const []}, + ], + }), + ); await c.load(); await tester.pumpWidget(harness(f, DiffView(controller: c))); await tester.pumpAndSettle(); @@ -129,11 +132,12 @@ void main() { testWidgets('renders the error message when git.diff fails', (tester) async { ipc.stub( - 'git.diff', - (_) async => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), - )); + 'git.diff', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), + ), + ); await c.load(); await tester.pumpWidget(harness(f, DiffView(controller: c))); await tester.pumpAndSettle(); diff --git a/test/builtin/editor/editor_controller_test.dart b/test/builtin/editor/editor_controller_test.dart index 3bed4ab5..f2893405 100644 --- a/test/builtin/editor/editor_controller_test.dart +++ b/test/builtin/editor/editor_controller_test.dart @@ -15,12 +15,12 @@ IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data) Map _buf(String id, String path, {bool dirty = false}) => {'id': id, 'path': path, 'dirty': dirty}; Map _read(String id, String path, String content, {bool dirty = false}) => { - 'id': id, - 'path': path, - 'content': content, - 'selection': {'start': 0, 'end': 0}, - 'dirty': dirty, - }; + 'id': id, + 'path': path, + 'content': content, + 'selection': {'start': 0, 'end': 0}, + 'dirty': dirty, +}; void emitEditor(DaemonBus bus, String kind, Map data) { bus.emit(DaemonEvent(subsystem: 'editor', kind: kind, data: data, ts: DateTime.now().toUtc())); @@ -45,15 +45,17 @@ void main() { group('hydrate', () { test('populates the open-buffer list and loads the active buffer', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'lib/a.dart'), _buf('b_2', 'lib/b.dart', dirty: true)] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'lib/a.dart'), _buf('b_2', 'lib/b.dart', dirty: true)], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (a) async => _ok(_read('b_1', 'lib/a.dart', 'hello'))); await c.hydrate(); @@ -66,10 +68,11 @@ void main() { test('with no active buffer leaves the list but no active content', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'lib/a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'lib/a.dart')], + }), + ); ipc.stub('editor.active', (_) async => _ok(const {})); // no `active` key await c.hydrate(); @@ -93,15 +96,17 @@ void main() { test('activate is a no-op when the id is already active', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x'))); await c.hydrate(); @@ -129,15 +134,17 @@ void main() { test('editor.opened refreshes the list and loads the new active buffer', () async { var listVersion = 1; ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': listVersion == 1 ? [_buf('b_1', 'a.dart')] : [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': listVersion == 1 ? [_buf('b_1', 'a.dart')] : [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (a) async { final id = a['id'] as String; return _ok(_read(id, id == 'b_1' ? 'a.dart' : 'b.dart', 'content-$id')); @@ -157,15 +164,17 @@ void main() { test('editor.closed refreshes the list and clears active when it was active', () async { var listVersion = 1; ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': listVersion == 1 ? [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')] : [_buf('b_2', 'b.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': listVersion == 1 ? [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')] : [_buf('b_2', 'b.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (a) async => _ok(_read(a['id'] as String, 'a.dart', 'x'))); await c.hydrate(); expect(c.buffers, hasLength(2)); @@ -180,15 +189,17 @@ void main() { test('editor.saved clears the dirty marker on the buffer', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart', dirty: true)] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart', dirty: true)], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x', dirty: true))); await c.hydrate(); expect(c.buffers.single.dirty, isTrue); @@ -202,15 +213,17 @@ void main() { test('editor.edited marks the right buffer dirty, leaving siblings alone', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (a) async => _ok(_read(a['id'] as String, 'a.dart', 'x'))); await c.hydrate(); expect(c.buffers.every((b) => !b.dirty), isTrue); @@ -226,15 +239,17 @@ void main() { test('editor.active-changed to a different buffer loads its content', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart'), _buf('b_2', 'b.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (a) async { final id = a['id'] as String; return _ok(_read(id, id == 'b_1' ? 'a.dart' : 'b.dart', 'body-$id')); @@ -253,15 +268,17 @@ void main() { group('local edits', () { test('pushLocalEdit marks the active buffer dirty and mirrors to IPC', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x'))); await c.hydrate(); @@ -281,15 +298,17 @@ void main() { test('save() issues editor.save for the active buffer', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x'))); await c.hydrate(); @@ -316,15 +335,17 @@ void main() { group('edge cases', () { test('editor.active-changed to null clears the active buffer', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x'))); await c.hydrate(); expect(c.activeId, 'b_1'); @@ -358,15 +379,17 @@ void main() { test('editor.opened with no id clears the active buffer', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'x'))); await c.hydrate(); expect(c.activeId, 'b_1'); @@ -378,15 +401,17 @@ void main() { test('a failing editor.read surfaces the error', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub( 'editor.read', (_) async => IpcResponse.err( @@ -400,15 +425,17 @@ void main() { test('our own edit echo (editor.edited) is suppressed once, not reloaded', () async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(_read('b_1', 'a.dart', 'original'))); ipc.stub('editor.set-content', (_) async => _ok(const {})); await c.hydrate(); @@ -433,15 +460,17 @@ void main() { group('editor settings (T-29)', () { Future hydrateWith(Map read) async { ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', 'lib/a.dart')] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', 'lib/a.dart')], + }), + ); ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); ipc.stub('editor.read', (_) async => _ok(read)); await c.hydrate(); } diff --git a/test/builtin/editor/editor_view_test.dart b/test/builtin/editor/editor_view_test.dart index b1c8f717..5ef7da9c 100644 --- a/test/builtin/editor/editor_view_test.dart +++ b/test/builtin/editor/editor_view_test.dart @@ -21,13 +21,13 @@ IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data) Map _buf(String id, String path, {bool dirty = false}) => {'id': id, 'path': path, 'dirty': dirty}; Map _read(String id, String path, {Map? settings}) => { - 'id': id, - 'path': path, - 'content': 'content of $path', - 'selection': {'start': 0, 'end': 0}, - 'dirty': false, - if (settings != null) 'editorSettings': settings, - }; + 'id': id, + 'path': path, + 'content': 'content of $path', + 'selection': {'start': 0, 'end': 0}, + 'dirty': false, + 'editorSettings': ?settings, +}; Finder _ruler() => find.byWidgetPredicate((w) => w is CustomPaint && w.painter?.runtimeType.toString() == '_RulerPainter'); @@ -40,12 +40,13 @@ void main() { void stubBuffers(List> buffers, {String? active}) { f.ipc.stub('editor.list', (_) async => _ok({'buffers': buffers})); f.ipc.stub( - 'editor.active', - (_) async => active == null - ? _ok(const {}) - : _ok({ - 'active': {'id': active} - })); + 'editor.active', + (_) async => active == null + ? _ok(const {}) + : _ok({ + 'active': {'id': active}, + }), + ); f.ipc.stub('editor.read', (a) async { final id = a['id'] as String; final b = buffers.firstWhere((b) => b['id'] == id); @@ -147,15 +148,17 @@ void main() { void stubOne(String path, {Map? settings}) { f.ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', path)] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', path)], + }), + ); f.ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); f.ipc.stub('editor.read', (_) async => _ok(_read('b_1', path, settings: settings))); } @@ -178,25 +181,28 @@ void main() { void stubReadContent(String path, String content, Map settings) { f.ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [_buf('b_1', path)] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [_buf('b_1', path)], + }), + ); f.ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); f.ipc.stub( - 'editor.read', - (_) async => _ok({ - 'id': 'b_1', - 'path': path, - 'content': content, - 'selection': {'start': 0, 'end': 0}, - 'dirty': false, - 'editorSettings': settings - })); + 'editor.read', + (_) async => _ok({ + 'id': 'b_1', + 'path': path, + 'content': content, + 'selection': {'start': 0, 'end': 0}, + 'dirty': false, + 'editorSettings': settings, + }), + ); f.ipc.stub('editor.set-content', (_) async => _ok(const {})); } diff --git a/test/builtin/editor/syntax_text_controller_test.dart b/test/builtin/editor/syntax_text_controller_test.dart index 20901b09..9cf52def 100644 --- a/test/builtin/editor/syntax_text_controller_test.dart +++ b/test/builtin/editor/syntax_text_controller_test.dart @@ -41,26 +41,30 @@ void main() { /// Pump a themed context, run [body] with a controller already /// given tokens from the active theme. - Future withController( - WidgetTester tester, - SyntaxTextController c, - Future Function(BuildContext ctx) body, - ) async { + Future withController(WidgetTester tester, SyntaxTextController c, Future Function(BuildContext ctx) body) async { late BuildContext ctx; - await tester.pumpWidget(harness(f, Builder(builder: (context) { - ctx = context; - return const SizedBox(); - }))); + await tester.pumpWidget( + harness( + f, + Builder( + builder: (context) { + ctx = context; + return const SizedBox(); + }, + ), + ), + ); c.tokens = ClideTheme.of(ctx).surface; await body(ctx); } testWidgets('renders highlighted spans as styled TextSpan children', (tester) async { final c = SyntaxTextController( - syntax: _FakeSyntax(const [ - SyntaxSpan(start: 0, end: 5, role: 'keyword'), // "class" - SyntaxSpan(start: 6, end: 9, role: 'type'), // "Foo" - ])); + syntax: _FakeSyntax(const [ + SyntaxSpan(start: 0, end: 5, role: 'keyword'), // "class" + SyntaxSpan(start: 6, end: 9, role: 'type'), // "Foo" + ]), + ); await withController(tester, c, (ctx) async { c.text = 'class Foo {}'; c.updatePath('a.dart'); @@ -76,9 +80,10 @@ void main() { // '😀' is a surrogate pair (4 UTF-8 bytes). A span after it must // still land on the right character offset. final c = SyntaxTextController( - syntax: _FakeSyntax(const [ - SyntaxSpan(start: 5, end: 8, role: 'type'), // "Foo" after "😀 " - ])); + syntax: _FakeSyntax(const [ + SyntaxSpan(start: 5, end: 8, role: 'type'), // "Foo" after "😀 " + ]), + ); await withController(tester, c, (ctx) async { c.text = '😀 Foo'; c.updatePath('a.dart'); diff --git a/test/builtin/editor/vim_edit_ops_test.dart b/test/builtin/editor/vim_edit_ops_test.dart index ba77b1cc..a1cc122f 100644 --- a/test/builtin/editor/vim_edit_ops_test.dart +++ b/test/builtin/editor/vim_edit_ops_test.dart @@ -8,9 +8,9 @@ import 'package:flutter/services.dart' show TextEditingValue, TextSelection; import 'package:flutter_test/flutter_test.dart'; TextEditingValue _tev(String text, int caret, {int? anchor}) => TextEditingValue( - text: text, - selection: TextSelection(baseOffset: anchor ?? caret, extentOffset: caret), - ); + text: text, + selection: TextSelection(baseOffset: anchor ?? caret, extentOffset: caret), +); void main() { group('motions', () { diff --git a/test/builtin/editor/vim_editor_test.dart b/test/builtin/editor/vim_editor_test.dart index a1b44220..f68fdce3 100644 --- a/test/builtin/editor/vim_editor_test.dart +++ b/test/builtin/editor/vim_editor_test.dart @@ -22,26 +22,29 @@ void main() { void stubOneBuffer(String content) { f.ipc.stub( - 'editor.list', - (_) async => _ok({ - 'buffers': [ - {'id': 'b_1', 'path': 'lib/a.dart', 'dirty': false} - ] - })); + 'editor.list', + (_) async => _ok({ + 'buffers': [ + {'id': 'b_1', 'path': 'lib/a.dart', 'dirty': false}, + ], + }), + ); f.ipc.stub( - 'editor.active', - (_) async => _ok({ - 'active': {'id': 'b_1'} - })); + 'editor.active', + (_) async => _ok({ + 'active': {'id': 'b_1'}, + }), + ); f.ipc.stub( - 'editor.read', - (_) async => _ok({ - 'id': 'b_1', - 'path': 'lib/a.dart', - 'content': content, - 'selection': {'start': 0, 'end': 0}, - 'dirty': false, - })); + 'editor.read', + (_) async => _ok({ + 'id': 'b_1', + 'path': 'lib/a.dart', + 'content': content, + 'selection': {'start': 0, 'end': 0}, + 'dirty': false, + }), + ); } Future pumpEditor(WidgetTester tester) async { diff --git a/test/builtin/files/file_tree_controller_test.dart b/test/builtin/files/file_tree_controller_test.dart index 92c695cf..da0569fa 100644 --- a/test/builtin/files/file_tree_controller_test.dart +++ b/test/builtin/files/file_tree_controller_test.dart @@ -19,13 +19,9 @@ import '../../helpers/kernel_fixture.dart'; IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data); IpcResponse _err(String msg) => IpcResponse.err( - id: '', - error: IpcError( - code: IpcExitCode.toolError, - kind: IpcErrorKind.toolError, - message: msg, - ), - ); + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: msg), +); Map _fileEntry({ required String name, @@ -34,15 +30,7 @@ Map _fileEntry({ bool isSymlink = false, int? sizeBytes, int? modifiedMs, -}) => - { - 'name': name, - 'path': path, - 'isDirectory': isDirectory, - 'isSymlink': isSymlink, - 'sizeBytes': sizeBytes, - 'modifiedMs': modifiedMs, - }; +}) => {'name': name, 'path': path, 'isDirectory': isDirectory, 'isSymlink': isSymlink, 'sizeBytes': sizeBytes, 'modifiedMs': modifiedMs}; // --------------------------------------------------------------------------- // Tests @@ -75,9 +63,7 @@ void main() { f.ipc.stub( 'files.ls', (_) async => _ok({ - 'entries': [ - _fileEntry(name: 'main.dart', path: 'lib/main.dart'), - ], + 'entries': [_fileEntry(name: 'main.dart', path: 'lib/main.dart')], }), ); @@ -148,17 +134,12 @@ void main() { final path = args['path'] as String? ?? ''; if (path == '') { return _ok({ - 'entries': [ - _fileEntry(name: 'lib', path: 'lib', isDirectory: true), - _fileEntry(name: 'main.dart', path: 'main.dart'), - ], + 'entries': [_fileEntry(name: 'lib', path: 'lib', isDirectory: true), _fileEntry(name: 'main.dart', path: 'main.dart')], }); } if (path == 'lib') { return _ok({ - 'entries': [ - _fileEntry(name: 'app.dart', path: 'lib/app.dart'), - ], + 'entries': [_fileEntry(name: 'app.dart', path: 'lib/app.dart')], }); } return _ok({'entries': []}); @@ -243,17 +224,12 @@ void main() { final path = args['path'] as String? ?? ''; if (path == '') { return _ok({ - 'entries': [ - _fileEntry(name: 'lib', path: 'lib', isDirectory: true), - _fileEntry(name: 'README.md', path: 'README.md'), - ], + 'entries': [_fileEntry(name: 'lib', path: 'lib', isDirectory: true), _fileEntry(name: 'README.md', path: 'README.md')], }); } if (path == 'lib') { return _ok({ - 'entries': [ - _fileEntry(name: 'main.dart', path: 'lib/main.dart'), - ], + 'entries': [_fileEntry(name: 'main.dart', path: 'lib/main.dart')], }); } return _ok({'entries': []}); @@ -309,12 +285,7 @@ void main() { final countAfterLoad = lsCallCount; // Emit files.changed for a file at root level — parent is ''. - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'README.md'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'README.md'}, ts: DateTime.now().toUtc())); // Give the async refresh a tick. await Future.delayed(Duration.zero); @@ -333,12 +304,7 @@ void main() { final countAfterLoad = lsCallCount; // 'lib' is not in _entries yet, so its parent 'lib/src' won't be there. - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'lib/src/foo.dart'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'lib/src/foo.dart'}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); expect(lsCallCount, countAfterLoad); @@ -355,12 +321,7 @@ void main() { await c.load(); final countAfterLoad = lsCallCount; - f.services.events.emit(DaemonEvent( - subsystem: 'editor', - kind: 'files.changed', - data: {'path': 'README.md'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'editor', kind: 'files.changed', data: {'path': 'README.md'}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); expect(lsCallCount, countAfterLoad); @@ -377,12 +338,7 @@ void main() { await c.load(); final countAfterLoad = lsCallCount; - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.opened', - data: {'path': 'README.md'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.opened', data: {'path': 'README.md'}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); expect(lsCallCount, countAfterLoad); @@ -395,12 +351,7 @@ void main() { await c.load(); final countAfterLoad = 1; - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'pubspec.yaml'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'pubspec.yaml'}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); // Root '' is in _entries, so reload fires. @@ -412,12 +363,7 @@ void main() { final c = makeCtrl(); await c.load(); - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': null}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': null}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); // No crash — just checking the null-path guard. }); @@ -448,12 +394,7 @@ void main() { // Dispose and then emit an event — must not crash. c.dispose(); ctrl = null; // prevent tearDown from double-disposing - f.services.events.emit(DaemonEvent( - subsystem: 'files', - kind: 'files.changed', - data: {'path': 'README.md'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'files', kind: 'files.changed', data: {'path': 'README.md'}, ts: DateTime.now().toUtc())); await Future.delayed(Duration.zero); // Test passes if no exception. }); diff --git a/test/builtin/files/file_tree_view_md_test.dart b/test/builtin/files/file_tree_view_md_test.dart index 22326226..ca551895 100644 --- a/test/builtin/files/file_tree_view_md_test.dart +++ b/test/builtin/files/file_tree_view_md_test.dart @@ -25,24 +25,13 @@ IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data) /// /// The [root] entry list populates the workspace root directory so the tree /// renders at least one file row. -void _stubTree( - KernelFixture f, { - required String rootPath, - required List> entries, -}) { +void _stubTree(KernelFixture f, {required String rootPath, required List> entries}) { f.ipc.stub('files.root', (_) async => _ok({'path': rootPath})); f.ipc.stub('files.watch', (_) async => _ok(const {})); f.ipc.stub('files.ls', (args) async => _ok({'entries': entries})); } -Map _file(String name, String path) => { - 'name': name, - 'path': path, - 'isDirectory': false, - 'isSymlink': false, - 'sizeBytes': 0, - 'modifiedMs': 0, - }; +Map _file(String name, String path) => {'name': name, 'path': path, 'isDirectory': false, 'isSymlink': false, 'sizeBytes': 0, 'modifiedMs': 0}; // --------------------------------------------------------------------------- // Tests @@ -108,14 +97,7 @@ void main() { testWidgets('clicking a .md in the filtered view publishes to builtin.markdown selection', (tester) async { const mdPath = 'governance/decisions/architecture.md'; - _stubTree( - f, - rootPath: '/repo', - entries: [ - _file('architecture.md', mdPath), - _file('tooling.md', 'governance/decisions/tooling.md'), - ], - ); + _stubTree(f, rootPath: '/repo', entries: [_file('architecture.md', mdPath), _file('tooling.md', 'governance/decisions/tooling.md')]); final published = []; final sub = f.services.messages.subscribe(publisher: 'builtin.markdown', channel: 'selection').listen(published.add); diff --git a/test/builtin/git/git_controller_test.dart b/test/builtin/git/git_controller_test.dart index 91ed5efe..27a09403 100644 --- a/test/builtin/git/git_controller_test.dart +++ b/test/builtin/git/git_controller_test.dart @@ -28,9 +28,9 @@ void main() { IpcResponse ok([Map data = const {}]) => IpcResponse.ok(id: '', data: data); IpcResponse err(String message) => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message), - ); + id: '', + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: message), + ); // Let the broadcast streams (bus / events) deliver. Future settle() => Future.delayed(Duration.zero); @@ -38,27 +38,28 @@ void main() { group('load + status parsing', () { test('hydrates branch / counts / file lists from git.status', () async { f.ipc.stub( - 'git.status', - (_) async => ok({ - 'branch': 'main', - 'upstream': 'origin/main', - 'ahead': 2, - 'behind': 1, - 'clean': false, - 'hasConflicts': true, - 'staged': [ - {'path': 'a.dart'}, - ], - 'unstaged': [ - {'path': 'b.dart'}, - ], - 'untracked': [ - {'path': 'c.dart'}, - ], - 'conflicted': [ - {'path': 'd.dart'}, - ], - })); + 'git.status', + (_) async => ok({ + 'branch': 'main', + 'upstream': 'origin/main', + 'ahead': 2, + 'behind': 1, + 'clean': false, + 'hasConflicts': true, + 'staged': [ + {'path': 'a.dart'}, + ], + 'unstaged': [ + {'path': 'b.dart'}, + ], + 'untracked': [ + {'path': 'c.dart'}, + ], + 'conflicted': [ + {'path': 'd.dart'}, + ], + }), + ); final c = controller(); await c.load(); expect(c.loading, isFalse); diff --git a/test/builtin/ipc_status/widget_test.dart b/test/builtin/ipc_status/widget_test.dart index c241ff8b..59a7caf8 100644 --- a/test/builtin/ipc_status/widget_test.dart +++ b/test/builtin/ipc_status/widget_test.dart @@ -39,12 +39,7 @@ void main() { }); testWidgets('all-tools-resolved shows a single "application ok" chip', (tester) async { - f.services.toolchain.applyResolved(const ResolvedPaths( - git: '/usr/bin/git', - pql: '/usr/bin/pql', - tmux: '/usr/bin/tmux', - shell: '/bin/bash', - )); + f.services.toolchain.applyResolved(const ResolvedPaths(git: '/usr/bin/git', pql: '/usr/bin/pql', tmux: '/usr/bin/tmux', shell: '/bin/bash')); await tester.pumpWidget(harness(f, const ToolStatusItem())); await tester.pumpAndSettle(); expect(find.text('application ok'), findsOneWidget); @@ -52,10 +47,7 @@ void main() { testWidgets('missing tools render a warning chip per missing tool', (tester) async { // git + tmux missing, pql resolved. - f.services.toolchain.applyResolved(const ResolvedPaths( - pql: '/usr/bin/pql', - shell: '/bin/bash', - )); + f.services.toolchain.applyResolved(const ResolvedPaths(pql: '/usr/bin/pql', shell: '/bin/bash')); await tester.pumpWidget(harness(f, const ToolStatusItem())); await tester.pumpAndSettle(); expect(find.text('git not found'), findsOneWidget); @@ -69,13 +61,17 @@ void main() { final item = f.services.panels.contributionsFor(Slots.statusbar).whereType().first; // Pump a Builder so we have a real BuildContext to hand to .build. late Widget produced; - await tester.pumpWidget(harness( - f, - Builder(builder: (ctx) { - produced = item.build(ctx); - return const SizedBox.shrink(); - }), - )); + await tester.pumpWidget( + harness( + f, + Builder( + builder: (ctx) { + produced = item.build(ctx); + return const SizedBox.shrink(); + }, + ), + ), + ); expect(produced, isA()); }); }); diff --git a/test/builtin/markdown/markdown_viewer_test.dart b/test/builtin/markdown/markdown_viewer_test.dart index 08d082de..e38ad4a0 100644 --- a/test/builtin/markdown/markdown_viewer_test.dart +++ b/test/builtin/markdown/markdown_viewer_test.dart @@ -25,7 +25,10 @@ IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data) void _stubRead(KernelFixture f, String path, String content) { f.ipc.stub('files.read', (args) async { if ((args['path'] as String?) == path) return _ok({'content': content}); - return IpcResponse.err(id: '', error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'not found')); + return IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'not found'), + ); }); } @@ -34,7 +37,10 @@ void _stubReadMap(KernelFixture f, Map paths) { f.ipc.stub('files.read', (args) async { final path = args['path'] as String? ?? ''; if (paths.containsKey(path)) return _ok({'content': paths[path]!}); - return IpcResponse.err(id: '', error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'not found')); + return IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'not found'), + ); }); } @@ -187,12 +193,7 @@ void main() { // Back should be disabled — the button is in a disabled state (no back entry). // We verify via the action-bar semantics label. - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && w.properties.enabled == false), findsOneWidget); }); testWidgets('back enabled after loading two files', (tester) async { @@ -202,12 +203,7 @@ void main() { await loadFile(tester, f, 'b.md'); // Back should be enabled. - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ), - findsWidgets, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)), findsWidgets); }); testWidgets('back navigates to previous file', (tester) async { @@ -220,9 +216,7 @@ void main() { expect(find.text('b.md'), findsOneWidget); // Tap Back. - final backButton = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backButton = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backButton.first); await pumpAsync(tester); @@ -236,12 +230,7 @@ void main() { await loadFile(tester, f, 'a.md'); await loadFile(tester, f, 'b.md'); - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false), findsOneWidget); }); testWidgets('forward enabled after going back', (tester) async { @@ -251,19 +240,12 @@ void main() { await loadFile(tester, f, 'b.md'); // Go back. - final backButton = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backButton = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backButton.first); await pumpAsync(tester); // Forward should now be enabled. - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true), - ), - findsWidgets, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true)), findsWidgets); }); testWidgets('forward navigates to next file after back', (tester) async { @@ -273,17 +255,13 @@ void main() { await loadFile(tester, f, 'b.md'); // Go back to a.md. - final backButton = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backButton = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backButton.first); await pumpAsync(tester); expect(find.text('a.md'), findsOneWidget); // Go forward to b.md. - final fwdButton = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true), - ); + final fwdButton = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && (w.properties.enabled ?? true)); await tester.tap(fwdButton.first); await pumpAsync(tester); expect(find.text('b.md'), findsOneWidget); @@ -296,9 +274,7 @@ void main() { await loadFile(tester, f, 'b.md'); // Go back to a.md. - final backButton = find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true), - ); + final backButton = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Back' && (w.properties.enabled ?? true)); await tester.tap(backButton.first); await pumpAsync(tester); @@ -306,12 +282,7 @@ void main() { await loadFile(tester, f, 'c.md'); // Forward should now be disabled (b.md was truncated). - expect( - find.byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false, - ), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Forward' && w.properties.enabled == false), findsOneWidget); }); }); @@ -325,10 +296,7 @@ void main() { await pumpView(tester, f); await loadFile(tester, f, 'a.md'); - expect( - find.byWidgetPredicate((w) => w is Semantics && (w.properties.label == 'Pin' || w.properties.label == 'Unpin')), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && (w.properties.label == 'Pin' || w.properties.label == 'Unpin')), findsOneWidget); }); testWidgets('pin jump affordance not visible before pin is set', (tester) async { @@ -336,10 +304,7 @@ void main() { await pumpView(tester, f); await loadFile(tester, f, 'a.md'); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsNothing); }); testWidgets('pin current shows jump-to-pin affordance', (tester) async { @@ -348,18 +313,11 @@ void main() { await loadFile(tester, f, 'a.md'); // Tap Pin current. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); // Jump-to-pin affordance should now be visible. - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsOneWidget); }); testWidgets('jump to pin loads the pinned file', (tester) async { @@ -368,11 +326,7 @@ void main() { await loadFile(tester, f, 'a.md'); // Pin a.md. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); // Navigate to b.md. @@ -380,11 +334,7 @@ void main() { expect(find.text('b.md'), findsOneWidget); // Jump to pin. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Jump to pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin').first); await pumpAsync(tester); // Should be back at a.md. @@ -397,28 +347,14 @@ void main() { await loadFile(tester, f, 'a.md'); // Pin a.md → jump-to-pin appears. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Pin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Pin').first); await pumpAsync(tester); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsOneWidget); // Tap the toggle again (now 'Unpin') → pin cleared. - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Unpin', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Unpin').first); await pumpAsync(tester); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Jump to pin'), findsNothing); }); }); @@ -430,10 +366,7 @@ void main() { testWidgets('edit pencil not visible before a file is loaded', (tester) async { await pumpView(tester, f); // No file loaded — placeholder shown, no chrome. - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), - findsNothing, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), findsNothing); }); testWidgets('edit pencil fires editor.open with current path', (tester) async { @@ -449,16 +382,9 @@ void main() { await pumpView(tester, f); await loadFile(tester, f, path); - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor'), findsOneWidget); - await tester.tap(find - .byWidgetPredicate( - (w) => w is Semantics && w.properties.label == 'Edit in editor', - ) - .first); + await tester.tap(find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Edit in editor').first); await pumpAsync(tester); expect(editorOpenArgs, hasLength(1)); diff --git a/test/builtin/menubar/file_actions_test.dart b/test/builtin/menubar/file_actions_test.dart index 378a51fd..a770ed3e 100644 --- a/test/builtin/menubar/file_actions_test.dart +++ b/test/builtin/menubar/file_actions_test.dart @@ -45,25 +45,22 @@ void main() { }); Widget harness(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(), - child: Align(alignment: Alignment.topLeft, child: child), - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(), + child: Align(alignment: Alignment.topLeft, child: child), ), - ); + ), + ), + ); testWidgets('OpenFolderDialog submits the typed path via onOpen', (tester) async { String? opened; - await tester.pumpWidget(harness(OpenFolderDialog( - onOpen: (p) async => opened = p, - onCancel: () {}, - ))); + await tester.pumpWidget(harness(OpenFolderDialog(onOpen: (p) async => opened = p, onCancel: () {}))); await tester.enterText(find.byType(EditableText), '/some/repo'); await tester.tap(find.text('Open')); await tester.pump(); @@ -71,10 +68,7 @@ void main() { }); testWidgets('OpenFolderDialog surfaces an error when onOpen throws', (tester) async { - await tester.pumpWidget(harness(OpenFolderDialog( - onOpen: (_) async => throw StateError('not a repo'), - onCancel: () {}, - ))); + await tester.pumpWidget(harness(OpenFolderDialog(onOpen: (_) async => throw StateError('not a repo'), onCancel: () {}))); await tester.enterText(find.byType(EditableText), '/bad'); await tester.tap(find.text('Open')); await tester.pump(); diff --git a/test/builtin/menubar/menu_bar_test.dart b/test/builtin/menubar/menu_bar_test.dart index 5710aa66..ae228f3c 100644 --- a/test/builtin/menubar/menu_bar_test.dart +++ b/test/builtin/menubar/menu_bar_test.dart @@ -22,12 +22,14 @@ void main() { f.services.extensions.register(MenuBarExtension(services: f.services)); await f.services.extensions.activateAll(); // A registered View command so the View menu has an enabled item. - f.services.commands.register(CommandContribution( - id: 'view.zoomIn', - command: 'view.zoomIn', - title: 'View: Zoom In', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'view.zoomIn', + command: 'view.zoomIn', + title: 'View: Zoom In', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); controller = MenuBarController(); }); @@ -37,32 +39,37 @@ void main() { }); Widget harness() => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: MediaQuery( - data: const MediaQueryData(), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 900, - height: 600, - child: DialogHost( - router: f.services.dialog, - child: Overlay( - initialEntries: [ - OverlayEntry(builder: (_) => Align(alignment: Alignment.topLeft, child: MenuBar(controller: controller))), - ], + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: MediaQuery( + data: const MediaQueryData(), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 900, + height: 600, + child: DialogHost( + router: f.services.dialog, + child: Overlay( + initialEntries: [ + OverlayEntry( + builder: (_) => Align( + alignment: Alignment.topLeft, + child: MenuBar(controller: controller), + ), ), - ), + ], ), ), ), ), ), - ); + ), + ), + ); Future openMenu(WidgetTester tester, String title) async { await tester.tap(find.text(title)); diff --git a/test/builtin/menubar/menu_model_test.dart b/test/builtin/menubar/menu_model_test.dart index 2c882f48..0ac23947 100644 --- a/test/builtin/menubar/menu_model_test.dart +++ b/test/builtin/menubar/menu_model_test.dart @@ -17,24 +17,30 @@ void main() { tearDown(() => f.dispose()); void cmd(String id, {String? title, String? binding}) { - f.services.commands.register(CommandContribution( - id: id, - command: id, - title: title, - defaultBinding: binding, - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: id, + command: id, + title: title, + defaultBinding: binding, + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); } group('resolveMenus', () { test('curated order: strips "Category:" titles, separators pass through, defaultBinding shows', () { cmd('a.one', title: 'A: One', binding: 'ctrl+1'); final tree = [ - TopMenu(title: 'A', mnemonic: 0, nodes: const [ - MenuCommandItem('a.one'), - MenuSeparator(), - MenuCommandItem('a.missing', fallbackTitle: 'Missing'), - ]), + TopMenu( + title: 'A', + mnemonic: 0, + nodes: const [ + MenuCommandItem('a.one'), + MenuSeparator(), + MenuCommandItem('a.missing', fallbackTitle: 'Missing'), + ], + ), ]; final items = resolveMenus(tree, f.services.commands, f.services).single.items; @@ -55,11 +61,7 @@ void main() { cmd('view.beta', title: 'View: Beta'); cmd('view.alpha', title: 'View: Alpha'); final tree = [ - TopMenu(title: 'View', mnemonic: 0, nodes: const [ - MenuCommandItem('view.zoomIn'), - MenuSeparator(), - MenuAutoFill('view.'), - ]), + TopMenu(title: 'View', mnemonic: 0, nodes: const [MenuCommandItem('view.zoomIn'), MenuSeparator(), MenuAutoFill('view.')]), ]; final items = resolveMenus(tree, f.services.commands, f.services).single.items.whereType().toList(); // zoomIn (placed) first; then auto-filled Alpha, Beta sorted by title; @@ -71,7 +73,11 @@ void main() { cmd('x.cmd', title: 'X: Cmd'); List resolve(bool Function(KernelServices) when) { final tree = [ - TopMenu(title: 'X', mnemonic: 0, nodes: [MenuCommandItem('x.cmd', enabledWhen: when)]), + TopMenu( + title: 'X', + mnemonic: 0, + nodes: [MenuCommandItem('x.cmd', enabledWhen: when)], + ), ]; return resolveMenus(tree, f.services.commands, f.services).single.items.cast(); } @@ -85,12 +91,8 @@ void main() { final tree = [ TopMenu(title: 'K', mnemonic: 0, nodes: const [MenuCommandItem('k.cmd')]), ]; - final item = resolveMenus( - tree, - f.services.commands, - f.services, - bindingLabel: (id) => id == 'k.cmd' ? 'Ctrl+K' : null, - ).single.items.first as ResolvedItem; + final item = + resolveMenus(tree, f.services.commands, f.services, bindingLabel: (id) => id == 'k.cmd' ? 'Ctrl+K' : null).single.items.first as ResolvedItem; expect(item.keybinding, 'Ctrl+K'); }); }); diff --git a/test/builtin/output/output_view_test.dart b/test/builtin/output/output_view_test.dart index 446487b3..a922ead8 100644 --- a/test/builtin/output/output_view_test.dart +++ b/test/builtin/output/output_view_test.dart @@ -71,10 +71,7 @@ void main() { await tester.pumpWidget(harness(f, OutputView(ring: _seeded()))); await tester.pumpAndSettle(); expect(find.byWidgetPredicate((w) => _textIs(w, 'Level: debug')), findsOneWidget); - await tester.tap(find.ancestor( - of: find.byWidgetPredicate((w) => _textIs(w, 'Level: debug')), - matching: find.byType(GestureDetector), - )); + await tester.tap(find.ancestor(of: find.byWidgetPredicate((w) => _textIs(w, 'Level: debug')), matching: find.byType(GestureDetector))); await tester.pumpAndSettle(); expect(find.byWidgetPredicate((w) => _textIs(w, 'Level: info')), findsOneWidget); }); @@ -83,10 +80,7 @@ void main() { await tester.pumpWidget(harness(f, OutputView(ring: _seeded()))); await tester.pumpAndSettle(); expect(find.byWidgetPredicate((w) => _textIs(w, 'Source: all')), findsOneWidget); - await tester.tap(find.ancestor( - of: find.byWidgetPredicate((w) => _textIs(w, 'Source: all')), - matching: find.byType(GestureDetector), - )); + await tester.tap(find.ancestor(of: find.byWidgetPredicate((w) => _textIs(w, 'Source: all')), matching: find.byType(GestureDetector))); await tester.pumpAndSettle(); // First source alphabetically is 'extensions'. expect(find.byWidgetPredicate((w) => _textIs(w, 'Source: extensions')), findsOneWidget); @@ -99,10 +93,7 @@ void main() { await tester.pumpWidget(harness(f, OutputView(ring: ring))); await tester.pumpAndSettle(); // Cycle level debug → info, hiding the only (debug) record. - await tester.tap(find.ancestor( - of: find.byWidgetPredicate((w) => _textIs(w, 'Level: debug')), - matching: find.byType(GestureDetector), - )); + await tester.tap(find.ancestor(of: find.byWidgetPredicate((w) => _textIs(w, 'Level: debug')), matching: find.byType(GestureDetector))); await tester.pumpAndSettle(); expect(find.byWidgetPredicate((w) => _textIs(w, 'No output matches the filter.')), findsOneWidget); }); @@ -133,10 +124,7 @@ void main() { testWidgets('Clear empties the view', (tester) async { await tester.pumpWidget(harness(f, OutputView(ring: _seeded()))); await tester.pumpAndSettle(); - await tester.tap(find.ancestor( - of: find.byWidgetPredicate((w) => _textIs(w, 'Clear')), - matching: find.byType(GestureDetector), - )); + await tester.tap(find.ancestor(of: find.byWidgetPredicate((w) => _textIs(w, 'Clear')), matching: find.byType(GestureDetector))); await tester.pumpAndSettle(); expect(find.byWidgetPredicate((w) => _textIs(w, 'alpha')), findsNothing); expect(find.byWidgetPredicate((w) => _textIs(w, 'No output yet.')), findsOneWidget); diff --git a/test/builtin/pql/pql_controller_test.dart b/test/builtin/pql/pql_controller_test.dart index 57ccbc3f..5916df10 100644 --- a/test/builtin/pql/pql_controller_test.dart +++ b/test/builtin/pql/pql_controller_test.dart @@ -12,9 +12,9 @@ import '../../helpers/kernel_fixture.dart'; IpcResponse _ok(Map data) => IpcResponse.ok(id: '', data: data); IpcResponse _err(String m) => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: m), - ); + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: m), +); void main() { late KernelFixture f; @@ -48,12 +48,13 @@ void main() { test('search populates ranked results', () async { f.ipc.stub( - 'pql.search', - (args) async => _ok({ - 'results': [ - {'path': 'a.md', 'score': 0.8}, - ], - })); + 'pql.search', + (args) async => _ok({ + 'results': [ + {'path': 'a.md', 'score': 0.8}, + ], + }), + ); await c.search('term'); expect(c.results.single['path'], 'a.md'); expect(c.error, isNull); @@ -78,12 +79,13 @@ void main() { test('runQuery populates rows; error surfaces', () async { f.ipc.stub( - 'pql.query', - (args) async => _ok({ - 'results': [ - {'name': 'T-1'}, - ], - })); + 'pql.query', + (args) async => _ok({ + 'results': [ + {'name': 'T-1'}, + ], + }), + ); await c.runQuery("type = 'ticket'"); expect(c.results.single['name'], 'T-1'); @@ -95,12 +97,13 @@ void main() { test('loadMarkdownFiles populates + errors', () async { f.ipc.stub( - 'pql.files', - (args) async => _ok({ - 'files': [ - {'path': 'docs/x.md'}, - ], - })); + 'pql.files', + (args) async => _ok({ + 'files': [ + {'path': 'docs/x.md'}, + ], + }), + ); await c.loadMarkdownFiles(); expect(c.results.single['path'], 'docs/x.md'); @@ -126,12 +129,13 @@ void main() { test('setSearchMode + toggleSearchMode flip the mode and clear results', () async { f.ipc.stub( - 'pql.search', - (_) async => _ok({ - 'results': [ - {'path': 'a.md', 'score': 0.5}, - ], - })); + 'pql.search', + (_) async => _ok({ + 'results': [ + {'path': 'a.md', 'score': 0.5}, + ], + }), + ); await c.search('x'); expect(c.results, isNotEmpty); c.setSearchMode(SearchMode.dsl); diff --git a/test/builtin/search/find_in_files_controller_test.dart b/test/builtin/search/find_in_files_controller_test.dart index a645837a..95c6f0a6 100644 --- a/test/builtin/search/find_in_files_controller_test.dart +++ b/test/builtin/search/find_in_files_controller_test.dart @@ -31,12 +31,7 @@ void main() { FindInFilesController make() => ctrl = FindInFilesController(ipc: f.ipc, events: f.services.events); void emitMatch(String id, List> matches) { - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.match', - data: {'searchId': id, 'matches': matches}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit(DaemonEvent(subsystem: 'search', kind: 'search.match', data: {'searchId': id, 'matches': matches}, ts: DateTime.now().toUtc())); } Map m(String path, int line) => {'path': path, 'line': line, 'matchStart': 0, 'matchEnd': 3, 'preview': 'foo bar'}; @@ -93,12 +88,9 @@ void main() { test('search.done clears running', () async { final c = make(); await c.run('foo'); - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.done', - data: const {'searchId': 's1', 'cancelled': false}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit( + DaemonEvent(subsystem: 'search', kind: 'search.done', data: const {'searchId': 's1', 'cancelled': false}, ts: DateTime.now().toUtc()), + ); await Future.delayed(Duration.zero); expect(c.running, isFalse); expect(c.done, isTrue); @@ -107,12 +99,9 @@ void main() { test('search.error surfaces the message', () async { final c = make(); await c.run('(bad'); - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.error', - data: const {'searchId': 's1', 'message': 'invalid regex: x'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit( + DaemonEvent(subsystem: 'search', kind: 'search.error', data: const {'searchId': 's1', 'message': 'invalid regex: x'}, ts: DateTime.now().toUtc()), + ); await Future.delayed(Duration.zero); expect(c.error, contains('invalid regex')); expect(c.running, isFalse); @@ -151,11 +140,12 @@ void main() { test('a failed search.grep surfaces the error', () async { f.ipc.stub( - 'search.grep', - (_) async => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'nope'), - )); + 'search.grep', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.userError, kind: IpcErrorKind.userError, message: 'nope'), + ), + ); final c = make(); await c.run('foo'); expect(c.error, 'nope'); diff --git a/test/builtin/search/search_panel_view_test.dart b/test/builtin/search/search_panel_view_test.dart index 53ff4ca1..a65a6702 100644 --- a/test/builtin/search/search_panel_view_test.dart +++ b/test/builtin/search/search_panel_view_test.dart @@ -28,17 +28,19 @@ void main() { tearDown(() => f.dispose()); void emitMatches() { - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.match', - data: const { - 'searchId': 's1', - 'matches': [ - {'path': 'lib/a.dart', 'line': 12, 'matchStart': 6, 'matchEnd': 9, 'preview': 'final foo = 1;'}, - ], - }, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit( + DaemonEvent( + subsystem: 'search', + kind: 'search.match', + data: const { + 'searchId': 's1', + 'matches': [ + {'path': 'lib/a.dart', 'line': 12, 'matchStart': 6, 'matchEnd': 9, 'preview': 'final foo = 1;'}, + ], + }, + ts: DateTime.now().toUtc(), + ), + ); } testWidgets('search renders matches grouped by file', (tester) async { @@ -79,12 +81,9 @@ void main() { await tester.enterText(find.byType(EditableText).first, 'foo'); await tester.pump(const Duration(milliseconds: 250)); await pumpAsync(tester); - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.done', - data: const {'searchId': 's1', 'cancelled': false}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit( + DaemonEvent(subsystem: 'search', kind: 'search.done', data: const {'searchId': 's1', 'cancelled': false}, ts: DateTime.now().toUtc()), + ); await pumpAsync(tester); expect(find.text('No results'), findsOneWidget); }); @@ -94,12 +93,9 @@ void main() { await tester.enterText(find.byType(EditableText).first, '(bad'); await tester.pump(const Duration(milliseconds: 250)); await pumpAsync(tester); - f.services.events.emit(DaemonEvent( - subsystem: 'search', - kind: 'search.error', - data: const {'searchId': 's1', 'message': 'invalid regex: boom'}, - ts: DateTime.now().toUtc(), - )); + f.services.events.emit( + DaemonEvent(subsystem: 'search', kind: 'search.error', data: const {'searchId': 's1', 'message': 'invalid regex: boom'}, ts: DateTime.now().toUtc()), + ); await pumpAsync(tester); expect(find.textContaining('invalid regex'), findsOneWidget); }); @@ -136,10 +132,7 @@ void main() { await seedReplace(tester); // The emitted match line is 'final foo = 1;' → preview shows the after // (rendered as a RichText span, so match on the plain text). - expect( - find.byWidgetPredicate((w) => w is RichText && w.text.toPlainText() == 'final bar = 1;'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is RichText && w.text.toPlainText() == 'final bar = 1;'), findsOneWidget); }); testWidgets('Replace all on a dirty tree shows a guard dialog, no apply', (tester) async { @@ -226,12 +219,13 @@ void main() { testWidgets('Markdown mode lists markdown files on switch', (tester) async { f.ipc.stub( - 'pql.files', - (_) async => _ok({ - 'files': [ - {'path': 'docs/initial-plan.md'}, - ], - })); + 'pql.files', + (_) async => _ok({ + 'files': [ + {'path': 'docs/initial-plan.md'}, + ], + }), + ); await tester.pumpWidget(harness(f, const SearchPanelView())); await pumpAsync(tester); @@ -243,11 +237,12 @@ void main() { testWidgets('Vault mode surfaces a pql search error', (tester) async { f.ipc.stub( - 'pql.search', - (_) async => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'pql down'), - )); + 'pql.search', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'pql down'), + ), + ); await tester.pumpWidget(harness(f, const SearchPanelView())); await pumpAsync(tester); await tester.tap(find.text('Vault')); diff --git a/test/builtin/theme_picker/widget_test.dart b/test/builtin/theme_picker/widget_test.dart index d5a8dea5..41f972d0 100644 --- a/test/builtin/theme_picker/widget_test.dart +++ b/test/builtin/theme_picker/widget_test.dart @@ -10,21 +10,21 @@ import '../../helpers/kernel_fixture.dart'; import '../../helpers/widget_harness.dart'; ThemeDefinition _def(String name) => ThemeDefinition( - name: name, - displayName: name, - dark: true, - palette: Palette(const { - 'primary': Color(0xFF00A3D2), - 'accent': Color(0xFFFA5F8B), - 'background': Color(0xFF21262F), - 'surface': Color(0xFF393E48), - 'panel': Color(0xFF292E38), - 'foreground': Color(0xFFE2E8F5), - 'success': Color(0xFF00AB9A), - 'warning': Color(0xFFD08447), - 'error': Color(0xFFF06C6F), - }), - ); + name: name, + displayName: name, + dark: true, + palette: Palette(const { + 'primary': Color(0xFF00A3D2), + 'accent': Color(0xFFFA5F8B), + 'background': Color(0xFF21262F), + 'surface': Color(0xFF393E48), + 'panel': Color(0xFF292E38), + 'foreground': Color(0xFFE2E8F5), + 'success': Color(0xFF00AB9A), + 'warning': Color(0xFFD08447), + 'error': Color(0xFFF06C6F), + }), +); void main() { group('ThemePickerExtension', () { @@ -59,22 +59,11 @@ void main() { test('default binding ctrl+k is registered', () async { f.services.extensions.register(ThemePickerExtension()); await f.services.extensions.activateAll(); - expect( - f.services.keybindings.commandFor(Keybinding.parse('ctrl+k')), - 'theme.pick', - ); + expect(f.services.keybindings.commandFor(Keybinding.parse('ctrl+k')), 'theme.pick'); }); testWidgets('settings modal lists base themes + a High contrast toggle', (tester) async { - await tester.pumpWidget( - harness( - f, - SettingsView( - controller: f.services.theme, - onDismiss: ([_]) {}, - ), - ), - ); + await tester.pumpWidget(harness(f, SettingsView(controller: f.services.theme, onDismiss: ([_]) {}))); // Each row renders both displayName and name; displayName==name in // test fixtures so the label appears twice per row. expect(find.text('summer-night'), findsNWidgets(2)); @@ -88,15 +77,7 @@ void main() { testWidgets('tapping a row calls controller.select + onDismiss', (tester) async { String? dismissed; - await tester.pumpWidget( - harness( - f, - SettingsView( - controller: f.services.theme, - onDismiss: ([v]) => dismissed = v, - ), - ), - ); + await tester.pumpWidget(harness(f, SettingsView(controller: f.services.theme, onDismiss: ([v]) => dismissed = v))); await tester.tap(find.bySemanticsLabel('forest')); await tester.pumpAndSettle(); expect(f.services.theme.currentName, 'forest'); @@ -105,15 +86,7 @@ void main() { testWidgets('Cancel button dismisses without selecting', (tester) async { String? dismissed = 'not-called'; - await tester.pumpWidget( - harness( - f, - SettingsView( - controller: f.services.theme, - onDismiss: ([v]) => dismissed = v, - ), - ), - ); + await tester.pumpWidget(harness(f, SettingsView(controller: f.services.theme, onDismiss: ([v]) => dismissed = v))); await tester.tap(find.bySemanticsLabel('Cancel')); await tester.pumpAndSettle(); expect(dismissed, isNull); @@ -126,12 +99,7 @@ void main() { late KernelFixture hf; await tester.runAsync(() async => hf = await KernelFixture.create(bundledThemes: [_def('paper'), _def('paper-hc')])); addTearDown(hf.dispose); - await tester.pumpWidget( - harness( - hf, - SettingsView(controller: hf.services.theme, onDismiss: ([_]) {}), - ), - ); + await tester.pumpWidget(harness(hf, SettingsView(controller: hf.services.theme, onDismiss: ([_]) {}))); // Base theme listed once (displayName + muted name); the -hc sibling is // folded into the toggle, not shown as a row. expect(find.text('paper'), findsNWidgets(2)); diff --git a/test/builtin/tickets/ticket_detail_test.dart b/test/builtin/tickets/ticket_detail_test.dart index f0a4914c..23d262ff 100644 --- a/test/builtin/tickets/ticket_detail_test.dart +++ b/test/builtin/tickets/ticket_detail_test.dart @@ -16,16 +16,19 @@ import 'package:flutter_test/flutter_test.dart'; import '../../helpers/kernel_fixture.dart'; import '../../helpers/widget_harness.dart'; -IpcResponse _ticket(String id) => IpcResponse.ok(id: '', data: { - 'id': id, - 'title': 'Ticket $id', - 'type': 'task', - 'status': 'backlog', - 'priority': 'medium', - 'description': 'Body of $id', - 'ancestors': [], - 'decisions': [], - }); +IpcResponse _ticket(String id) => IpcResponse.ok( + id: '', + data: { + 'id': id, + 'title': 'Ticket $id', + 'type': 'task', + 'status': 'backlog', + 'priority': 'medium', + 'description': 'Body of $id', + 'ancestors': [], + 'decisions': [], + }, +); void main() { group('TicketDetailController — loads on load (T-199)', () { @@ -61,11 +64,13 @@ void main() { setUp(() async { f = await KernelFixture.create(); f.services.panels.registerSlot(const SlotDefinition(id: Slots.contextPanel, position: SlotPosition.right)); - f.services.arrangement.applyPreset(const LayoutPresetContribution( - id: 'test', - displayName: 'test', - slots: [LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, visible: false)], - )); + f.services.arrangement.applyPreset( + const LayoutPresetContribution( + id: 'test', + displayName: 'test', + slots: [LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, visible: false)], + ), + ); f.services.extensions.register(TicketsExtension()); await f.services.extensions.activate('builtin.tickets'); }); @@ -131,22 +136,26 @@ void main() { testWidgets('renders parents, decisions, assignee, and applies a status change', (tester) async { Map? statusArgs; f.ipc.stub( - 'pql.tickets.show', - (args) async => IpcResponse.ok(id: '', data: { - 'id': 'T-1', - 'title': 'Rich ticket', - 'type': 'task', - 'status': 'backlog', - 'priority': 'high', - 'assigned_to': 'alice', - 'description': 'body', - 'ancestors': [ - {'id': 'T-9', 'title': 'Parent epic', 'type': 'epic'}, - ], - 'decisions': [ - {'id': 'D-1', 'title': 'Decision one', 'type': 'confirmed', 'domain': 'architecture'}, - ], - })); + 'pql.tickets.show', + (args) async => IpcResponse.ok( + id: '', + data: { + 'id': 'T-1', + 'title': 'Rich ticket', + 'type': 'task', + 'status': 'backlog', + 'priority': 'high', + 'assigned_to': 'alice', + 'description': 'body', + 'ancestors': [ + {'id': 'T-9', 'title': 'Parent epic', 'type': 'epic'}, + ], + 'decisions': [ + {'id': 'D-1', 'title': 'Decision one', 'type': 'confirmed', 'domain': 'architecture'}, + ], + }, + ), + ); f.ipc.stub('pql.tickets.status', (args) async { statusArgs = args; return IpcResponse.ok(id: '', data: const {}); diff --git a/test/builtin/tickets/tickets_view_test.dart b/test/builtin/tickets/tickets_view_test.dart index 4183bf7c..02cc0a19 100644 --- a/test/builtin/tickets/tickets_view_test.dart +++ b/test/builtin/tickets/tickets_view_test.dart @@ -12,13 +12,13 @@ import '../../helpers/kernel_fixture.dart'; import '../../helpers/widget_harness.dart'; Map _t(String id, String title, String status, {String? type, String? parentId}) => { - 'id': id, - 'title': title, - 'status': status, - 'type': type ?? 'task', - 'priority': 'medium', - if (parentId != null) 'parent_id': parentId, - }; + 'id': id, + 'title': title, + 'status': status, + 'type': type ?? 'task', + 'priority': 'medium', + 'parent_id': ?parentId, +}; IpcResponse _list(List> tickets) => IpcResponse.ok(id: '', data: {'tickets': tickets}); @@ -50,12 +50,7 @@ void main() { }); testWidgets('renders sectioned cards from the loaded list', (tester) async { - f.ipc.stub( - 'pql.tickets.list', - (_) async => _list([ - _t('T-1', 'Active thing', 'in_progress', parentId: 'T-9'), - _t('T-2', 'Queued thing', 'backlog'), - ])); + f.ipc.stub('pql.tickets.list', (_) async => _list([_t('T-1', 'Active thing', 'in_progress', parentId: 'T-9'), _t('T-2', 'Queued thing', 'backlog')])); await pumpView(tester); expect(find.textContaining('IN PROGRESS'), findsOneWidget); @@ -68,12 +63,7 @@ void main() { }); testWidgets('filter narrows the visible cards', (tester) async { - f.ipc.stub( - 'pql.tickets.list', - (_) async => _list([ - _t('T-1', 'Alpha', 'backlog'), - _t('T-2', 'Beta', 'backlog'), - ])); + f.ipc.stub('pql.tickets.list', (_) async => _list([_t('T-1', 'Alpha', 'backlog'), _t('T-2', 'Beta', 'backlog')])); await pumpView(tester); expect(find.text('Alpha'), findsOneWidget); @@ -115,11 +105,12 @@ void main() { testWidgets('a load error is surfaced', (tester) async { f.ipc.stub( - 'pql.tickets.list', - (_) async => IpcResponse.err( - id: '', - error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), - )); + 'pql.tickets.list', + (_) async => IpcResponse.err( + id: '', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), + ), + ); await pumpView(tester); expect(find.text('boom'), findsOneWidget); }); @@ -158,12 +149,7 @@ void main() { // window — hence the 350ms pumps below. Future loadTwoTypes(WidgetTester tester) async { - f.ipc.stub( - 'pql.tickets.list', - (_) async => _list([ - _t('T-1', 'a bug item', 'backlog', type: 'bug'), - _t('T-2', 'a task item', 'backlog', type: 'task'), - ])); + f.ipc.stub('pql.tickets.list', (_) async => _list([_t('T-1', 'a bug item', 'backlog', type: 'bug'), _t('T-2', 'a task item', 'backlog', type: 'task')])); await pumpView(tester); } diff --git a/test/builtin/welcome/dialog_test.dart b/test/builtin/welcome/dialog_test.dart index e32a3462..1ee1dc57 100644 --- a/test/builtin/welcome/dialog_test.dart +++ b/test/builtin/welcome/dialog_test.dart @@ -16,10 +16,7 @@ import '../../helpers/widget_harness.dart'; /// Wraps [harness] in a DialogHost rooted on the fixture's dialog /// router so kernel.dialog.show(...) calls render into the tree. Widget _harness(KernelFixture f, Widget child) { - return harness( - f, - DialogHost(router: f.services.dialog, child: child), - ); + return harness(f, DialogHost(router: f.services.dialog, child: child)); } void main() { @@ -43,15 +40,12 @@ void main() { tearDown(() async => f.dispose()); testWidgets('MissingPluginException path renders the OpenProjectDialog', (tester) async { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async { - if (call.method == 'pickDirectory') { - throw MissingPluginException(); - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async { + if (call.method == 'pickDirectory') { + throw MissingPluginException(); + } + return null; + }); tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); @@ -67,10 +61,7 @@ void main() { }); testWidgets('OpenProjectDialog Cancel dismisses the modal', (tester) async { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async => throw MissingPluginException(), - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async => throw MissingPluginException()); tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); @@ -85,10 +76,7 @@ void main() { }); testWidgets('OpenProjectDialog Open with empty path is a no-op', (tester) async { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async => throw MissingPluginException(), - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async => throw MissingPluginException()); tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); @@ -105,10 +93,7 @@ void main() { }); testWidgets('OpenProjectDialog Open with a non-repo path keeps the dialog (project.open returns false)', (tester) async { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async => throw MissingPluginException(), - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async => throw MissingPluginException()); tester.view.physicalSize = const Size(1200, 800); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); diff --git a/test/builtin/welcome/widget_test.dart b/test/builtin/welcome/widget_test.dart index b80c208e..f46c4ff5 100644 --- a/test/builtin/welcome/widget_test.dart +++ b/test/builtin/welcome/widget_test.dart @@ -75,12 +75,7 @@ void main() { }); testWidgets('status line shows "application ok" when all tools resolved', (tester) async { - f.services.toolchain.applyResolved(const ResolvedPaths( - git: '/usr/bin/git', - pql: '/usr/bin/pql', - tmux: '/usr/bin/tmux', - shell: '/bin/bash', - )); + f.services.toolchain.applyResolved(const ResolvedPaths(git: '/usr/bin/git', pql: '/usr/bin/pql', tmux: '/usr/bin/tmux', shell: '/bin/bash')); await tester.pumpWidget(harness(f, const WelcomeView())); await tester.pumpAndSettle(); expect(find.text('application ok'), findsOneWidget); @@ -91,9 +86,7 @@ void main() { tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); - f.services.toolchain.applyResolved(const ResolvedPaths( - pql: '/usr/bin/pql', - )); + f.services.toolchain.applyResolved(const ResolvedPaths(pql: '/usr/bin/pql')); await tester.pumpWidget(harness(f, const WelcomeView())); await tester.pumpAndSettle(); expect(find.textContaining('git not found'), findsOneWidget); @@ -102,15 +95,17 @@ void main() { testWidgets('theme-name link fires the theme.pick command when tapped', (tester) async { var invocations = 0; - f.services.commands.register(CommandContribution( - id: 'theme.pick', - command: 'theme.pick', - title: 'Theme: Pick', - run: (_) async { - invocations++; - return IpcResponse.ok(id: '', data: const {}); - }, - )); + f.services.commands.register( + CommandContribution( + id: 'theme.pick', + command: 'theme.pick', + title: 'Theme: Pick', + run: (_) async { + invocations++; + return IpcResponse.ok(id: '', data: const {}); + }, + ), + ); await tester.pumpWidget(harness(f, const WelcomeView())); await tester.pumpAndSettle(); await tester.tap(find.textContaining('theme:')); @@ -140,21 +135,21 @@ void main() { // harness()'s unbounded width breaks WelcomeView's Positioned status line // + Flexible rows independently). Widget tightWelcome() => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: const MediaQuery( - data: MediaQueryData(size: Size(1200, 900)), - child: Align( - alignment: Alignment.topLeft, - child: SizedBox(width: 1200, height: 900, child: WelcomeView()), - ), - ), + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: const MediaQuery( + data: MediaQueryData(size: Size(1200, 900)), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox(width: 1200, height: 900, child: WelcomeView()), ), ), - ); + ), + ), + ); Future seedRecents(WidgetTester tester, String json) async { // runAsync: real event loop, so SettingsStore's file I/O completes. @@ -186,10 +181,7 @@ void main() { }); testWidgets('sticky-startup toggle flips when tapped (T-115/T-122)', (tester) async { - await seedRecents( - tester, - '[{"path":"/tmp/clide-fixture","name":"clide-fixture","lastOpened":"2026-05-18T00:00:00.000Z"}]', - ); + await seedRecents(tester, '[{"path":"/tmp/clide-fixture","name":"clide-fixture","lastOpened":"2026-05-18T00:00:00.000Z"}]'); tester.view.physicalSize = const Size(1200, 900); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); @@ -205,10 +197,7 @@ void main() { }); testWidgets('tapping a recent row kicks off _openRecent without throwing (T-122)', (tester) async { - await seedRecents( - tester, - '[{"path":"/tmp/clide-fixture","name":"clide-fixture","lastOpened":"2026-05-18T00:00:00.000Z"}]', - ); + await seedRecents(tester, '[{"path":"/tmp/clide-fixture","name":"clide-fixture","lastOpened":"2026-05-18T00:00:00.000Z"}]'); tester.view.physicalSize = const Size(1200, 900); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); @@ -228,15 +217,12 @@ void main() { testWidgets('Open folder opens the fallback dialog when the picker throws MissingPluginException', (tester) async { // Pre-register a mock that throws — emulating a platform without // native picker support. - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async { - if (call.method == 'pickDirectory') { - throw MissingPluginException(); - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async { + if (call.method == 'pickDirectory') { + throw MissingPluginException(); + } + return null; + }); await tester.pumpWidget(harness(f, const WelcomeView())); await tester.pumpAndSettle(); await tester.tap(find.text('Open folder…')); diff --git a/test/cli/argv_dispatch_test.dart b/test/cli/argv_dispatch_test.dart index 80af5524..28a9149f 100644 --- a/test/cli/argv_dispatch_test.dart +++ b/test/cli/argv_dispatch_test.dart @@ -20,9 +20,15 @@ void main() { }); test('valid argv → parsed and re-dispatched (round-trip via ping)', () async { - final res = await d.dispatch(IpcRequest(id: 'x', cmd: argvSentinelCmd, args: { - 'argv': ['ping'], - })); + final res = await d.dispatch( + IpcRequest( + id: 'x', + cmd: argvSentinelCmd, + args: { + 'argv': ['ping'], + }, + ), + ); expect(res.ok, isTrue); expect(res.id, 'x'); expect(res.data['pong'], isTrue); @@ -36,26 +42,36 @@ void main() { }); test('args.argv is not a list → userError', () async { - final res = await d.dispatch(IpcRequest(id: 'z', cmd: argvSentinelCmd, args: { - 'argv': 'not a list', - })); + final res = await d.dispatch(IpcRequest(id: 'z', cmd: argvSentinelCmd, args: {'argv': 'not a list'})); expect(res.ok, isFalse); expect(res.error?.kind, IpcErrorKind.userError); }); test('argv that fails parseArgv → that error flows back unmodified', () async { - final res = await d.dispatch(IpcRequest(id: 'p', cmd: argvSentinelCmd, args: { - 'argv': const [], // empty argv triggers parseArgv usage error - })); + final res = await d.dispatch( + IpcRequest( + id: 'p', + cmd: argvSentinelCmd, + args: { + 'argv': const [], // empty argv triggers parseArgv usage error + }, + ), + ); expect(res.ok, isFalse); expect(res.error?.kind, IpcErrorKind.userError); expect(res.error?.message, contains('usage')); }); test('outer request id is preserved on the response', () async { - final res = await d.dispatch(IpcRequest(id: 'unique-id-123', cmd: argvSentinelCmd, args: { - 'argv': ['ping'], - })); + final res = await d.dispatch( + IpcRequest( + id: 'unique-id-123', + cmd: argvSentinelCmd, + args: { + 'argv': ['ping'], + }, + ), + ); expect(res.id, 'unique-id-123'); }); } diff --git a/test/cli/clide_cli_e2e_test.dart b/test/cli/clide_cli_e2e_test.dart index 46063b80..74146bd2 100644 --- a/test/cli/clide_cli_e2e_test.dart +++ b/test/cli/clide_cli_e2e_test.dart @@ -36,14 +36,7 @@ void main() { if (!hasCC) return; final src = '$repoRoot/native/clide-cli/clide.c'; final out = '${Directory.systemTemp.createTempSync('clide-cli-test-').path}/clide'; - final build = await Process.run('cc', [ - '-std=c99', - '-O2', - '-Wall', - src, - '-o', - out, - ]); + final build = await Process.run('cc', ['-std=c99', '-O2', '-Wall', src, '-o', out]); expect(build.exitCode, 0, reason: 'cc failed: ${build.stderr}'); binaryPath = out; diff --git a/test/cli/clide_version_test.dart b/test/cli/clide_version_test.dart index 89df2a05..ae509408 100644 --- a/test/cli/clide_version_test.dart +++ b/test/cli/clide_version_test.dart @@ -26,10 +26,6 @@ String _pubspecVersion() { void main() { test('clideVersion matches pubspec.yaml version (T-213)', () { - expect( - clideVersion, - _pubspecVersion(), - reason: 'build_info.g.dart drifted from pubspec.yaml — run `make gen-build-info`', - ); + expect(clideVersion, _pubspecVersion(), reason: 'build_info.g.dart drifted from pubspec.yaml — run `make gen-build-info`'); }); } diff --git a/test/daemon/dispatcher_test.dart b/test/daemon/dispatcher_test.dart index df7e891b..60023e21 100644 --- a/test/daemon/dispatcher_test.dart +++ b/test/daemon/dispatcher_test.dart @@ -122,7 +122,10 @@ void main() { d.register( 'git.checkout', (req) async => IpcResponse.ok(id: req.id, data: const {}), - schema: CommandSchema(positional: const ['ref'], args: {'ref': ArgSpec(pattern: RegExp(r'^\w+$'))}), + schema: CommandSchema( + positional: const ['ref'], + args: {'ref': ArgSpec(pattern: RegExp(r'^\w+$'))}, + ), ); final tools = d.mcpTools(); diff --git a/test/daemon/editor_commands_test.dart b/test/daemon/editor_commands_test.dart index 09ea8d96..0f0b1c19 100644 --- a/test/daemon/editor_commands_test.dart +++ b/test/daemon/editor_commands_test.dart @@ -138,9 +138,7 @@ void main() { await call('editor.open', {'path': 'a.md'}); await call('editor.open', {'path': 'b.md'}); final r = await call('editor.list'); - final names = [ - for (final b in (r.data['buffers'] as List).cast()) b['path'], - ]; + final names = [for (final b in (r.data['buffers'] as List).cast()) b['path']]; expect(names, containsAll(['a.md', 'b.md'])); }); @@ -204,14 +202,14 @@ void main() { test('editor.set-selection clamps and applies', () async { await call('editor.open', {'path': 'doc.md'}); final r = await call('editor.set-selection', { - 'selection': {'start': 0, 'end': 3} + 'selection': {'start': 0, 'end': 3}, }); expect(r.ok, isTrue); }); test('editor.set-selection without an id or active buffer returns not-found', () async { final r = await call('editor.set-selection', { - 'selection': {'start': 0, 'end': 1} + 'selection': {'start': 0, 'end': 1}, }); expect(r.ok, isFalse); expect(r.error!.kind, 'not_found'); @@ -226,7 +224,7 @@ void main() { expect(read1.data['content'], 'replaced'); final r2 = await call('editor.set-content', { 'text': 'short', - 'selection': {'start': 1, 'end': 99} + 'selection': {'start': 1, 'end': 99}, }); expect(r2.ok, isTrue); }); diff --git a/test/daemon/files_commands_test.dart b/test/daemon/files_commands_test.dart index 9e71284c..50adf54b 100644 --- a/test/daemon/files_commands_test.dart +++ b/test/daemon/files_commands_test.dart @@ -67,9 +67,7 @@ void main() { test('files.ls into a subdirectory returns its contents', () async { final r = await call('files.ls', const {'path': 'lib'}); expect(r.ok, isTrue); - final names = [ - for (final e in (r.data['entries'] as List).cast()) e['name'], - ]; + final names = [for (final e in (r.data['entries'] as List).cast()) e['name']]; expect(names, ['main.dart']); }); diff --git a/test/daemon/git_commands_errors_test.dart b/test/daemon/git_commands_errors_test.dart index 50170c8d..95acd529 100644 --- a/test/daemon/git_commands_errors_test.dart +++ b/test/daemon/git_commands_errors_test.dart @@ -40,8 +40,8 @@ void main() { ( 'git.stage', const { - 'paths': ['file.txt'] - } + 'paths': ['file.txt'], + }, ), ('git.stage-all', const {}), ('git.unstage', const {}), @@ -53,8 +53,8 @@ void main() { ( 'git.discard', const { - 'paths': ['file.txt'] - } + 'paths': ['file.txt'], + }, ), ('git.commit', const {'message': 'hi'}), ('git.stash', const {}), diff --git a/test/daemon/git_commands_test.dart b/test/daemon/git_commands_test.dart index 3e7acddb..2022f59c 100644 --- a/test/daemon/git_commands_test.dart +++ b/test/daemon/git_commands_test.dart @@ -13,23 +13,11 @@ void main() { setUp(() async { sandbox = await Directory.systemTemp.createTemp('clide-git-cmd-test-'); await Process.run('git', ['init'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['config', 'user.email', 'test@test.com'], - workingDirectory: sandbox.path, - ); - await Process.run( - 'git', - ['config', 'user.name', 'Test'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['config', 'user.email', 'test@test.com'], workingDirectory: sandbox.path); + await Process.run('git', ['config', 'user.name', 'Test'], workingDirectory: sandbox.path); await File('${sandbox.path}/file.txt').writeAsString('hello\n'); await Process.run('git', ['add', '.'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['commit', '-m', 'init'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['commit', '-m', 'init'], workingDirectory: sandbox.path); sink = RecordingEventSink(); dispatcher = DaemonDispatcher(); @@ -64,7 +52,7 @@ void main() { test('git.stage + git.status shows staged file', () async { await File('${sandbox.path}/new.txt').writeAsString('x'); final stage = await call('git.stage', { - 'paths': ['new.txt'] + 'paths': ['new.txt'], }); expect(stage.ok, isTrue); @@ -82,10 +70,10 @@ void main() { test('git.unstage removes from staging', () async { await File('${sandbox.path}/new.txt').writeAsString('x'); await call('git.stage', { - 'paths': ['new.txt'] + 'paths': ['new.txt'], }); final unstage = await call('git.unstage', { - 'paths': ['new.txt'] + 'paths': ['new.txt'], }); expect(unstage.ok, isTrue); @@ -97,7 +85,7 @@ void main() { test('git.commit creates a commit', () async { await File('${sandbox.path}/c.txt').writeAsString('x'); await call('git.stage', { - 'paths': ['c.txt'] + 'paths': ['c.txt'], }); final r = await call('git.commit', {'message': 'test commit'}); expect(r.ok, isTrue); @@ -121,7 +109,7 @@ void main() { test('git.diff --staged returns staged diffs', () async { await File('${sandbox.path}/file.txt').writeAsString('modified\n'); await call('git.stage', { - 'paths': ['file.txt'] + 'paths': ['file.txt'], }); final r = await call('git.diff', {'staged': true}); expect(r.ok, isTrue); @@ -139,7 +127,7 @@ void main() { test('git.discard restores a file', () async { await File('${sandbox.path}/file.txt').writeAsString('changed'); final r = await call('git.discard', { - 'paths': ['file.txt'] + 'paths': ['file.txt'], }); expect(r.ok, isTrue); final content = await File('${sandbox.path}/file.txt').readAsString(); @@ -155,12 +143,9 @@ void main() { test('mutations emit git.changed events', () async { await File('${sandbox.path}/e.txt').writeAsString('x'); await call('git.stage', { - 'paths': ['e.txt'] + 'paths': ['e.txt'], }); - expect( - sink.events, - contains(predicate((e) => e.kind == 'git.changed')), - ); + expect(sink.events, contains(predicate((e) => e.kind == 'git.changed'))); }); test('git.stage-all stages everything', () async { @@ -192,7 +177,7 @@ void main() { await File('${sandbox.path}/file.txt').writeAsString('hello\nworld\n'); await File('${sandbox.path}/other.txt').writeAsString('o'); final r = await call('git.diff', { - 'paths': ['file.txt'] + 'paths': ['file.txt'], }); expect(r.ok, isTrue); final diffs = r.data['diffs'] as List; diff --git a/test/daemon/image_commands_test.dart b/test/daemon/image_commands_test.dart index 161e5183..5fe5ba72 100644 --- a/test/daemon/image_commands_test.dart +++ b/test/daemon/image_commands_test.dart @@ -17,19 +17,14 @@ void main() { void wire({bool liveUi = true, Set found = const {'docs/diagram.png'}, bool withResolver = true}) { published = []; d = DaemonDispatcher(); - registerImageCommands( - d, - () { - if (!liveUi) return null; - return (publisher, channel, data) => published.add((publisher: publisher, channel: channel, data: data)); - }, - resolve: withResolver ? (path) => found.contains(path) ? '/abs/$path' : null : null, - ); + registerImageCommands(d, () { + if (!liveUi) return null; + return (publisher, channel, data) => published.add((publisher: publisher, channel: channel, data: data)); + }, resolve: withResolver ? (path) => found.contains(path) ? '/abs/$path' : null : null); } - Future show(List positional, {Map? flags}) => d.dispatch( - IpcRequest(id: '1', cmd: 'image.show', args: {'positional': positional, if (flags != null) 'flags': flags}), - ); + Future show(List positional, {Map? flags}) => + d.dispatch(IpcRequest(id: '1', cmd: 'image.show', args: {'positional': positional, 'flags': ?flags})); test('resolves a workspace-relative path and publishes an image message', () async { wire(); diff --git a/test/daemon/pane_commands_test.dart b/test/daemon/pane_commands_test.dart index bf4aeed3..1c36365e 100644 --- a/test/daemon/pane_commands_test.dart +++ b/test/daemon/pane_commands_test.dart @@ -75,10 +75,7 @@ void main() { expect(viaText.ok, isTrue); expect(viaText.data['written'], greaterThan(0)); - final viaBase64 = await call('pane.write', { - 'id': id, - 'bytes_b64': base64Encode(utf8.encode('def')), - }); + final viaBase64 = await call('pane.write', {'id': id, 'bytes_b64': base64Encode(utf8.encode('def'))}); expect(viaBase64.ok, isTrue); }); @@ -115,7 +112,7 @@ void main() { test('pane.spawn rejects non-string argv entries', () async { final r = await call('pane.spawn', const { - 'argv': ['/bin/sh', 42] + 'argv': ['/bin/sh', 42], }); expect(r.ok, isFalse); expect(r.error!.message, contains('strings')); @@ -262,7 +259,7 @@ void main() { test('merges PTY panes and UI tabs in one list', () async { await call('pane.spawn', { - 'argv': const ['/bin/cat'] + 'argv': const ['/bin/cat'], }); final r = await call('pane.list', const {}); final panes = (r.data['panes'] as List).cast(); diff --git a/test/daemon/panel_commands_test.dart b/test/daemon/panel_commands_test.dart index dd8f1c63..075ac37c 100644 --- a/test/daemon/panel_commands_test.dart +++ b/test/daemon/panel_commands_test.dart @@ -17,10 +17,7 @@ void main() { late _FakeResizer resizer; setUp(() { - resizer = _FakeResizer( - slots: {'sidebar': 200, 'context': 240, 'workspace': 800}, - editorRatio: 0.35, - ); + resizer = _FakeResizer(slots: {'sidebar': 200, 'context': 240, 'workspace': 800}, editorRatio: 0.35); dispatcher = DaemonDispatcher(); registerPanelCommands(dispatcher, resizer); }); diff --git a/test/daemon/pql_commands_errors_test.dart b/test/daemon/pql_commands_errors_test.dart index 8065336a..13016427 100644 --- a/test/daemon/pql_commands_errors_test.dart +++ b/test/daemon/pql_commands_errors_test.dart @@ -49,7 +49,7 @@ void main() { const { 'ids': ['T-1'], 'status': 'done', - } + }, ), ('pql.tickets.board', const {}), ('pql.plan.status', const {}), diff --git a/test/daemon/pql_commands_test.dart b/test/daemon/pql_commands_test.dart index 56105b3d..98a7db5b 100644 --- a/test/daemon/pql_commands_test.dart +++ b/test/daemon/pql_commands_test.dart @@ -181,11 +181,7 @@ void main() { }); test('pql.decisions.show forwards withRefs / withTickets', () async { - final r = await call('pql.decisions.show', { - 'id': 'D-1', - 'withRefs': true, - 'withTickets': true, - }); + final r = await call('pql.decisions.show', {'id': 'D-1', 'withRefs': true, 'withTickets': true}); expect(r.ok, isTrue); expect(r.data['id'], 'D-1'); }); @@ -198,12 +194,7 @@ void main() { }); test('pql.tickets.list with filters narrows + shows status', () async { - final r = await call('pql.tickets.list', { - 'status': 'done', - 'team': 'whatever', - 'assigned': 'no-one', - 'decision': 'D-1', - }); + final r = await call('pql.tickets.list', {'status': 'done', 'team': 'whatever', 'assigned': 'no-one', 'decision': 'D-1'}); expect(r.ok, isTrue); expect(r.data['tickets'], isA()); }); @@ -212,11 +203,7 @@ void main() { final missing = await call('pql.tickets.show'); expect(missing.ok, isFalse); expect(missing.error!.kind, 'user_error'); - final ok = await call('pql.tickets.show', { - 'id': 'T-1', - 'withContext': true, - 'withBlockers': true, - }); + final ok = await call('pql.tickets.show', {'id': 'T-1', 'withContext': true, 'withBlockers': true}); expect(ok.ok, isTrue); }); diff --git a/test/daemon/search_commands_test.dart b/test/daemon/search_commands_test.dart index 53d6c57e..e854bb15 100644 --- a/test/daemon/search_commands_test.dart +++ b/test/daemon/search_commands_test.dart @@ -17,12 +17,7 @@ void main() { File('${dir.path}/a.dart').writeAsStringSync('final answer = 42;\n'); File('${dir.path}/b.dart').writeAsStringSync('// no hits here\n'); sink = RecordingEventSink(); - final service = SearchService( - root: dir, - ignore: IgnoreSet([]), - events: sink, - useIsolates: false, - ); + final service = SearchService(root: dir, ignore: IgnoreSet([]), events: sink, useIsolates: false); d = DaemonDispatcher(); registerSearchCommands(d, service); }); diff --git a/test/daemon/status_command_test.dart b/test/daemon/status_command_test.dart index a65646f0..cccce23f 100644 --- a/test/daemon/status_command_test.dart +++ b/test/daemon/status_command_test.dart @@ -13,13 +13,7 @@ void main() { test('status resolves and returns the assembled snapshot with exit 0', () async { final d = DaemonDispatcher(); - registerStatusCommand( - d, - () async => { - 'workspace': '/repo', - 'focusedFile': null, - 'panes': const [], - }); + registerStatusCommand(d, () async => {'workspace': '/repo', 'focusedFile': null, 'panes': const []}); final r = await d.dispatch(req()); expect(r.ok, isTrue); expect(r.data['workspace'], '/repo'); diff --git a/test/daemon/ui_command_test.dart b/test/daemon/ui_command_test.dart index 82dafa4f..83452766 100644 --- a/test/daemon/ui_command_test.dart +++ b/test/daemon/ui_command_test.dart @@ -20,19 +20,13 @@ void main() { published = []; filterValues = {}; d = DaemonDispatcher(); - registerUiCommands( - d, - () { - if (!liveUi) return null; - return (publisher, channel, data) => published.add((publisher: publisher, channel: channel, data: data)); - }, - filterValue: liveUi ? (address) => filterValues[address] : null, - ); + registerUiCommands(d, () { + if (!liveUi) return null; + return (publisher, channel, data) => published.add((publisher: publisher, channel: channel, data: data)); + }, filterValue: liveUi ? (address) => filterValues[address] : null); } - Future open(List positional) => d.dispatch( - IpcRequest(id: '1', cmd: 'ui.open', args: {'positional': positional}), - ); + Future open(List positional) => d.dispatch(IpcRequest(id: '1', cmd: 'ui.open', args: {'positional': positional})); test('ui open tickets T-48 publishes a tickets selection', () async { wire(); @@ -97,9 +91,8 @@ void main() { // -- ui.toast (T-50 drive-half) ------------------------------------------- - Future toast(List positional, {Map? flags}) => d.dispatch( - IpcRequest(id: '1', cmd: 'ui.toast', args: {'positional': positional, if (flags != null) 'flags': flags}), - ); + Future toast(List positional, {Map? flags}) => + d.dispatch(IpcRequest(id: '1', cmd: 'ui.toast', args: {'positional': positional, 'flags': ?flags})); test('ui toast publishes a toast message (default info severity)', () async { wire(); @@ -150,9 +143,7 @@ void main() { // -- ui.filter (T-270 drive+observe half) --------------------------------- - Future filter(List positional) => d.dispatch( - IpcRequest(id: '1', cmd: 'ui.filter', args: {'positional': positional}), - ); + Future filter(List positional) => d.dispatch(IpcRequest(id: '1', cmd: 'ui.filter', args: {'positional': positional})); test('ui filter
publishes a filter.set', () async { wire(); diff --git a/test/editor/registry_test.dart b/test/editor/registry_test.dart index 66f52c3c..e52c1810 100644 --- a/test/editor/registry_test.dart +++ b/test/editor/registry_test.dart @@ -138,10 +138,7 @@ void main() { test('contentFromArgs decodes content_b64 when text is absent', () { expect(EditorRegistry.contentFromArgs({'text': 'plain'}), 'plain'); - expect( - EditorRegistry.contentFromArgs({'content_b64': 'aGVsbG8='}), - 'hello', - ); + expect(EditorRegistry.contentFromArgs({'content_b64': 'aGVsbG8='}), 'hello'); expect(EditorRegistry.contentFromArgs(const {}), ''); }); diff --git a/test/extension/src/contribution_test.dart b/test/extension/src/contribution_test.dart index 380314a8..77ca5b3c 100644 --- a/test/extension/src/contribution_test.dart +++ b/test/extension/src/contribution_test.dart @@ -25,10 +25,7 @@ void main() { }); test('StatusItemContribution pins the statusbar slot', () { - final s = StatusItemContribution( - id: 'ipc-status.indicator', - build: (_) => const SizedBox.shrink(), - ); + final s = StatusItemContribution(id: 'ipc-status.indicator', build: (_) => const SizedBox.shrink()); expect(s.slot, Slots.statusbar); }); @@ -42,20 +39,12 @@ void main() { }); test('TrayItemContribution pins the tray slot', () { - final t = TrayItemContribution( - id: 't', - label: 'Label', - onSelected: () {}, - ); + final t = TrayItemContribution(id: 't', label: 'Label', onSelected: () {}); expect(t.slot, Slots.tray); }); test('ToolbarButtonContribution pins the toolbar slot', () { - final b = ToolbarButtonContribution( - id: 'save', - label: 'Save', - onPressed: () {}, - ); + final b = ToolbarButtonContribution(id: 'save', label: 'Save', onPressed: () {}); expect(b.slot, Slots.toolbar); }); }); diff --git a/test/extension/src/extension_context_test.dart b/test/extension/src/extension_context_test.dart index 796ea426..1d15d023 100644 --- a/test/extension/src/extension_context_test.dart +++ b/test/extension/src/extension_context_test.dart @@ -53,11 +53,13 @@ void main() { test('publish + subscribe round-trip through the message bus', () async { Message? received; - f.services.extensions.register(_SugarExt((ctx) async { - final first = ctx.subscribe(channel: 'greet').first; - ctx.publish('greet', {'hello': 'world'}); - received = await first; - })); + f.services.extensions.register( + _SugarExt((ctx) async { + final first = ctx.subscribe(channel: 'greet').first; + ctx.publish('greet', {'hello': 'world'}); + received = await first; + }), + ); await f.services.extensions.activate('sugar.ext'); expect(received, isNotNull); expect(received!.publisher, 'sugar.ext'); @@ -68,10 +70,12 @@ void main() { test('t + tr resolve against the extension namespace', () async { String? t; String? tr; - f.services.extensions.register(_SugarExt((ctx) async { - t = ctx.t('missing.key'); - tr = ctx.tr('missing.key', replacers: const []); - })); + f.services.extensions.register( + _SugarExt((ctx) async { + t = ctx.t('missing.key'); + tr = ctx.tr('missing.key', replacers: const []); + }), + ); await f.services.extensions.activate('sugar.ext'); // No catalog loaded for this namespace → i18n falls back, but the // sugar getters still execute and return a non-null string. diff --git a/test/extension/src/manifest_test.dart b/test/extension/src/manifest_test.dart index 75558fd9..13497535 100644 --- a/test/extension/src/manifest_test.dart +++ b/test/extension/src/manifest_test.dart @@ -46,24 +46,15 @@ depends_on: [builtin.git, 42, builtin.diff] }); test('missing id throws FormatException', () { - expect( - () => ExtensionManifest.fromYamlString('title: Floating'), - throwsA(isA()), - ); + expect(() => ExtensionManifest.fromYamlString('title: Floating'), throwsA(isA())); }); test('empty id throws FormatException', () { - expect( - () => ExtensionManifest.fromYamlString('id: ""'), - throwsA(isA()), - ); + expect(() => ExtensionManifest.fromYamlString('id: ""'), throwsA(isA())); }); test('non-map root throws FormatException', () { - expect( - () => ExtensionManifest.fromYamlString('- just a list'), - throwsA(isA()), - ); + expect(() => ExtensionManifest.fromYamlString('- just a list'), throwsA(isA())); }); }); } diff --git a/test/files/ignore_test.dart b/test/files/ignore_test.dart index 55f04dac..5f99a204 100644 --- a/test/files/ignore_test.dart +++ b/test/files/ignore_test.dart @@ -69,10 +69,7 @@ void main() { test('** crosses directory boundaries', () { final s = IgnoreSet.parse(const ['docs/**/*.draft.md\n']); - expect( - s.isIgnored('docs/deep/nested/a.draft.md', isDirectory: false), - isTrue, - ); + expect(s.isIgnored('docs/deep/nested/a.draft.md', isDirectory: false), isTrue); expect(s.isIgnored('docs/a.draft.md', isDirectory: false), isTrue); expect(s.isIgnored('other/a.draft.md', isDirectory: false), isFalse); }); diff --git a/test/files/path_safety_test.dart b/test/files/path_safety_test.dart index ebfe8485..66ec08c1 100644 --- a/test/files/path_safety_test.dart +++ b/test/files/path_safety_test.dart @@ -104,10 +104,7 @@ void main() { final link = Link('${root.path}/leak')..createSync(secret.path); expect(link.existsSync(), isTrue); - expect( - () => resolveUnderRootFollowingSymlinks(root, 'leak'), - throwsA(isA()), - ); + expect(() => resolveUnderRootFollowingSymlinks(root, 'leak'), throwsA(isA())); }); test('tolerates symlinks in the workspace root path itself', () { @@ -130,10 +127,7 @@ void main() { Link('${root.path}/b').createSync(secret.path); Link('${root.path}/a').createSync('${root.path}/b'); - expect( - () => resolveUnderRootFollowingSymlinks(root, 'a'), - throwsA(isA()), - ); + expect(() => resolveUnderRootFollowingSymlinks(root, 'a'), throwsA(isA())); }); }); @@ -171,10 +165,7 @@ void main() { addTearDown(() => outside.existsSync() ? outside.deleteSync(recursive: true) : null); File('${outside.path}/secret.txt').writeAsStringSync('payload'); Link('${extra.path}/leak').createSync('${outside.path}/secret.txt'); - expect( - () => resolveUnderRootsFollowingSymlinks(root, [extra], '${extra.absolute.path}/leak'), - throwsA(isA()), - ); + expect(() => resolveUnderRootsFollowingSymlinks(root, [extra], '${extra.absolute.path}/leak'), throwsA(isA())); }); }); } diff --git a/test/files/walk_test.dart b/test/files/walk_test.dart index 9ea1d150..e8a6ea15 100644 --- a/test/files/walk_test.dart +++ b/test/files/walk_test.dart @@ -28,13 +28,7 @@ void main() { test('walks recursively, returns files sorted by path', () async { final r = await walkFiles(root: root, ignore: IgnoreSet([])); expect(r.truncated, isFalse); - expect(r.files.map((e) => e.path).toList(), [ - 'README.md', - 'build/output.bin', - 'lib/main.dart', - 'lib/src/util.dart', - 'pubspec.yaml', - ]); + expect(r.files.map((e) => e.path).toList(), ['README.md', 'build/output.bin', 'lib/main.dart', 'lib/src/util.dart', 'pubspec.yaml']); }); test('prunes ignored directories (build/ via builtin ignore)', () async { diff --git a/test/files/watcher_test.dart b/test/files/watcher_test.dart index f01a5ba2..a409b603 100644 --- a/test/files/watcher_test.dart +++ b/test/files/watcher_test.dart @@ -15,12 +15,12 @@ void main() { test('fromEvent maps every FileSystemEvent.type to a wire kind', () { final dir = Directory.systemTemp; FileSystemEvent ev(int type) => switch (type) { - FileSystemEvent.create => FileSystemCreateEvent('${dir.path}/f', false), - FileSystemEvent.delete => FileSystemDeleteEvent('${dir.path}/f', false), - FileSystemEvent.modify => FileSystemModifyEvent('${dir.path}/f', false, false), - FileSystemEvent.move => FileSystemMoveEvent('${dir.path}/f', false, '${dir.path}/g'), - _ => FileSystemModifyEvent('${dir.path}/f', false, false), - }; + FileSystemEvent.create => FileSystemCreateEvent('${dir.path}/f', false), + FileSystemEvent.delete => FileSystemDeleteEvent('${dir.path}/f', false), + FileSystemEvent.modify => FileSystemModifyEvent('${dir.path}/f', false, false), + FileSystemEvent.move => FileSystemMoveEvent('${dir.path}/f', false, '${dir.path}/g'), + _ => FileSystemModifyEvent('${dir.path}/f', false, false), + }; expect(FileChangeKind.fromEvent(ev(FileSystemEvent.create)), FileChangeKind.created); expect(FileChangeKind.fromEvent(ev(FileSystemEvent.delete)), FileChangeKind.deleted); expect(FileChangeKind.fromEvent(ev(FileSystemEvent.modify)), FileChangeKind.modified); @@ -65,15 +65,9 @@ void main() { // amount. firstWhere completes on the first matching event; // the timeout fails the test with a clear message if inotify // never delivers (instead of asserting on an empty list). - final saw = watcher.stream.firstWhere( - (c) => c.path == 'new.txt', - orElse: () => throw StateError('stream closed before new.txt arrived'), - ); + final saw = watcher.stream.firstWhere((c) => c.path == 'new.txt', orElse: () => throw StateError('stream closed before new.txt arrived')); await File('${sandbox.path}/new.txt').writeAsString('hi'); - final change = await saw.timeout( - ioTimeout, - onTimeout: () => fail('no `new.txt` event within ${ioTimeout.inSeconds}s'), - ); + final change = await saw.timeout(ioTimeout, onTimeout: () => fail('no `new.txt` event within ${ioTimeout.inSeconds}s')); expect(change.path, 'new.txt'); }); diff --git a/test/git/diff_test.dart b/test/git/diff_test.dart index 519b8f97..00e5e666 100644 --- a/test/git/diff_test.dart +++ b/test/git/diff_test.dart @@ -161,23 +161,11 @@ index abc..def 100644 setUp(() async { sandbox = await Directory.systemTemp.createTemp('clide-git-diff-test-'); await Process.run('git', ['init'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['config', 'user.email', 'test@test.com'], - workingDirectory: sandbox.path, - ); - await Process.run( - 'git', - ['config', 'user.name', 'Test'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['config', 'user.email', 'test@test.com'], workingDirectory: sandbox.path); + await Process.run('git', ['config', 'user.name', 'Test'], workingDirectory: sandbox.path); await File('${sandbox.path}/file.txt').writeAsString('line1\nline2\n'); await Process.run('git', ['add', '.'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['commit', '-m', 'init'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['commit', '-m', 'init'], workingDirectory: sandbox.path); }); tearDown(() async { @@ -194,11 +182,7 @@ index abc..def 100644 test('returns staged diff with staged: true', () async { await File('${sandbox.path}/file.txt').writeAsString('line1\nmodified\n'); - await Process.run( - 'git', - ['add', 'file.txt'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['add', 'file.txt'], workingDirectory: sandbox.path); final diffs = await gitDiff(sandbox, staged: true); expect(diffs, hasLength(1)); }); diff --git a/test/git/operations_test.dart b/test/git/operations_test.dart index 2e3ca3dc..450b238d 100644 --- a/test/git/operations_test.dart +++ b/test/git/operations_test.dart @@ -9,23 +9,11 @@ void main() { setUp(() async { sandbox = await Directory.systemTemp.createTemp('clide-git-ops-test-'); await Process.run('git', ['init'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['config', 'user.email', 'test@test.com'], - workingDirectory: sandbox.path, - ); - await Process.run( - 'git', - ['config', 'user.name', 'Test'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['config', 'user.email', 'test@test.com'], workingDirectory: sandbox.path); + await Process.run('git', ['config', 'user.name', 'Test'], workingDirectory: sandbox.path); await File('${sandbox.path}/file.txt').writeAsString('hello\n'); await Process.run('git', ['add', '.'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['commit', '-m', 'init'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['commit', '-m', 'init'], workingDirectory: sandbox.path); }); tearDown(() async { @@ -35,11 +23,7 @@ void main() { test('gitStage stages a file', () async { await File('${sandbox.path}/new.txt').writeAsString('x'); await gitStage(sandbox, ['new.txt']); - final r = await Process.run( - 'git', - ['diff', '--cached', '--name-only'], - workingDirectory: sandbox.path, - ); + final r = await Process.run('git', ['diff', '--cached', '--name-only'], workingDirectory: sandbox.path); expect((r.stdout as String).trim(), 'new.txt'); }); @@ -47,11 +31,7 @@ void main() { await File('${sandbox.path}/new.txt').writeAsString('x'); await gitStage(sandbox, ['new.txt']); await gitUnstage(sandbox, ['new.txt']); - final r = await Process.run( - 'git', - ['diff', '--cached', '--name-only'], - workingDirectory: sandbox.path, - ); + final r = await Process.run('git', ['diff', '--cached', '--name-only'], workingDirectory: sandbox.path); expect((r.stdout as String).trim(), isEmpty); }); @@ -60,19 +40,12 @@ void main() { await gitStage(sandbox, ['c.txt']); final hash = await gitCommit(sandbox, 'test commit'); expect(hash, hasLength(40)); - final r = await Process.run( - 'git', - ['log', '-1', '--format=%s'], - workingDirectory: sandbox.path, - ); + final r = await Process.run('git', ['log', '-1', '--format=%s'], workingDirectory: sandbox.path); expect((r.stdout as String).trim(), 'test commit'); }); test('gitCommit with nothing staged throws', () async { - expect( - () => gitCommit(sandbox, 'empty'), - throwsA(isA()), - ); + expect(() => gitCommit(sandbox, 'empty'), throwsA(isA())); }); test('gitLog returns entries', () async { @@ -111,22 +84,9 @@ void main() { }); test('GitLogEntry.toJson serialises every field (body omitted when empty)', () { - const a = GitLogEntry( - hash: 'h', - shortHash: 's', - subject: 'sub', - author: 'a', - date: 'd', - ); + const a = GitLogEntry(hash: 'h', shortHash: 's', subject: 'sub', author: 'a', date: 'd'); expect(a.toJson().containsKey('body'), isFalse); - const b = GitLogEntry( - hash: 'h', - shortHash: 's', - subject: 'sub', - author: 'a', - date: 'd', - body: 'bd', - ); + const b = GitLogEntry(hash: 'h', shortHash: 's', subject: 'sub', author: 'a', date: 'd', body: 'bd'); expect(b.toJson()['body'], 'bd'); }); @@ -142,35 +102,19 @@ void main() { await File('${sandbox.path}/b.txt').writeAsString('y'); await gitStage(sandbox, ['a.txt', 'b.txt']); await gitUnstage(sandbox, const []); - final r = await Process.run( - 'git', - ['diff', '--cached', '--name-only'], - workingDirectory: sandbox.path, - ); + final r = await Process.run('git', ['diff', '--cached', '--name-only'], workingDirectory: sandbox.path); expect((r.stdout as String).trim(), isEmpty); }); test('gitStageHunk + gitUnstageHunk apply a patch via _applyPatch', () async { await File('${sandbox.path}/file.txt').writeAsString('hello\nworld\n'); - final patchResult = await Process.run( - 'git', - ['diff', '-U0'], - workingDirectory: sandbox.path, - ); + final patchResult = await Process.run('git', ['diff', '-U0'], workingDirectory: sandbox.path); final patch = patchResult.stdout as String; await gitStageHunk(sandbox, patch); - final cached = await Process.run( - 'git', - ['diff', '--cached', '--name-only'], - workingDirectory: sandbox.path, - ); + final cached = await Process.run('git', ['diff', '--cached', '--name-only'], workingDirectory: sandbox.path); expect((cached.stdout as String).trim(), 'file.txt'); await gitUnstageHunk(sandbox, patch); - final cleared = await Process.run( - 'git', - ['diff', '--cached', '--name-only'], - workingDirectory: sandbox.path, - ); + final cleared = await Process.run('git', ['diff', '--cached', '--name-only'], workingDirectory: sandbox.path); expect((cleared.stdout as String).trim(), isEmpty); }); diff --git a/test/git/status_test.dart b/test/git/status_test.dart index 5ddeacaa..9090db83 100644 --- a/test/git/status_test.dart +++ b/test/git/status_test.dart @@ -9,24 +9,12 @@ void main() { setUp(() async { sandbox = await Directory.systemTemp.createTemp('clide-git-status-test-'); await Process.run('git', ['init'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['config', 'user.email', 'test@test.com'], - workingDirectory: sandbox.path, - ); - await Process.run( - 'git', - ['config', 'user.name', 'Test'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['config', 'user.email', 'test@test.com'], workingDirectory: sandbox.path); + await Process.run('git', ['config', 'user.name', 'Test'], workingDirectory: sandbox.path); // Initial commit so HEAD exists. await File('${sandbox.path}/.gitkeep').writeAsString(''); await Process.run('git', ['add', '.'], workingDirectory: sandbox.path); - await Process.run( - 'git', - ['commit', '-m', 'init'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['commit', '-m', 'init'], workingDirectory: sandbox.path); }); tearDown(() async { @@ -48,11 +36,7 @@ void main() { test('staged file appears in staged', () async { await File('${sandbox.path}/staged.txt').writeAsString('x'); - await Process.run( - 'git', - ['add', 'staged.txt'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['add', 'staged.txt'], workingDirectory: sandbox.path); final status = await gitStatus(sandbox); expect(status.staged, hasLength(1)); expect(status.staged.first.path, 'staged.txt'); @@ -75,11 +59,7 @@ void main() { test('file staged and then modified appears in both', () async { await File('${sandbox.path}/both.txt').writeAsString('v1'); - await Process.run( - 'git', - ['add', 'both.txt'], - workingDirectory: sandbox.path, - ); + await Process.run('git', ['add', 'both.txt'], workingDirectory: sandbox.path); await File('${sandbox.path}/both.txt').writeAsString('v2'); final status = await gitStatus(sandbox); expect(status.staged.any((e) => e.path == 'both.txt'), isTrue); diff --git a/test/goldens/clide_button_goldens_test.dart b/test/goldens/clide_button_goldens_test.dart index 8eecb790..fe324033 100644 --- a/test/goldens/clide_button_goldens_test.dart +++ b/test/goldens/clide_button_goldens_test.dart @@ -32,54 +32,23 @@ void main() { ), GoldenTestScenario( name: 'primary / enabled', - child: _wrap( - f, - ClideButton( - label: 'Commit', - onPressed: () {}, - variant: ClideButtonVariant.primary, - ), - ), + child: _wrap(f, ClideButton(label: 'Commit', onPressed: () {}, variant: ClideButtonVariant.primary)), ), GoldenTestScenario( name: 'primary / disabled', - child: _wrap( - f, - const ClideButton( - label: 'Commit', - onPressed: null, - variant: ClideButtonVariant.primary, - ), - ), + child: _wrap(f, const ClideButton(label: 'Commit', onPressed: null, variant: ClideButtonVariant.primary)), ), GoldenTestScenario( name: 'subtle / enabled', - child: _wrap( - f, - ClideButton( - label: 'Open', - onPressed: () {}, - variant: ClideButtonVariant.subtle, - ), - ), + child: _wrap(f, ClideButton(label: 'Open', onPressed: () {}, variant: ClideButtonVariant.subtle)), ), GoldenTestScenario( name: 'subtle / disabled', - child: _wrap( - f, - const ClideButton( - label: 'Open', - onPressed: null, - variant: ClideButtonVariant.subtle, - ), - ), + child: _wrap(f, const ClideButton(label: 'Open', onPressed: null, variant: ClideButtonVariant.subtle)), ), ], ), ); } -Widget _wrap(KernelFixture f, Widget child) => SizedBox( - width: 140, - child: harness(f, child), - ); +Widget _wrap(KernelFixture f, Widget child) => SizedBox(width: 140, child: harness(f, child)); diff --git a/test/goldens/clide_icon_goldens_test.dart b/test/goldens/clide_icon_goldens_test.dart index 62b0160d..4d150b4c 100644 --- a/test/goldens/clide_icon_goldens_test.dart +++ b/test/goldens/clide_icon_goldens_test.dart @@ -29,14 +29,7 @@ void main() { ]) GoldenTestScenario( name: pair.$1, - child: harness( - f, - SizedBox( - width: 24, - height: 24, - child: ClideIcon(pair.$2, size: 24), - ), - ), + child: harness(f, SizedBox(width: 24, height: 24, child: ClideIcon(pair.$2, size: 24))), ), ], ), diff --git a/test/goldens/collapser_card_goldens_test.dart b/test/goldens/collapser_card_goldens_test.dart index 35460da8..5c54afa9 100644 --- a/test/goldens/collapser_card_goldens_test.dart +++ b/test/goldens/collapser_card_goldens_test.dart @@ -15,23 +15,29 @@ void main() { // A stand-in inner item card: content + its own per-item status mark (the // collapser carries the aggregate; items keep their own — T-305). Widget item(String label, ClideRunStatus status) => Padding( - padding: const EdgeInsets.only(bottom: 10), - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all(color: const Color(0xFF393E48)), - borderRadius: BorderRadius.circular(3), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - child: Row( - children: [ - Expanded(child: Text(label, style: const TextStyle(fontSize: 11, color: Color(0xFF6A7280)), textDirection: TextDirection.ltr)), - ClideStatusIndicator(status: status, size: 12), - ], + padding: const EdgeInsets.only(bottom: 10), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: const Color(0xFF393E48)), + borderRadius: BorderRadius.circular(3), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + children: [ + Expanded( + child: Text( + label, + style: const TextStyle(fontSize: 11, color: Color(0xFF6A7280)), + textDirection: TextDirection.ltr, + ), ), - ), + ClideStatusIndicator(status: status, size: 12), + ], ), - ); + ), + ), + ); goldenTest( 'ClideCollapserCard (T-305): collapsed color variants + expanded inner canvas', @@ -42,37 +48,43 @@ void main() { GoldenTestScenario( name: 'collapsed — default (muted)', child: _wrap( - f, - const ClideCollapserCard( - label: 'Activity', - collapsedSummary: 'Read conversation_view.dart', - counter: '3 steps', - status: ClideRunStatus.success, - children: [SizedBox.shrink()])), + f, + const ClideCollapserCard( + label: 'Activity', + collapsedSummary: 'Read conversation_view.dart', + counter: '3 steps', + status: ClideRunStatus.success, + children: [SizedBox.shrink()], + ), + ), ), GoldenTestScenario( name: 'collapsed — edits (teal color)', child: _wrap( - f, - const ClideCollapserCard( - label: 'Edits', - color: Color(0xFF00AB9A), - collapsedSummary: 'clide_markdown.dart', - counter: '7 edits', - status: ClideRunStatus.success, - children: [SizedBox.shrink()])), + f, + const ClideCollapserCard( + label: 'Edits', + color: Color(0xFF00AB9A), + collapsedSummary: 'clide_markdown.dart', + counter: '7 edits', + status: ClideRunStatus.success, + children: [SizedBox.shrink()], + ), + ), ), GoldenTestScenario( name: 'collapsed — error (red color)', child: _wrap( - f, - const ClideCollapserCard( - label: 'Bash', - color: Color(0xFFF06C6F), - collapsedSummary: 'npm test', - counter: '1 step', - status: ClideRunStatus.error, - children: [SizedBox.shrink()])), + f, + const ClideCollapserCard( + label: 'Bash', + color: Color(0xFFF06C6F), + collapsedSummary: 'npm test', + counter: '1 step', + status: ClideRunStatus.error, + children: [SizedBox.shrink()], + ), + ), ), GoldenTestScenario( name: 'expanded — inner canvas of item cards', diff --git a/test/goldens/conversation_card_goldens_test.dart b/test/goldens/conversation_card_goldens_test.dart index 73ba1a18..85b01a43 100644 --- a/test/goldens/conversation_card_goldens_test.dart +++ b/test/goldens/conversation_card_goldens_test.dart @@ -64,7 +64,12 @@ void main() { collapsedByDefault: true, collapsedSummary: '/lib/main.dart', body: Text('/lib/main.dart', textDirection: TextDirection.ltr), - extraSegments: [CardSegment(label: 'result', child: Text('void main() {}', textDirection: TextDirection.ltr))], + extraSegments: [ + CardSegment( + label: 'result', + child: Text('void main() {}', textDirection: TextDirection.ltr), + ), + ], ), ), ), @@ -80,7 +85,12 @@ void main() { collapsible: true, collapsedByDefault: false, body: Text('/lib/main.dart', textDirection: TextDirection.ltr), - extraSegments: [CardSegment(label: 'result', child: Text('void main() {}', textDirection: TextDirection.ltr))], + extraSegments: [ + CardSegment( + label: 'result', + child: Text('void main() {}', textDirection: TextDirection.ltr), + ), + ], ), ), ), @@ -97,8 +107,14 @@ void main() { collapsedByDefault: false, body: Text('{ "description": "explore the codebase" }', textDirection: TextDirection.ltr), extraSegments: [ - CardSegment(label: 'prompt', child: Text('find all the widgets and summarise', textDirection: TextDirection.ltr)), - CardSegment(label: 'result', child: Text('found 42 widgets', textDirection: TextDirection.ltr)), + CardSegment( + label: 'prompt', + child: Text('find all the widgets and summarise', textDirection: TextDirection.ltr), + ), + CardSegment( + label: 'result', + child: Text('found 42 widgets', textDirection: TextDirection.ltr), + ), ], ), ), @@ -178,7 +194,4 @@ void main() { ); } -Widget _wrap(KernelFixture f, Widget child) => SizedBox( - width: 360, - child: harness(f, child), - ); +Widget _wrap(KernelFixture f, Widget child) => SizedBox(width: 360, child: harness(f, child)); diff --git a/test/goldens/flutter_test_config.dart b/test/goldens/flutter_test_config.dart index d1e3da70..52713191 100644 --- a/test/goldens/flutter_test_config.dart +++ b/test/goldens/flutter_test_config.dart @@ -5,8 +5,5 @@ import 'package:alchemist/alchemist.dart'; import '../helpers/golden_harness.dart'; Future testExecutable(FutureOr Function() testMain) async { - return AlchemistConfig.runWithConfig( - config: clideGoldenConfig(), - run: testMain, - ); + return AlchemistConfig.runWithConfig(config: clideGoldenConfig(), run: testMain); } diff --git a/test/goldens/tool_collapser_goldens_test.dart b/test/goldens/tool_collapser_goldens_test.dart index bb2e9046..1f271315 100644 --- a/test/goldens/tool_collapser_goldens_test.dart +++ b/test/goldens/tool_collapser_goldens_test.dart @@ -17,16 +17,26 @@ void main() { tearDown(() async => f.dispose()); Widget bashContent() => const ConversationCard( - variant: ConversationCardVariant.bordered, - accent: Color(0xFF4C9AFF), - label: 'Bash', - status: ConversationCardStatus.success, - body: Text('npm test', style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), textDirection: TextDirection.ltr), - extraSegments: [ - CardSegment( - label: 'result', child: Text('All 42 tests passed', style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), textDirection: TextDirection.ltr)), - ], - ); + variant: ConversationCardVariant.bordered, + accent: Color(0xFF4C9AFF), + label: 'Bash', + status: ConversationCardStatus.success, + body: Text( + 'npm test', + style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), + textDirection: TextDirection.ltr, + ), + extraSegments: [ + CardSegment( + label: 'result', + child: Text( + 'All 42 tests passed', + style: TextStyle(fontSize: 12, color: Color(0xFF9DA5B4)), + textDirection: TextDirection.ltr, + ), + ), + ], + ); goldenTest( 'single tool collapser (T-305): collapsed ticker + expanded inner card', diff --git a/test/helpers/fake_ipc.dart b/test/helpers/fake_ipc.dart index 7cb316c6..337cf9e5 100644 --- a/test/helpers/fake_ipc.dart +++ b/test/helpers/fake_ipc.dart @@ -24,19 +24,12 @@ class FakeDaemonClient extends DaemonClient { } @override - Future request( - String cmd, { - Map args = const {}, - }) async { + Future request(String cmd, {Map args = const {}}) async { final stub = _stubs[cmd]; if (stub != null) return stub(args); return IpcResponse.err( id: '', - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: 'no stub for $cmd', - ), + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'no stub for $cmd'), ); } @@ -46,10 +39,7 @@ class FakeDaemonClient extends DaemonClient { notifyListeners(); } - void stub( - String cmd, - Future Function(Map) handler, - ) { + void stub(String cmd, Future Function(Map) handler) { _stubs[cmd] = handler; } } diff --git a/test/helpers/golden_harness.dart b/test/helpers/golden_harness.dart index 6906d6fa..29a6454c 100644 --- a/test/helpers/golden_harness.dart +++ b/test/helpers/golden_harness.dart @@ -9,11 +9,7 @@ import 'package:alchemist/alchemist.dart'; AlchemistConfig clideGoldenConfig() { return const AlchemistConfig( theme: null, // we're not using Material ThemeData - platformGoldensConfig: PlatformGoldensConfig( - enabled: true, - ), - ciGoldensConfig: CiGoldensConfig( - enabled: false, - ), + platformGoldensConfig: PlatformGoldensConfig(enabled: true), + ciGoldensConfig: CiGoldensConfig(enabled: false), ); } diff --git a/test/helpers/kernel_fixture.dart b/test/helpers/kernel_fixture.dart index be36556a..6f1f4a04 100644 --- a/test/helpers/kernel_fixture.dart +++ b/test/helpers/kernel_fixture.dart @@ -34,7 +34,7 @@ class KernelFixture { preloadNamespaces: preloadNamespaces ?? catalogs.keys.toList(), defaultLocale: defaultLocale, initialLocale: initialLocale, - daemonClientFactory: (log, events, _, __) { + daemonClientFactory: (log, events, _, _) { fake = FakeDaemonClient(log: log, events: events); return fake!; }, @@ -45,11 +45,7 @@ class KernelFixture { // ~10 minutes (T-280); `existsSync` opens no native port, so it's safe. onValidateProject: onValidateProject ?? _walkForGitRoot, ); - return KernelFixture._( - services: services, - ipc: fake!, - tempDir: tempDir, - ); + return KernelFixture._(services: services, ipc: fake!, tempDir: tempDir); } Future dispose() async { @@ -100,10 +96,5 @@ ThemeDefinition _miniTheme() { 'error': Color(0xFFF06C6F), 'info': Color(0xFF00A3D2), }; - return const ThemeDefinition( - name: 'test', - displayName: 'Test', - dark: true, - palette: Palette(palette), - ); + return const ThemeDefinition(name: 'test', displayName: 'Test', dark: true, palette: Palette(palette)); } diff --git a/test/helpers/widget_harness.dart b/test/helpers/widget_harness.dart index a8201634..1a2bee2f 100644 --- a/test/helpers/widget_harness.dart +++ b/test/helpers/widget_harness.dart @@ -21,14 +21,7 @@ Widget harness(KernelFixture fixture, Widget child) { controller: fixture.services.theme, child: MediaQuery( data: const MediaQueryData(), - child: Overlay( - initialEntries: [ - OverlayEntry( - canSizeOverlay: true, - builder: (_) => child, - ), - ], - ), + child: Overlay(initialEntries: [OverlayEntry(canSizeOverlay: true, builder: (_) => child)]), ), ), ), @@ -45,12 +38,7 @@ Widget harness(KernelFixture fixture, Widget child) { /// with [child] (the trigger) placed at [alignment]. Set /// `tester.view.physicalSize = size` to match (the default 800×600 matches the /// default test view, so no setup is needed unless you change [size]). -Widget anchoredHarness( - KernelFixture fixture, - Widget child, { - Size size = const Size(800, 600), - Alignment alignment = Alignment.topLeft, -}) { +Widget anchoredHarness(KernelFixture fixture, Widget child, {Size size = const Size(800, 600), Alignment alignment = Alignment.topLeft}) { return Directionality( textDirection: TextDirection.ltr, child: ClideKernel( @@ -61,7 +49,9 @@ Widget anchoredHarness( data: MediaQueryData(size: size), child: Overlay( initialEntries: [ - OverlayEntry(builder: (_) => Align(alignment: alignment, child: child)), + OverlayEntry( + builder: (_) => Align(alignment: alignment, child: child), + ), ], ), ), diff --git a/test/ipc/command_schema_test.dart b/test/ipc/command_schema_test.dart index 9bfda482..1ee323e5 100644 --- a/test/ipc/command_schema_test.dart +++ b/test/ipc/command_schema_test.dart @@ -109,7 +109,10 @@ void main() { group('normalize (argv shape → named args)', () { const schema = CommandSchema( positional: ['slot'], - args: {'slot': ArgSpec(), 'to': ArgSpec(type: ArgType.number)}, + args: { + 'slot': ArgSpec(), + 'to': ArgSpec(type: ArgType.number), + }, ); test('positional[i] maps to the i-th declared name', () { diff --git a/test/ipc/envelope_test.dart b/test/ipc/envelope_test.dart index f74c3bb0..3d7be177 100644 --- a/test/ipc/envelope_test.dart +++ b/test/ipc/envelope_test.dart @@ -4,11 +4,7 @@ import 'package:test/test.dart'; void main() { group('IpcRequest', () { test('encodes and decodes round-trip', () { - final req = IpcRequest( - id: '42', - cmd: 'git.status', - args: const {'path': '.'}, - ); + final req = IpcRequest(id: '42', cmd: 'git.status', args: const {'path': '.'}); final line = req.encode(); final decoded = IpcMessage.decode(line); expect(decoded, isA()); @@ -49,32 +45,18 @@ void main() { test('encodes with error payload, no data', () { final r = IpcResponse.err( id: '9', - error: IpcError( - code: IpcExitCode.notFound, - kind: IpcErrorKind.notFound, - message: 'missing', - hint: 'try --help', - ), + error: IpcError(code: IpcExitCode.notFound, kind: IpcErrorKind.notFound, message: 'missing', hint: 'try --help'), ); final json = r.toJson(); expect(json['ok'], false); - expect(json['error'], { - 'code': 3, - 'kind': 'not_found', - 'message': 'missing', - 'hint': 'try --help', - }); + expect(json['error'], {'code': 3, 'kind': 'not_found', 'message': 'missing', 'hint': 'try --help'}); expect(json.containsKey('data'), isFalse); }); test('decode preserves error fields', () { final original = IpcResponse.err( id: '9', - error: IpcError( - code: IpcExitCode.conflict, - kind: IpcErrorKind.conflict, - message: 'race', - ), + error: IpcError(code: IpcExitCode.conflict, kind: IpcErrorKind.conflict, message: 'race'), ); final r = IpcMessage.decode(original.encode()) as IpcResponse; expect(r.ok, false); @@ -89,12 +71,7 @@ void main() { group('IpcEvent', () { test('serializes subsystem + kind + ts + data', () { final ts = DateTime.utc(2026, 4, 21, 12, 0, 0); - final e = IpcEvent( - subsystem: 'git', - kind: 'status-changed', - timestamp: ts, - data: const {'staged': 3}, - ); + final e = IpcEvent(subsystem: 'git', kind: 'status-changed', timestamp: ts, data: const {'staged': 3}); final json = e.toJson(); expect(json['type'], 'event'); expect(json['subsystem'], 'git'); @@ -110,10 +87,7 @@ void main() { }); test('throws on unknown type', () { - expect( - () => IpcMessage.decode('{"type":"bogus","id":"1"}'), - throwsA(isA()), - ); + expect(() => IpcMessage.decode('{"type":"bogus","id":"1"}'), throwsA(isA())); }); test('throws on malformed JSON', () { diff --git a/test/ipc/mcp_server_test.dart b/test/ipc/mcp_server_test.dart index 77271382..823df367 100644 --- a/test/ipc/mcp_server_test.dart +++ b/test/ipc/mcp_server_test.dart @@ -20,11 +20,7 @@ void main() { setUp(() async { discoveryDir = await Directory.systemTemp.createTemp('clide-mcp-disc-'); - server = McpServer( - workspaceRoot: '/var/mnt/test/clide-fixture', - log: _silent(), - discoveryDirOverride: discoveryDir.path, - ); + server = McpServer(workspaceRoot: '/var/mnt/test/clide-fixture', log: _silent(), discoveryDirOverride: discoveryDir.path); await server.start(); }); @@ -155,7 +151,7 @@ void main() { 'method': 'tools/call', 'params': { 'name': 'mcp__ide__executeCode', - 'arguments': {'code': 'print(1)'} + 'arguments': {'code': 'print(1)'}, }, }); final reply = jsonDecode(await replyFuture.timeout(const Duration(seconds: 2))) as Map; @@ -214,12 +210,7 @@ void main() { dispatcher.register('echo', (req) async => IpcResponse.ok(id: req.id, data: {'echo': req.args['text']})); // A poor MCP fit — withheld from the tool surface (D-86). dispatcher.register('pane.tail', (req) async => IpcResponse.ok(id: req.id, data: const {}), mcpExpose: false); - srv = McpServer( - workspaceRoot: '/x', - log: _silent(), - discoveryDirOverride: disc.path, - dispatcher: dispatcher, - ); + srv = McpServer(workspaceRoot: '/x', log: _silent(), discoveryDirOverride: disc.path, dispatcher: dispatcher); await srv.start(); }); @@ -234,8 +225,12 @@ void main() { final client = HttpClient(); addTearDown(client.close); final resp = await (await client.getUrl(Uri.parse('http://127.0.0.1:${srv.port}/sse'))).close(); - final dataLines = - resp.transform(utf8.decoder).transform(const LineSplitter()).where((l) => l.startsWith('data: ')).map((l) => l.substring(6)).asBroadcastStream(); + final dataLines = resp + .transform(utf8.decoder) + .transform(const LineSplitter()) + .where((l) => l.startsWith('data: ')) + .map((l) => l.substring(6)) + .asBroadcastStream(); final endpoint = Completer(); final sub = dataLines.listen((d) { final m = RegExp(r'sessionId=([\w-]+)').firstMatch(d); @@ -312,8 +307,12 @@ void main() { final client = HttpClient(); addTearDown(client.close); final resp = await (await client.getUrl(Uri.parse('http://127.0.0.1:${srv.port}/sse'))).close(); - final data = - resp.transform(utf8.decoder).transform(const LineSplitter()).where((l) => l.startsWith('data: ')).map((l) => l.substring(6)).asBroadcastStream(); + final data = resp + .transform(utf8.decoder) + .transform(const LineSplitter()) + .where((l) => l.startsWith('data: ')) + .map((l) => l.substring(6)) + .asBroadcastStream(); final ep = Completer(); final sub = data.listen((d) { final m = RegExp(r'sessionId=([\w-]+)').firstMatch(d); @@ -324,12 +323,14 @@ void main() { final replyFuture = data.firstWhere((s) => s.contains('"id":13')); final post = await client.postUrl(Uri.parse('http://127.0.0.1:${srv.port}/messages?sessionId=$sid')); post.headers.contentType = ContentType.json; - post.write(jsonEncode({ - 'jsonrpc': '2.0', - 'id': 13, - 'method': 'tools/call', - 'params': {'name': 'mcp__clide__echo', 'arguments': const {}}, - })); + post.write( + jsonEncode({ + 'jsonrpc': '2.0', + 'id': 13, + 'method': 'tools/call', + 'params': {'name': 'mcp__clide__echo', 'arguments': const {}}, + }), + ); await post.close(); final reply = jsonDecode(await replyFuture.timeout(const Duration(seconds: 2))) as Map; expect((reply['result'] as Map)['isError'], isTrue); diff --git a/test/ipc/paths_test.dart b/test/ipc/paths_test.dart index f605fb06..f55e72c2 100644 --- a/test/ipc/paths_test.dart +++ b/test/ipc/paths_test.dart @@ -15,17 +15,11 @@ void main() { }); test('is deterministic for the same input', () { - expect( - workspaceSocketPath('/home/me/repo'), - workspaceSocketPath('/home/me/repo'), - ); + expect(workspaceSocketPath('/home/me/repo'), workspaceSocketPath('/home/me/repo')); }); test('different workspace roots hash to different paths', () { - expect( - workspaceSocketPath('/home/me/repo-a'), - isNot(workspaceSocketPath('/home/me/repo-b')), - ); + expect(workspaceSocketPath('/home/me/repo-a'), isNot(workspaceSocketPath('/home/me/repo-b'))); }); test('socketDirectory uses XDG_RUNTIME_DIR on Linux when set', () { diff --git a/test/ipc/schema_v1_test.dart b/test/ipc/schema_v1_test.dart index d0f052c5..b5628625 100644 --- a/test/ipc/schema_v1_test.dart +++ b/test/ipc/schema_v1_test.dart @@ -16,12 +16,7 @@ void main() { }); test('error kinds are unique strings', () { - final kinds = { - IpcErrorKind.userError, - IpcErrorKind.toolError, - IpcErrorKind.notFound, - IpcErrorKind.conflict, - }; + final kinds = {IpcErrorKind.userError, IpcErrorKind.toolError, IpcErrorKind.notFound, IpcErrorKind.conflict}; expect(kinds.length, 4); }); }); diff --git a/test/ipc/server_events_pull_test.dart b/test/ipc/server_events_pull_test.dart index 2a1976c9..a93fcbc8 100644 --- a/test/ipc/server_events_pull_test.dart +++ b/test/ipc/server_events_pull_test.dart @@ -20,10 +20,7 @@ import 'package:test/test.dart'; Logger _silent() => Logger(minLevel: LogLevel.error, sinks: const []); -Future _connect(IpcServer s) async => Socket.connect( - InternetAddress(s.socketPath, type: InternetAddressType.unix), - 0, - ); +Future _connect(IpcServer s) async => Socket.connect(InternetAddress(s.socketPath, type: InternetAddressType.unix), 0); void _emit(DaemonBus bus, String sub, String kind, [Map data = const {}]) => bus.emit(DaemonEvent(subsystem: sub, kind: kind, data: data, ts: DateTime.now().toUtc())); @@ -62,12 +59,11 @@ void main() { try { final lines = s.cast>().transform(utf8.decoder).transform(const LineSplitter()); final it = StreamIterator(lines); - s.write('${IpcRequest(id: 'e', cmd: 'events', args: { - 'flags': { - if (since != null) 'since': since, - if (filter != null) 'filter': filter, - }, - }).encode()}\n'); + s.write( + '${IpcRequest(id: 'e', cmd: 'events', args: { + 'flags': {'since': ?since, 'filter': ?filter}, + }).encode()}\n', + ); await s.flush(); if (!await it.moveNext().timeout(const Duration(seconds: 2))) { throw StateError('no response'); diff --git a/test/ipc/server_streaming_test.dart b/test/ipc/server_streaming_test.dart index 9a2a88c1..10140d82 100644 --- a/test/ipc/server_streaming_test.dart +++ b/test/ipc/server_streaming_test.dart @@ -19,10 +19,7 @@ import 'package:test/test.dart'; Logger _silent() => Logger(minLevel: LogLevel.error, sinks: const []); -Future _connect(IpcServer s) async => Socket.connect( - InternetAddress(s.socketPath, type: InternetAddressType.unix), - 0, - ); +Future _connect(IpcServer s) async => Socket.connect(InternetAddress(s.socketPath, type: InternetAddressType.unix), 0); /// Wrap a Socket in a line iterator backed by a single broadcast /// stream so the same connection can read multiple framed lines. @@ -50,15 +47,12 @@ Future _send(Socket s, IpcRequest req) async { } IpcRequest _tailReq({String? filter, String id = 't'}) => IpcRequest( - id: id, - cmd: 'tail', - args: { - 'flags': { - 'events': true, - if (filter != null) 'filter': filter, - }, - }, - ); + id: id, + cmd: 'tail', + args: { + 'flags': {'events': true, 'filter': ?filter}, + }, +); void main() { late Directory ws; @@ -70,8 +64,13 @@ void main() { ws = await Directory.systemTemp.createTemp('clide-stream-test-'); dispatcher = DaemonDispatcher(); bus = DaemonBus(); - server = - IpcServer(dispatcher: dispatcher, workspaceRoot: '${ws.path}/${DateTime.now().microsecondsSinceEpoch}', log: _silent(), events: bus, replayDepth: 4); + server = IpcServer( + dispatcher: dispatcher, + workspaceRoot: '${ws.path}/${DateTime.now().microsecondsSinceEpoch}', + log: _silent(), + events: bus, + replayDepth: 4, + ); await server.start(); }); @@ -213,10 +212,15 @@ void main() { final r = _lineReader(s); final q = _Lines(r.lines); await _send( - s, - IpcRequest(id: 'a', cmd: '_argv', args: { + s, + IpcRequest( + id: 'a', + cmd: '_argv', + args: { 'argv': ['tail', '--events', '--filter', 'pane'], - })); + }, + ), + ); final ack = IpcMessage.decode(await q.next()) as IpcResponse; expect(ack.ok, isTrue); expect(ack.data['streaming'], isTrue); diff --git a/test/ipc/server_test.dart b/test/ipc/server_test.dart index b62c91e4..bc16ca4e 100644 --- a/test/ipc/server_test.dart +++ b/test/ipc/server_test.dart @@ -94,10 +94,7 @@ void main() { test('malformed JSON line surfaces a userError', () async { server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog()); await server.start(); - final c = await Socket.connect( - InternetAddress(server.socketPath, type: InternetAddressType.unix), - 0, - ); + final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0); c.write('{not json\n'); await c.flush(); final line = await c.cast>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 2)); @@ -175,10 +172,7 @@ void main() { test('stop closes an in-flight client connection', () async { server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog()); await server.start(); - final c = await Socket.connect( - InternetAddress(server.socketPath, type: InternetAddressType.unix), - 0, - ); + final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0); c.write('${IpcRequest(id: 'q', cmd: 'ping').encode()}\n'); await c.flush(); await c.cast>().transform(utf8.decoder).transform(const LineSplitter()).first; @@ -192,10 +186,7 @@ void main() { test('multiple sequential requests on the same connection each get a reply', () async { server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog()); await server.start(); - final c = await Socket.connect( - InternetAddress(server.socketPath, type: InternetAddressType.unix), - 0, - ); + final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0); final replies = c.cast>().transform(utf8.decoder).transform(const LineSplitter()); final iter = StreamIterator(replies); for (var i = 0; i < 3; i++) { @@ -239,10 +230,7 @@ void main() { test('a non-request message (e.g. event) surfaces a userError', () async { server = IpcServer(dispatcher: dispatcher, workspaceRoot: workRoot, log: _silentLog()); await server.start(); - final c = await Socket.connect( - InternetAddress(server.socketPath, type: InternetAddressType.unix), - 0, - ); + final c = await Socket.connect(InternetAddress(server.socketPath, type: InternetAddressType.unix), 0); final evt = IpcEvent(subsystem: 'test', kind: 'wrong-shape', timestamp: DateTime.now().toUtc()); c.write('${evt.encode()}\n'); await c.flush(); @@ -258,10 +246,7 @@ void main() { Logger _silentLog() => Logger(minLevel: LogLevel.error, sinks: const []); Future _roundTrip(String socketPath, IpcRequest req) async { - final c = await Socket.connect( - InternetAddress(socketPath, type: InternetAddressType.unix), - 0, - ); + final c = await Socket.connect(InternetAddress(socketPath, type: InternetAddressType.unix), 0); c.write('${req.encode()}\n'); await c.flush(); final line = await c.cast>().transform(utf8.decoder).transform(const LineSplitter()).first.timeout(const Duration(seconds: 2)); diff --git a/test/kernel/cli_install_test.dart b/test/kernel/cli_install_test.dart index fb96869e..87a25c40 100644 --- a/test/kernel/cli_install_test.dart +++ b/test/kernel/cli_install_test.dart @@ -25,37 +25,19 @@ void main() { return f; } - CliInstaller installer({ - required String resolvedExecutable, - required Map env, - List? candidates, - String? installDir, - }) => - CliInstaller( - resolvedExecutable: resolvedExecutable, - env: env, - bundledClientCandidates: candidates, - installDir: installDir, - ); + CliInstaller installer({required String resolvedExecutable, required Map env, List? candidates, String? installDir}) => + CliInstaller(resolvedExecutable: resolvedExecutable, env: env, bundledClientCandidates: candidates, installDir: installDir); group('findBundledClient', () { test('returns the first candidate that exists', () { final present = '${tmp.path}/clide-cli'; touchExec(present); - final i = installer( - resolvedExecutable: '${tmp.path}/clide', - env: {'PATH': ''}, - candidates: ['${tmp.path}/missing', present], - ); + final i = installer(resolvedExecutable: '${tmp.path}/clide', env: {'PATH': ''}, candidates: ['${tmp.path}/missing', present]); expect(i.findBundledClient(), present); }); test('returns null when no candidate exists', () { - final i = installer( - resolvedExecutable: '${tmp.path}/clide', - env: {'PATH': ''}, - candidates: ['${tmp.path}/nope'], - ); + final i = installer(resolvedExecutable: '${tmp.path}/clide', env: {'PATH': ''}, candidates: ['${tmp.path}/nope']); expect(i.findBundledClient(), isNull); }); }); @@ -63,20 +45,14 @@ void main() { group('inspect', () { test('missing when no clide on PATH', () { final binDir = Directory('${tmp.path}/bin')..createSync(); - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir.path}, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir.path}); expect(i.inspect().state, CliInstallState.missing); }); test('installed when clide is a plain non-GUI binary', () { final binDir = Directory('${tmp.path}/bin')..createSync(); touchExec('${binDir.path}/clide'); - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir.path}, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir.path}); final s = i.inspect(); expect(s.state, CliInstallState.installed); expect(s.pathEntry, '${binDir.path}/clide'); @@ -86,10 +62,7 @@ void main() { final gui = touchExec('${tmp.path}/gui/clide').path; final binDir = Directory('${tmp.path}/bin')..createSync(); Link('${binDir.path}/clide').createSync(gui); - final i = installer( - resolvedExecutable: gui, - env: {'PATH': binDir.path}, - ); + final i = installer(resolvedExecutable: gui, env: {'PATH': binDir.path}); final s = i.inspect(); expect(s.state, CliInstallState.staleGui); expect(s.needsInstall, isTrue); @@ -113,10 +86,7 @@ void main() { final dev = touchExec('${tmp.path}/native/linux-x64/clide').path; final binDir = Directory('${tmp.path}/bin')..createSync(); Link('${binDir.path}/clide').createSync(dev); - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir.path}, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir.path}); final s = i.inspect(); expect(s.state, CliInstallState.devTree); // A dev build is intentional — it doesn't prompt a reinstall. @@ -140,20 +110,14 @@ void main() { group('defaults', () { test('install dir and bundled candidates derive from env + exe dir', () { - final i = CliInstaller( - resolvedExecutable: '/opt/clide/bundle/clide', - env: const {'HOME': '/home/dev', 'CLIDE_CLI_BIN': '/dev/tree/clide'}, - ); + final i = CliInstaller(resolvedExecutable: '/opt/clide/bundle/clide', env: const {'HOME': '/home/dev', 'CLIDE_CLI_BIN': '/dev/tree/clide'}); expect(i.installDir, '/home/dev/.local/bin'); // CLIDE_CLI_BIN override first, then the in-bundle path. expect(i.bundledClientCandidates, ['/dev/tree/clide', '/opt/clide/bundle/clide-cli']); }); test('omits the CLIDE_CLI_BIN candidate when unset', () { - final i = CliInstaller( - resolvedExecutable: '/opt/clide/bundle/clide', - env: const {'HOME': '/home/dev'}, - ); + final i = CliInstaller(resolvedExecutable: '/opt/clide/bundle/clide', env: const {'HOME': '/home/dev'}); expect(i.bundledClientCandidates, ['/opt/clide/bundle/clide-cli']); }); @@ -186,12 +150,7 @@ void main() { group('install', () { test('fails clearly when no bundled client is present', () { - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': ''}, - candidates: ['${tmp.path}/none'], - installDir: '${tmp.path}/bin', - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': ''}, candidates: ['${tmp.path}/none'], installDir: '${tmp.path}/bin'); final r = i.install(); expect(r.ok, isFalse); expect(r.message, contains('No bundled clide client')); @@ -200,12 +159,7 @@ void main() { test('copies the client, marks it executable, reports onPath', () { final src = touchExec('${tmp.path}/bundle/clide-cli', contents: '#!/bin/sh\necho hi\n'); final binDir = '${tmp.path}/bin'; - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir}, - candidates: [src.path], - installDir: binDir, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir}, candidates: [src.path], installDir: binDir); final r = i.install(); expect(r.ok, isTrue); expect(r.onPath, isTrue); @@ -222,12 +176,7 @@ void main() { test('flags fromDevTree when the source is a dev-tree build (T-256)', () { final src = touchExec('${tmp.path}/native/linux-x64/clide'); final binDir = '${tmp.path}/bin'; - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir}, - candidates: [src.path], - installDir: binDir, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir}, candidates: [src.path], installDir: binDir); final r = i.install(); expect(r.ok, isTrue); expect(r.fromDevTree, isTrue); @@ -237,12 +186,7 @@ void main() { test('a bundled (non-dev) source is not flagged fromDevTree', () { final src = touchExec('${tmp.path}/bundle/clide-cli'); final binDir = '${tmp.path}/bin'; - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': binDir}, - candidates: [src.path], - installDir: binDir, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': binDir}, candidates: [src.path], installDir: binDir); expect(i.install().fromDevTree, isFalse); }); @@ -252,12 +196,7 @@ void main() { final binDir = '${tmp.path}/bin'; Directory(binDir).createSync(); Link('$binDir/clide').createSync(gui); // stale symlink into the GUI - final i = installer( - resolvedExecutable: gui, - env: {'PATH': binDir}, - candidates: [src.path], - installDir: binDir, - ); + final i = installer(resolvedExecutable: gui, env: {'PATH': binDir}, candidates: [src.path], installDir: binDir); expect(i.install().ok, isTrue); // The GUI binary must be untouched; the bin entry is now a real file. expect(File(gui).readAsStringSync(), 'GUI'); @@ -270,12 +209,7 @@ void main() { // installDir path is occupied by a regular file → createSync throws. final blocker = '${tmp.path}/blocked'; File(blocker).writeAsStringSync('not a dir'); - final i = installer( - resolvedExecutable: '${tmp.path}/gui/clide', - env: {'PATH': ''}, - candidates: [src.path], - installDir: blocker, - ); + final i = installer(resolvedExecutable: '${tmp.path}/gui/clide', env: {'PATH': ''}, candidates: [src.path], installDir: blocker); final r = i.install(); expect(r.ok, isFalse); expect(r.message, contains('Install failed')); diff --git a/test/kernel/src/commands/keybindings_test.dart b/test/kernel/src/commands/keybindings_test.dart index c2a9b669..744bdaba 100644 --- a/test/kernel/src/commands/keybindings_test.dart +++ b/test/kernel/src/commands/keybindings_test.dart @@ -80,31 +80,19 @@ void main() { tearDown(() => kb.clearState()); test('returns null for KeyUpEvent', () { - final up = KeyUpEvent( - physicalKey: PhysicalKeyboardKey.keyG, - logicalKey: LogicalKeyboardKey.keyG, - timeStamp: Duration.zero, - ); + final up = KeyUpEvent(physicalKey: PhysicalKeyboardKey.keyG, logicalKey: LogicalKeyboardKey.keyG, timeStamp: Duration.zero); expect(KeybindingResolver.fromKeyEvent(up, kb), isNull); }); test('returns null when logicalKey has no keyLabel', () { // A synthetic logical key with an unassigned id has an empty label. final unlabeled = LogicalKeyboardKey(0x1000fffff); - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.controlLeft, - logicalKey: unlabeled, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.controlLeft, logicalKey: unlabeled, timeStamp: Duration.zero); expect(KeybindingResolver.fromKeyEvent(down, kb), isNull); }); test('maps a plain KeyDownEvent to a modifier-free Keybinding', () { - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.keyG, - logicalKey: LogicalKeyboardKey.keyG, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.keyG, logicalKey: LogicalKeyboardKey.keyG, timeStamp: Duration.zero); final b = KeybindingResolver.fromKeyEvent(down, kb); expect(b, isNotNull); expect(b!.key, 'g'); @@ -118,11 +106,7 @@ void main() { _holdModifier(PhysicalKeyboardKey.altLeft, LogicalKeyboardKey.altLeft); _holdModifier(PhysicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaLeft); - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.keyG, - logicalKey: LogicalKeyboardKey.keyG, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.keyG, logicalKey: LogicalKeyboardKey.keyG, timeStamp: Duration.zero); final b = KeybindingResolver.fromKeyEvent(down, kb)!; expect(b.key, 'g'); expect(b.modifiers.toSet(), {'ctrl', 'shift', 'alt', 'cmd'}); @@ -131,7 +115,5 @@ void main() { } void _holdModifier(PhysicalKeyboardKey physical, LogicalKeyboardKey logical) { - HardwareKeyboard.instance.handleKeyEvent( - KeyDownEvent(physicalKey: physical, logicalKey: logical, timeStamp: Duration.zero), - ); + HardwareKeyboard.instance.handleKeyEvent(KeyDownEvent(physicalKey: physical, logicalKey: logical, timeStamp: Duration.zero)); } diff --git a/test/kernel/src/commands/palette_test.dart b/test/kernel/src/commands/palette_test.dart index 4a6f50bf..4c04df4a 100644 --- a/test/kernel/src/commands/palette_test.dart +++ b/test/kernel/src/commands/palette_test.dart @@ -4,11 +4,11 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter_test/flutter_test.dart'; CommandContribution _cmd(String id, {String? title}) => CommandContribution( - id: id, - command: id, - title: title, - run: (_) async => IpcResponse.ok(id: '', data: const {}), - ); + id: id, + command: id, + title: title, + run: (_) async => IpcResponse.ok(id: '', data: const {}), +); void main() { group('PaletteController', () { @@ -39,15 +39,9 @@ void main() { test('filter is case-insensitive against title or command', () { palette.setFilter('git'); - expect( - palette.filtered().map((c) => c.command).toSet(), - {'git.commit', 'git.push'}, - ); + expect(palette.filtered().map((c) => c.command).toSet(), {'git.commit', 'git.push'}); palette.setFilter('PICK'); - expect( - palette.filtered().map((c) => c.command).toSet(), - {'theme.pick'}, - ); + expect(palette.filtered().map((c) => c.command).toSet(), {'theme.pick'}); }); test('invoke closes the palette + runs the command', () async { diff --git a/test/kernel/src/commands/registry_test.dart b/test/kernel/src/commands/registry_test.dart index ff4d9b8d..d2edd5ca 100644 --- a/test/kernel/src/commands/registry_test.dart +++ b/test/kernel/src/commands/registry_test.dart @@ -3,12 +3,8 @@ import 'package:clide/extension/extension.dart'; import 'package:clide/kernel/kernel.dart'; import 'package:flutter_test/flutter_test.dart'; -CommandContribution _cmd(String name, Future Function() run) => CommandContribution( - id: name, - command: name, - title: 'cmd $name', - run: (_) => run(), - ); +CommandContribution _cmd(String name, Future Function() run) => + CommandContribution(id: name, command: name, title: 'cmd $name', run: (_) => run()); void main() { group('CommandRegistry', () { @@ -22,12 +18,7 @@ void main() { test('execute returns the handler response', () async { final r = CommandRegistry(); - r.register( - _cmd( - 'ping', - () async => IpcResponse.ok(id: '', data: const {'pong': true}), - ), - ); + r.register(_cmd('ping', () async => IpcResponse.ok(id: '', data: const {'pong': true}))); final resp = await r.execute('ping'); expect(resp.ok, true); expect(resp.data['pong'], true); diff --git a/test/kernel/src/events/types_test.dart b/test/kernel/src/events/types_test.dart index fb3837dc..7aa44671 100644 --- a/test/kernel/src/events/types_test.dart +++ b/test/kernel/src/events/types_test.dart @@ -48,12 +48,7 @@ void main() { test('DaemonEvent merges ts into payload', () { final ts = DateTime.utc(2026, 5, 11, 12, 0, 0); - final e = DaemonEvent( - subsystem: 'pty', - kind: 'output', - data: {'bytes': 'aGVsbG8='}, - ts: ts, - ); + final e = DaemonEvent(subsystem: 'pty', kind: 'output', data: {'bytes': 'aGVsbG8='}, ts: ts); expect(e.subsystem, 'pty'); expect(e.kind, 'output'); expect(e.payload()['ts'], ts.toIso8601String()); @@ -63,22 +58,10 @@ void main() { group('Team events', () { test('TeamMemberJoined includes only the set optional fields in payload', () { - const minimal = TeamMemberJoined( - team: 'alpha', - agentId: 'bob@alpha', - name: 'bob', - agentType: 'reviewer', - paneId: '%3', - ); + const minimal = TeamMemberJoined(team: 'alpha', agentId: 'bob@alpha', name: 'bob', agentType: 'reviewer', paneId: '%3'); expect(minimal.subsystem, 'team'); expect(minimal.kind, 'member-joined'); - expect(minimal.payload(), { - 'team': 'alpha', - 'agentId': 'bob@alpha', - 'name': 'bob', - 'agentType': 'reviewer', - 'paneId': '%3', - }); + expect(minimal.payload(), {'team': 'alpha', 'agentId': 'bob@alpha', 'name': 'bob', 'agentType': 'reviewer', 'paneId': '%3'}); const full = TeamMemberJoined( team: 'alpha', diff --git a/test/kernel/src/extensions_manager_test.dart b/test/kernel/src/extensions_manager_test.dart index f48511f8..fac47d84 100644 --- a/test/kernel/src/extensions_manager_test.dart +++ b/test/kernel/src/extensions_manager_test.dart @@ -8,13 +8,7 @@ import '../../helpers/kernel_fixture.dart'; /// Minimal no-op extension used as a test actor. class _Ext extends ClideExtension { - _Ext({ - required this.id, - this.dependsOn = const [], - this.contributions = const [], - this.onActivate, - this.onDeactivate, - }); + _Ext({required this.id, this.dependsOn = const [], this.contributions = const [], this.onActivate, this.onDeactivate}); @override final String id; @@ -56,53 +50,30 @@ void main() { test('register + activateAll runs extensions in dep order', () async { final order = []; f.services.extensions - ..register(_Ext( - id: 'a', - onActivate: (_) async => order.add('a'), - )) - ..register(_Ext( - id: 'b', - dependsOn: const ['a'], - onActivate: (_) async => order.add('b'), - )) - ..register(_Ext( - id: 'c', - dependsOn: const ['b'], - onActivate: (_) async => order.add('c'), - )); + ..register(_Ext(id: 'a', onActivate: (_) async => order.add('a'))) + ..register(_Ext(id: 'b', dependsOn: const ['a'], onActivate: (_) async => order.add('b'))) + ..register(_Ext(id: 'c', dependsOn: const ['b'], onActivate: (_) async => order.add('c'))); await f.services.extensions.activateAll(); expect(order, ['a', 'b', 'c']); }); test('missing dep skips the dependent with a warning', () async { final order = []; - f.services.extensions.register(_Ext( - id: 'needs-missing', - dependsOn: const ['does.not.exist'], - onActivate: (_) async => order.add('needs-missing'), - )); + f.services.extensions.register(_Ext(id: 'needs-missing', dependsOn: const ['does.not.exist'], onActivate: (_) async => order.add('needs-missing'))); await f.services.extensions.activateAll(); expect(order, isEmpty); expect(f.services.extensions.isActivated('needs-missing'), false); }); test('contribution points wire into panel registry on activate', () async { - f.services.extensions.register(_Ext( - id: 'with-tab', - contributions: [ - TabContribution( - id: 'with-tab.view', - slot: Slots.workspace, - title: 'T', - build: (_) => const SizedBox.shrink(), - ), - ], - )); - await f.services.extensions.activateAll(); - expect( - f.services.panels.tabsFor(Slots.workspace).map((t) => t.id), - ['with-tab.view'], + f.services.extensions.register( + _Ext( + id: 'with-tab', + contributions: [TabContribution(id: 'with-tab.view', slot: Slots.workspace, title: 'T', build: (_) => const SizedBox.shrink())], + ), ); + await f.services.extensions.activateAll(); + expect(f.services.panels.tabsFor(Slots.workspace).map((t) => t.id), ['with-tab.view']); }); test('activating an extension auto-loads its localized tab namespace (T-155)', () async { @@ -119,46 +90,37 @@ void main() { addTearDown(local.dispose); // Not registered yet → string() falls back to the placeholder. - expect( - local.services.i18n.string('tab.title', namespace: 'ext.localized', placeholder: 'fallback'), - 'fallback', - ); + expect(local.services.i18n.string('tab.title', namespace: 'ext.localized', placeholder: 'fallback'), 'fallback'); - local.services.extensions.register(_Ext( - id: 'ext.localized', - contributions: [ - TabContribution( - id: 'ext.localized.view', - slot: Slots.workspace, - title: 'Fallback', - titleKey: 'tab.title', - i18nNamespace: 'ext.localized', - build: (_) => const SizedBox.shrink(), - ), - ], - )); + local.services.extensions.register( + _Ext( + id: 'ext.localized', + contributions: [ + TabContribution( + id: 'ext.localized.view', + slot: Slots.workspace, + title: 'Fallback', + titleKey: 'tab.title', + i18nNamespace: 'ext.localized', + build: (_) => const SizedBox.shrink(), + ), + ], + ), + ); await local.services.extensions.activateAll(); // Activation auto-loaded the catalog → resolves, no "namespace not // registered" warning. - expect( - local.services.i18n.string('tab.title', namespace: 'ext.localized', placeholder: 'fallback'), - 'Localized', - ); + expect(local.services.i18n.string('tab.title', namespace: 'ext.localized', placeholder: 'fallback'), 'Localized'); }); test('deactivate removes contributions from the registry', () async { - f.services.extensions.register(_Ext( - id: 'ephemeral', - contributions: [ - TabContribution( - id: 'ephemeral.view', - slot: Slots.workspace, - title: 'T', - build: (_) => const SizedBox.shrink(), - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'ephemeral', + contributions: [TabContribution(id: 'ephemeral.view', slot: Slots.workspace, title: 'T', build: (_) => const SizedBox.shrink())], + ), + ); await f.services.extensions.activateAll(); expect(f.services.panels.tabsFor(Slots.workspace), hasLength(1)); await f.services.extensions.deactivate('ephemeral'); @@ -166,32 +128,27 @@ void main() { }); test('CommandContribution registers + default binding is bound', () async { - f.services.extensions.register(_Ext( - id: 'has-cmd', - contributions: [ - CommandContribution( - id: 'c', - command: 'test.cmd', - defaultBinding: 'ctrl+alt+k', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'has-cmd', + contributions: [ + CommandContribution( + id: 'c', + command: 'test.cmd', + defaultBinding: 'ctrl+alt+k', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ], + ), + ); await f.services.extensions.activateAll(); expect(f.services.commands.get('test.cmd'), isNotNull); - expect( - f.services.keybindings.commandFor(Keybinding.parse('ctrl+alt+k')), - 'test.cmd', - ); + expect(f.services.keybindings.commandFor(Keybinding.parse('ctrl+alt+k')), 'test.cmd'); }); test('setEnabled=false deactivates; =true reactivates', () async { final order = []; - f.services.extensions.register(_Ext( - id: 'toggle', - onActivate: (_) async => order.add('on'), - onDeactivate: () async => order.add('off'), - )); + f.services.extensions.register(_Ext(id: 'toggle', onActivate: (_) async => order.add('on'), onDeactivate: () async => order.add('off'))); await f.services.extensions.activateAll(); expect(order, ['on']); await f.services.extensions.setEnabled('toggle', false); @@ -228,16 +185,12 @@ void main() { }); test('TrayItemContribution lands in TrayRegistry; deactivate removes it', () async { - f.services.extensions.register(_Ext( - id: 'tray-ext', - contributions: [ - const TrayItemContribution( - id: 'tray-ext.item', - label: 'Item', - onSelected: _noop, - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'tray-ext', + contributions: [const TrayItemContribution(id: 'tray-ext.item', label: 'Item', onSelected: _noop)], + ), + ); await f.services.extensions.activateAll(); expect(f.services.tray.items.map((i) => i.id), contains('tray-ext.item')); await f.services.extensions.deactivate('tray-ext'); @@ -245,21 +198,15 @@ void main() { }); test('StatusItem + ToolbarButton contributions activate and deactivate cleanly', () async { - f.services.extensions.register(_Ext( - id: 'status-and-toolbar', - contributions: [ - StatusItemContribution( - id: 'status-and-toolbar.status', - priority: 1, - build: (_) => const SizedBox.shrink(), - ), - ToolbarButtonContribution( - id: 'status-and-toolbar.btn', - label: 'B', - onPressed: () {}, - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'status-and-toolbar', + contributions: [ + StatusItemContribution(id: 'status-and-toolbar.status', priority: 1, build: (_) => const SizedBox.shrink()), + ToolbarButtonContribution(id: 'status-and-toolbar.btn', label: 'B', onPressed: () {}), + ], + ), + ); await f.services.extensions.activateAll(); // Both contributions register through PanelRegistry.contributionsFor. expect(f.services.panels.contributionsFor(Slots.statusbar).whereType(), hasLength(1)); @@ -271,16 +218,12 @@ void main() { // LayoutPresetContribution is consumed by the default-layout // extension's own activate(); the manager's add/remove just hit // the no-op case branch. - f.services.extensions.register(_Ext( - id: 'preset-only', - contributions: [ - const LayoutPresetContribution( - id: 'preset-only.default', - displayName: 'Preset only', - slots: [], - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'preset-only', + contributions: [const LayoutPresetContribution(id: 'preset-only.default', displayName: 'Preset only', slots: [])], + ), + ); await f.services.extensions.activateAll(); await f.services.extensions.deactivate('preset-only'); }); @@ -298,36 +241,32 @@ void main() { }); test('activate failure is caught and logged (extension survives)', () async { - f.services.extensions.register(_Ext( - id: 'throws-on-activate', - onActivate: (_) async => throw StateError('kaboom'), - )); + f.services.extensions.register(_Ext(id: 'throws-on-activate', onActivate: (_) async => throw StateError('kaboom'))); await f.services.extensions.activateAll(); expect(f.services.extensions.isActivated('throws-on-activate'), isFalse); }); test('deactivate failure is caught and logged', () async { - f.services.extensions.register(_Ext( - id: 'throws-on-deactivate', - onDeactivate: () async => throw StateError('kaboom'), - )); + f.services.extensions.register(_Ext(id: 'throws-on-deactivate', onDeactivate: () async => throw StateError('kaboom'))); await f.services.extensions.activateAll(); expect(f.services.extensions.isActivated('throws-on-deactivate'), isTrue); await f.services.extensions.deactivate('throws-on-deactivate'); }); test('deactivating a CommandContribution removes its keybinding', () async { - f.services.extensions.register(_Ext( - id: 'with-bound-cmd', - contributions: [ - CommandContribution( - id: 'with-bound-cmd.cmd', - command: 'bound.cmd', - defaultBinding: 'ctrl+alt+j', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - ), - ], - )); + f.services.extensions.register( + _Ext( + id: 'with-bound-cmd', + contributions: [ + CommandContribution( + id: 'with-bound-cmd.cmd', + command: 'bound.cmd', + defaultBinding: 'ctrl+alt+j', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ], + ), + ); await f.services.extensions.activateAll(); expect(f.services.keybindings.commandFor(Keybinding.parse('ctrl+alt+j')), 'bound.cmd'); await f.services.extensions.deactivate('with-bound-cmd'); @@ -343,12 +282,14 @@ void main() { test('extension context exposes every kernel service via passthrough getters', () async { ClideExtensionContext? captured; - f.services.extensions.register(_Ext( - id: 'ctx-capture', - onActivate: (ctx) async { - captured = ctx; - }, - )); + f.services.extensions.register( + _Ext( + id: 'ctx-capture', + onActivate: (ctx) async { + captured = ctx; + }, + ), + ); await f.services.extensions.activateAll(); final ctx = captured!; expect(ctx.id, 'ctx-capture'); diff --git a/test/kernel/src/facade_test.dart b/test/kernel/src/facade_test.dart index d1707714..5dba6206 100644 --- a/test/kernel/src/facade_test.dart +++ b/test/kernel/src/facade_test.dart @@ -45,14 +45,7 @@ void main() { }, ); addTearDown(f.dispose); - expect( - f.services.i18n.string( - 'title', - namespace: 'builtin.welcome', - placeholder: '-', - ), - 'clide', - ); + expect(f.services.i18n.string('title', namespace: 'builtin.welcome', placeholder: '-'), 'clide'); }); test('dispose shuts down IPC + notifiers without throwing', () async { @@ -62,17 +55,21 @@ void main() { testWidgets('ClideKernel.of throws a FlutterError when no ancestor exists', (tester) async { late Object captured; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Builder(builder: (ctx) { - try { - ClideKernel.of(ctx); - } catch (e) { - captured = e; - } - return const SizedBox(); - }), - )); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Builder( + builder: (ctx) { + try { + ClideKernel.of(ctx); + } catch (e) { + captured = e; + } + return const SizedBox(); + }, + ), + ), + ); expect(captured, isA()); }); }); diff --git a/test/kernel/src/i18n/catalog_loader_test.dart b/test/kernel/src/i18n/catalog_loader_test.dart index 0ffcb537..ca4e7088 100644 --- a/test/kernel/src/i18n/catalog_loader_test.dart +++ b/test/kernel/src/i18n/catalog_loader_test.dart @@ -29,9 +29,7 @@ class _MapAssetBundle extends CachingAssetBundle { void main() { group('AssetCatalogLoader', () { test('returns parsed JSON for a present asset', () async { - final bundle = _MapAssetBundle({ - 'lib/kernel/src/i18n/catalog/welcome_en_us.json': '{"title":{"translation":"Hi"}}', - }); + final bundle = _MapAssetBundle({'lib/kernel/src/i18n/catalog/welcome_en_us.json': '{"title":{"translation":"Hi"}}'}); final loader = AssetCatalogLoader(bundle: bundle); final r = await loader.load('welcome', const Locale('en', 'US')); expect(r['title'], isA()); @@ -44,25 +42,19 @@ void main() { }); test('returns an empty map on malformed JSON (FormatException catch)', () async { - final bundle = _MapAssetBundle({ - 'lib/kernel/src/i18n/catalog/welcome_en_us.json': 'not json at all', - }); + final bundle = _MapAssetBundle({'lib/kernel/src/i18n/catalog/welcome_en_us.json': 'not json at all'}); final loader = AssetCatalogLoader(bundle: bundle); expect(await loader.load('welcome', const Locale('en', 'US')), isEmpty); }); test('returns an empty map when the asset is blank', () async { - final bundle = _MapAssetBundle({ - 'lib/kernel/src/i18n/catalog/welcome_en_us.json': ' \n', - }); + final bundle = _MapAssetBundle({'lib/kernel/src/i18n/catalog/welcome_en_us.json': ' \n'}); final loader = AssetCatalogLoader(bundle: bundle); expect(await loader.load('welcome', const Locale('en', 'US')), isEmpty); }); test('returns an empty map when JSON parses to a non-object', () async { - final bundle = _MapAssetBundle({ - 'lib/kernel/src/i18n/catalog/welcome_en_us.json': '[1, 2, 3]', - }); + final bundle = _MapAssetBundle({'lib/kernel/src/i18n/catalog/welcome_en_us.json': '[1, 2, 3]'}); final loader = AssetCatalogLoader(bundle: bundle); expect(await loader.load('welcome', const Locale('en', 'US')), isEmpty); }); diff --git a/test/kernel/src/i18n/i18n_test.dart b/test/kernel/src/i18n/i18n_test.dart index 9fa93d2a..8d75a24e 100644 --- a/test/kernel/src/i18n/i18n_test.dart +++ b/test/kernel/src/i18n/i18n_test.dart @@ -30,11 +30,7 @@ void main() { } test('locale getters reflect constructor arguments', () { - final i = build( - catalogs: const {}, - initial: const Locale('nl', 'NL'), - defaultLocale: const Locale('en', 'US'), - ); + final i = build(catalogs: const {}, initial: const Locale('nl', 'NL'), defaultLocale: const Locale('en', 'US')); expect(i.currentLocale, const Locale('nl', 'NL')); expect(i.defaultLocale, const Locale('en', 'US')); expect(i.availableLocales, [const Locale('en', 'US'), const Locale('nl', 'NL')]); @@ -43,10 +39,7 @@ void main() { test('missing key with placeholder returns placeholder', () async { final i = build(catalogs: const {}); await i.ensureNamespaceLoaded('builtin.x'); - expect( - i.string('missing', namespace: 'builtin.x', placeholder: 'fallback'), - 'fallback', - ); + expect(i.string('missing', namespace: 'builtin.x', placeholder: 'fallback'), 'fallback'); }); test('missing key with null placeholder returns the key', () async { @@ -57,68 +50,66 @@ void main() { test('unknown namespace still returns placeholder (no crash)', () { final i = build(catalogs: const {}); - expect( - i.string('k', namespace: 'not.registered', placeholder: 'fb'), - 'fb', - ); + expect(i.string('k', namespace: 'not.registered', placeholder: 'fb'), 'fb'); }); test('exact locale hit beats fallback', () async { - final i = build(catalogs: { - 'builtin.x': { - const Locale('en', 'US'): { - 'greet': {'translation': 'Hello'}, - }, - const Locale('en'): { - 'greet': {'translation': 'Hi'}, + final i = build( + catalogs: { + 'builtin.x': { + const Locale('en', 'US'): { + 'greet': {'translation': 'Hello'}, + }, + const Locale('en'): { + 'greet': {'translation': 'Hi'}, + }, }, }, - }); - await i.ensureNamespaceLoaded('builtin.x'); - expect( - i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), - 'Hello', ); + await i.ensureNamespaceLoaded('builtin.x'); + expect(i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), 'Hello'); }); test('language-only locale hit falls through from exact', () async { - final i = build(catalogs: { - 'builtin.x': { - const Locale('nl'): { - 'greet': {'translation': 'Hoi'}, + final i = build( + catalogs: { + 'builtin.x': { + const Locale('nl'): { + 'greet': {'translation': 'Hoi'}, + }, }, }, - }, initial: const Locale('nl', 'NL')); - await i.ensureNamespaceLoaded('builtin.x'); - expect( - i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), - 'Hoi', + initial: const Locale('nl', 'NL'), ); + await i.ensureNamespaceLoaded('builtin.x'); + expect(i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), 'Hoi'); }); test('falls through to default-locale when current locale is empty', () async { - final i = build(catalogs: { - 'builtin.x': { - const Locale('en', 'US'): { - 'greet': {'translation': 'Hello'}, + final i = build( + catalogs: { + 'builtin.x': { + const Locale('en', 'US'): { + 'greet': {'translation': 'Hello'}, + }, }, }, - }, initial: const Locale('nl', 'NL')); - await i.ensureNamespaceLoaded('builtin.x'); - expect( - i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), - 'Hello', + initial: const Locale('nl', 'NL'), ); + await i.ensureNamespaceLoaded('builtin.x'); + expect(i.string('greet', namespace: 'builtin.x', placeholder: 'fb'), 'Hello'); }); test('interpolation replaces all replacers; missing ones silent', () async { - final i = build(catalogs: { - 'builtin.x': { - const Locale('en', 'US'): { - 'welcome': {'translation': 'Hi {name} at {path}'}, + final i = build( + catalogs: { + 'builtin.x': { + const Locale('en', 'US'): { + 'welcome': {'translation': 'Hi {name} at {path}'}, + }, }, }, - }); + ); await i.ensureNamespaceLoaded('builtin.x'); expect( i.interpolated( @@ -135,18 +126,20 @@ void main() { }); test('namespace isolation — same key, different values', () async { - final i = build(catalogs: { - 'a': { - const Locale('en', 'US'): { - 'k': {'translation': 'A'}, + final i = build( + catalogs: { + 'a': { + const Locale('en', 'US'): { + 'k': {'translation': 'A'}, + }, + }, + 'b': { + const Locale('en', 'US'): { + 'k': {'translation': 'B'}, + }, }, }, - 'b': { - const Locale('en', 'US'): { - 'k': {'translation': 'B'}, - }, - }, - }); + ); await i.ensureNamespaceLoaded('a'); await i.ensureNamespaceLoaded('b'); expect(i.string('k', namespace: 'a', placeholder: '-'), 'A'); @@ -154,16 +147,18 @@ void main() { }); test('setLocale refreshes cached namespaces and notifies listeners', () async { - final i = build(catalogs: { - 'x': { - const Locale('en', 'US'): { - 'k': {'translation': 'Hello'}, - }, - const Locale('nl'): { - 'k': {'translation': 'Hallo'}, + final i = build( + catalogs: { + 'x': { + const Locale('en', 'US'): { + 'k': {'translation': 'Hello'}, + }, + const Locale('nl'): { + 'k': {'translation': 'Hallo'}, + }, }, }, - }); + ); await i.ensureNamespaceLoaded('x'); var notified = 0; i.addListener(() => notified++); @@ -177,10 +172,7 @@ void main() { i.registerCatalog('ext.linear', const Locale('en', 'US'), const { 'issue.title': {'translation': 'Issues'}, }); - expect( - i.string('issue.title', namespace: 'ext.linear', placeholder: '-'), - 'Issues', - ); + expect(i.string('issue.title', namespace: 'ext.linear', placeholder: '-'), 'Issues'); }); test('unregisterCatalog forgets a namespace', () async { @@ -196,11 +188,13 @@ void main() { test('plain-string shape (no nested translation) is accepted', () async { // Forward-compat: if a catalog later switches to `"k": "v"` // instead of `"k": {"translation": "v"}`, lookup still works. - final i = build(catalogs: { - 'x': { - const Locale('en', 'US'): {'k': 'direct'}, + final i = build( + catalogs: { + 'x': { + const Locale('en', 'US'): {'k': 'direct'}, + }, }, - }); + ); await i.ensureNamespaceLoaded('x'); expect(i.string('k', namespace: 'x', placeholder: '-'), 'direct'); }); @@ -208,23 +202,12 @@ void main() { group('FallbackChain.resolve', () { test('ordering: exact, lang, default, default-lang', () { - final chain = const FallbackChain( - current: Locale('nl', 'NL'), - defaultLocale: Locale('en', 'US'), - ).resolve(); - expect(chain.map((l) => l.toString()).toList(), [ - 'nl_NL', - 'nl', - 'en_US', - 'en', - ]); + final chain = const FallbackChain(current: Locale('nl', 'NL'), defaultLocale: Locale('en', 'US')).resolve(); + expect(chain.map((l) => l.toString()).toList(), ['nl_NL', 'nl', 'en_US', 'en']); }); test('deduplicates when current == default', () { - final chain = const FallbackChain( - current: Locale('en', 'US'), - defaultLocale: Locale('en', 'US'), - ).resolve(); + final chain = const FallbackChain(current: Locale('en', 'US'), defaultLocale: Locale('en', 'US')).resolve(); expect(chain, ['en_US', 'en'].map((_) => isA())); expect(chain.length, 2); }); diff --git a/test/kernel/src/ipc/client_test.dart b/test/kernel/src/ipc/client_test.dart index 474c74d3..b238f3be 100644 --- a/test/kernel/src/ipc/client_test.dart +++ b/test/kernel/src/ipc/client_test.dart @@ -113,15 +113,8 @@ void main() { await daemon.waitForClient(); await Future.delayed(const Duration(milliseconds: 50)); - final eventFuture = bus.stream.firstWhere( - (e) => e.event is DaemonEvent, - ); - final ev = IpcEvent( - subsystem: 'pty', - kind: 'output', - data: {'bytes': 'aGVsbG8='}, - timestamp: DateTime.now(), - ); + final eventFuture = bus.stream.firstWhere((e) => e.event is DaemonEvent); + final ev = IpcEvent(subsystem: 'pty', kind: 'output', data: {'bytes': 'aGVsbG8='}, timestamp: DateTime.now()); daemon.send(ev.encode()); final received = await eventFuture.timeout(const Duration(seconds: 2)); final daemonEvent = received.event as DaemonEvent; diff --git a/test/kernel/src/keymap/editor_presets_test.dart b/test/kernel/src/keymap/editor_presets_test.dart index 19887a9f..1c795f36 100644 --- a/test/kernel/src/keymap/editor_presets_test.dart +++ b/test/kernel/src/keymap/editor_presets_test.dart @@ -36,11 +36,7 @@ void main() { // Build a service whose bundle serves the real shipped preset content. Future activate(String preset) async { final src = File('assets/keymaps/$preset.yaml').readAsStringSync(); - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/$preset.yaml': src}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/$preset.yaml': src})); await svc.setPreset(preset); return svc; } @@ -72,11 +68,13 @@ void main() { test('the Ctrl+K Ctrl+T sequence binds the theme picker', () async { final svc = await activate('vscode'); - final hit = svc.keymap!.effectiveBindings.any((b) => - b.sequence.length == 2 && - b.sequence[0] == KeyChord.parse('ctrl+k') && - b.sequence[1] == KeyChord.parse('ctrl+t') && - isCommand(b.intent, 'theme.pick')); + final hit = svc.keymap!.effectiveBindings.any( + (b) => + b.sequence.length == 2 && + b.sequence[0] == KeyChord.parse('ctrl+k') && + b.sequence[1] == KeyChord.parse('ctrl+t') && + isCommand(b.intent, 'theme.pick'), + ); expect(hit, isTrue); }); }); diff --git a/test/kernel/src/keymap/key_chord_test.dart b/test/kernel/src/keymap/key_chord_test.dart index 85ef9217..75224f47 100644 --- a/test/kernel/src/keymap/key_chord_test.dart +++ b/test/kernel/src/keymap/key_chord_test.dart @@ -102,29 +102,17 @@ void main() { tearDown(() => kb.clearState()); test('returns null for non-KeyDown / non-Repeat events', () { - final up = KeyUpEvent( - physicalKey: PhysicalKeyboardKey.keyA, - logicalKey: LogicalKeyboardKey.keyA, - timeStamp: Duration.zero, - ); + final up = KeyUpEvent(physicalKey: PhysicalKeyboardKey.keyA, logicalKey: LogicalKeyboardKey.keyA, timeStamp: Duration.zero); expect(KeyChord.fromKeyEvent(up, kb), isNull); }); test('returns null for a bare modifier press', () { - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.controlLeft, - logicalKey: LogicalKeyboardKey.controlLeft, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.controlLeft, logicalKey: LogicalKeyboardKey.controlLeft, timeStamp: Duration.zero); expect(KeyChord.fromKeyEvent(down, kb), isNull); }); test('maps a plain key down to a modifier-free chord', () { - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.keyA, - logicalKey: LogicalKeyboardKey.keyA, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.keyA, logicalKey: LogicalKeyboardKey.keyA, timeStamp: Duration.zero); final chord = KeyChord.fromKeyEvent(down, kb)!; expect(chord.modifiers, isEmpty); expect(chord.key, LogicalKeyboardKey.keyA); @@ -140,11 +128,7 @@ void main() { ]) { kb.handleKeyEvent(KeyDownEvent(physicalKey: m.$1, logicalKey: m.$2, timeStamp: Duration.zero)); } - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.keyP, - logicalKey: LogicalKeyboardKey.keyP, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.keyP, logicalKey: LogicalKeyboardKey.keyP, timeStamp: Duration.zero); final chord = KeyChord.fromKeyEvent(down, kb)!; expect(chord.key, LogicalKeyboardKey.keyP); expect(chord.modifiers.toSet(), {KeyModifier.ctrl, KeyModifier.alt, KeyModifier.shift, KeyModifier.meta}); diff --git a/test/kernel/src/keymap/keymap_service_test.dart b/test/kernel/src/keymap/keymap_service_test.dart index 28e4dc3b..36f13533 100644 --- a/test/kernel/src/keymap/keymap_service_test.dart +++ b/test/kernel/src/keymap/keymap_service_test.dart @@ -33,9 +33,7 @@ void main() { final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n'}), ); await svc.load(); expect(svc.keymap, isNotNull); @@ -47,9 +45,7 @@ void main() { final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/vscode.yaml': 'name: vscode\nbindings:\n - intent: palette.open\n keys: ctrl+shift+p\n', - }), + bundle: _bundle({'assets/keymaps/vscode.yaml': 'name: vscode\nbindings:\n - intent: palette.open\n keys: ctrl+shift+p\n'}), ); await svc.load(); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+shift+p'), const {}), isA()); @@ -63,33 +59,25 @@ void main() { }); test('layers a user file on top of the preset', () async { - await File('${appDir.path}/keybindings.yaml').writeAsString( - 'name: user\nbindings:\n - intent: activate\n keys: ctrl+p\n', - ); + await File('${appDir.path}/keybindings.yaml').writeAsString('name: user\nbindings:\n - intent: activate\n keys: ctrl+p\n'); final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: palette.open\n keys: ctrl+p\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: palette.open\n keys: ctrl+p\n'}), ); await svc.load(); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+p'), const {}), isA()); }); test('layers a settings overlay above the user file', () async { - await File('${appDir.path}/keybindings.yaml').writeAsString( - 'name: user\nbindings:\n - intent: activate\n keys: ctrl+p\n', - ); + await File('${appDir.path}/keybindings.yaml').writeAsString('name: user\nbindings:\n - intent: activate\n keys: ctrl+p\n'); await settings.set>(kKeymapOverridesSetting, [ {'intent': 'dismiss', 'keys': 'ctrl+p'}, ]); final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: palette.open\n keys: ctrl+p\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: palette.open\n keys: ctrl+p\n'}), ); await svc.load(); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+p'), const {}), isA()); @@ -100,9 +88,7 @@ void main() { final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n'}), ); await svc.load(); expect(svc.keymap!.resolve(KeyChord.parse('escape'), const {}), isA()); @@ -115,9 +101,7 @@ void main() { final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n'}), ); await svc.load(); expect(svc.keymap!.resolve(KeyChord.parse('escape'), const {}), isA()); @@ -126,11 +110,7 @@ void main() { group('registerCommandBinding / unregisterCommandBindings', () { test('adds an InvokeCommandIntent that resolves after load', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.registerCommandBinding('ctrl+shift+g', 'git.commit'); final intent = svc.keymap!.resolve(KeyChord.parse('ctrl+shift+g'), const {}); @@ -140,11 +120,7 @@ void main() { }); test('honours a when-clause on the contribution', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.registerCommandBinding('ctrl+s', 'editor.save', when: 'editor.focused'); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+s'), const {}), isNull); @@ -152,25 +128,15 @@ void main() { }); test('user file overrides a contributed binding for the same chord', () async { - await File('${appDir.path}/keybindings.yaml').writeAsString( - 'name: user\nbindings:\n - intent: dismiss\n keys: ctrl+x\n', - ); - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + await File('${appDir.path}/keybindings.yaml').writeAsString('name: user\nbindings:\n - intent: dismiss\n keys: ctrl+x\n'); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.registerCommandBinding('ctrl+x', 'editor.cut'); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+x'), const {}), isA()); }); test('unregisterCommandBindings removes prior contributions', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.registerCommandBinding('ctrl+x', 'editor.cut'); expect(svc.keymap!.resolve(KeyChord.parse('ctrl+x'), const {}), isA()); @@ -179,11 +145,7 @@ void main() { }); test('unregister of an unknown command is a no-op', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.unregisterCommandBindings('nothing-registered'); // doesn't throw }); @@ -191,11 +153,7 @@ void main() { group('scope flags', () { test('setScopeFlag updates the context; notifies listeners on change', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); var notified = 0; svc.addListener(() => notified++); @@ -209,11 +167,7 @@ void main() { }); test('clearScopeFlag removes the entry; no-op when absent', () async { - final svc = KeymapService( - settings: settings, - appDir: appDir, - bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'}), - ); + final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings: []\n'})); await svc.load(); svc.setScopeFlag('foo', true); svc.clearScopeFlag('foo'); @@ -243,11 +197,7 @@ void main() { group('resolveEvent', () { test('returns null before load()', () { final svc = KeymapService(settings: settings, appDir: appDir, bundle: _bundle(const {})); - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.escape, - logicalKey: LogicalKeyboardKey.escape, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.escape, logicalKey: LogicalKeyboardKey.escape, timeStamp: Duration.zero); expect(svc.resolveEvent(down, HardwareKeyboard.instance), isNull); }); @@ -255,16 +205,10 @@ void main() { final svc = KeymapService( settings: settings, appDir: appDir, - bundle: _bundle({ - 'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n', - }), + bundle: _bundle({'assets/keymaps/default.yaml': 'name: default\nbindings:\n - intent: dismiss\n keys: escape\n'}), ); await svc.load(); - final down = KeyDownEvent( - physicalKey: PhysicalKeyboardKey.escape, - logicalKey: LogicalKeyboardKey.escape, - timeStamp: Duration.zero, - ); + final down = KeyDownEvent(physicalKey: PhysicalKeyboardKey.escape, logicalKey: LogicalKeyboardKey.escape, timeStamp: Duration.zero); expect(svc.resolveEvent(down, HardwareKeyboard.instance), isA()); }); }); diff --git a/test/kernel/src/keymap/shipped_presets_test.dart b/test/kernel/src/keymap/shipped_presets_test.dart index 4bf2d9bb..30fcc970 100644 --- a/test/kernel/src/keymap/shipped_presets_test.dart +++ b/test/kernel/src/keymap/shipped_presets_test.dart @@ -45,9 +45,7 @@ void main() { }); test('default.yaml binds Tab / Shift+Tab to focus traversal', () { - final layer = KeymapLayer.fromYaml( - File('assets/keymaps/default.yaml').readAsStringSync(), - ); + final layer = KeymapLayer.fromYaml(File('assets/keymaps/default.yaml').readAsStringSync()); expect(layer.bindings.any((b) => b.intent is NextFocusIntent), isTrue); expect(layer.bindings.any((b) => b.intent is PreviousFocusIntent), isTrue); }); diff --git a/test/kernel/src/log_test.dart b/test/kernel/src/log_test.dart index cbd9ea9a..ebd42f2c 100644 --- a/test/kernel/src/log_test.dart +++ b/test/kernel/src/log_test.dart @@ -5,10 +5,7 @@ void main() { group('Logger', () { test('respects minLevel — lower-level messages drop silently', () { final out = []; - final log = Logger( - minLevel: LogLevel.warn, - sinks: [out.add], - ); + final log = Logger(minLevel: LogLevel.warn, sinks: [out.add]); log.debug('s', 'dropped'); log.info('s', 'dropped'); log.warn('s', 'kept'); @@ -38,10 +35,7 @@ void main() { test('broken sink does not kill the logger', () { final good = []; - final log = Logger(minLevel: LogLevel.info, sinks: [ - (_) => throw StateError('bad sink'), - good.add, - ]); + final log = Logger(minLevel: LogLevel.info, sinks: [(_) => throw StateError('bad sink'), good.add]); log.info('s', 'still delivered'); expect(good, hasLength(1)); }); diff --git a/test/kernel/src/panels/drag_resize_test.dart b/test/kernel/src/panels/drag_resize_test.dart index a720de06..f47f2f92 100644 --- a/test/kernel/src/panels/drag_resize_test.dart +++ b/test/kernel/src/panels/drag_resize_test.dart @@ -19,28 +19,28 @@ void main() { testWidgets('horizontal drag adjusts the sidebar slot size', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), - LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), - ], - )); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 40, - height: 200, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.sidebar, - axis: Axis.horizontal, + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [ + LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), + LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), + ], + ), + ); + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 40, + height: 200, + child: DragResizeHandle(arrangement: arr, slot: Slots.sidebar, axis: Axis.horizontal), ), ), ), - )); + ); await tester.pumpAndSettle(); final center = tester.getCenter(find.byType(DragResizeHandle)); final gesture = await tester.startGesture(center, kind: PointerDeviceKind.mouse); @@ -53,28 +53,28 @@ void main() { testWidgets('contextPanel drag inverts the delta sign', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), - LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), - ], - )); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 40, - height: 200, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.contextPanel, - axis: Axis.horizontal, + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [ + LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), + LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), + ], + ), + ); + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 40, + height: 200, + child: DragResizeHandle(arrangement: arr, slot: Slots.contextPanel, axis: Axis.horizontal), ), ), ), - )); + ); await tester.pumpAndSettle(); final center = tester.getCenter(find.byType(DragResizeHandle)); final gesture = await tester.startGesture(center, kind: PointerDeviceKind.mouse); @@ -86,28 +86,26 @@ void main() { testWidgets('exposes a slider Semantics node with the current size', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 240), - ], - )); + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 240)], + ), + ); final semHandle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 40, - height: 200, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.sidebar, - axis: Axis.horizontal, + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 40, + height: 200, + child: DragResizeHandle(arrangement: arr, slot: Slots.sidebar, axis: Axis.horizontal), ), ), ), - )); + ); await tester.pumpAndSettle(); final data = tester.getSemantics(find.byType(DragResizeHandle)); expect(data.label, 'Sidebar width'); @@ -120,28 +118,26 @@ void main() { testWidgets('contextPanel slider Semantics label matches the slot', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 320), - ], - )); + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 320)], + ), + ); final semHandle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 40, - height: 200, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.contextPanel, - axis: Axis.horizontal, + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 40, + height: 200, + child: DragResizeHandle(arrangement: arr, slot: Slots.contextPanel, axis: Axis.horizontal), ), ), ), - )); + ); await tester.pumpAndSettle(); final data = tester.getSemantics(find.byType(DragResizeHandle)); expect(data.label, 'Context panel width'); @@ -162,28 +158,26 @@ void main() { testWidgets('vertical-axis handle uses arrow up/down shortcuts and "height" label', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.workspace, position: SlotPosition.center, defaultSize: 300), - ], - )); + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [LayoutSlot(slot: Slots.workspace, position: SlotPosition.center, defaultSize: 300)], + ), + ); final semHandle = tester.ensureSemantics(); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 200, - height: 40, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.workspace, - axis: Axis.vertical, + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 200, + height: 40, + child: DragResizeHandle(arrangement: arr, slot: Slots.workspace, axis: Axis.vertical), ), ), ), - )); + ); await tester.pumpAndSettle(); final data = tester.getSemantics(find.byType(DragResizeHandle)); // Custom (non-sidebar, non-contextPanel) slots fall through to @@ -194,28 +188,28 @@ void main() { testWidgets('hovered state flips the line colour without throwing', (tester) async { final arr = LayoutArrangement(); - arr.applyPreset(const LayoutPresetContribution( - id: 'test-preset', - displayName: 'Test', - slots: [ - LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), - LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), - ], - )); - await tester.pumpWidget(harness( - f, - Center( - child: SizedBox( - width: 40, - height: 200, - child: DragResizeHandle( - arrangement: arr, - slot: Slots.sidebar, - axis: Axis.horizontal, + arr.applyPreset( + const LayoutPresetContribution( + id: 'test-preset', + displayName: 'Test', + slots: [ + LayoutSlot(slot: Slots.sidebar, position: SlotPosition.left, defaultSize: 200), + LayoutSlot(slot: Slots.contextPanel, position: SlotPosition.right, defaultSize: 200), + ], + ), + ); + await tester.pumpWidget( + harness( + f, + Center( + child: SizedBox( + width: 40, + height: 200, + child: DragResizeHandle(arrangement: arr, slot: Slots.sidebar, axis: Axis.horizontal), ), ), ), - )); + ); await tester.pumpAndSettle(); // Hover over the handle. final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); diff --git a/test/kernel/src/panels/registry_test.dart b/test/kernel/src/panels/registry_test.dart index b0446b81..f6d5b8e0 100644 --- a/test/kernel/src/panels/registry_test.dart +++ b/test/kernel/src/panels/registry_test.dart @@ -3,18 +3,8 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -TabContribution _tab({ - required String id, - required SlotId slot, - int priority = 0, -}) => - TabContribution( - id: id, - slot: slot, - title: id, - priority: priority, - build: (_) => const SizedBox.shrink(), - ); +TabContribution _tab({required String id, required SlotId slot, int priority = 0}) => + TabContribution(id: id, slot: slot, title: id, priority: priority, build: (_) => const SizedBox.shrink()); void main() { group('PanelRegistry', () { @@ -23,10 +13,7 @@ void main() { setUp(() => r = PanelRegistry()); test('registerSlot creates an empty mount list', () { - r.registerSlot(const SlotDefinition( - id: Slots.sidebar, - position: SlotPosition.left, - )); + r.registerSlot(const SlotDefinition(id: Slots.sidebar, position: SlotPosition.left)); expect(r.definitionFor(Slots.sidebar)!.position, SlotPosition.left); expect(r.contributionsFor(Slots.sidebar), isEmpty); }); @@ -65,11 +52,7 @@ void main() { }); test('contributing a non-slot contribution is a no-op for slots', () { - r.contribute(CommandContribution( - id: 'c', - command: 'c', - run: (_) async => throw UnimplementedError(), - )); + r.contribute(CommandContribution(id: 'c', command: 'c', run: (_) async => throw UnimplementedError())); expect(r.tabsFor(Slots.sidebar), isEmpty); }); diff --git a/test/kernel/src/panels/view_pane_snapshot_test.dart b/test/kernel/src/panels/view_pane_snapshot_test.dart index 97415245..bf6cf4ff 100644 --- a/test/kernel/src/panels/view_pane_snapshot_test.dart +++ b/test/kernel/src/panels/view_pane_snapshot_test.dart @@ -7,12 +7,7 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -TabContribution _tab(String id, SlotId slot) => TabContribution( - id: id, - slot: slot, - title: id.toUpperCase(), - build: (_) => const SizedBox.shrink(), - ); +TabContribution _tab(String id, SlotId slot) => TabContribution(id: id, slot: slot, title: id.toUpperCase(), build: (_) => const SizedBox.shrink()); void main() { group('snapshotViewPanes', () { @@ -42,11 +37,13 @@ void main() { }); test('active follows the kernel activeTab; visible follows the arrangement', () { - arrangement.applyPreset(const LayoutPresetContribution( - id: 'test', - displayName: 'Test', - slots: [LayoutSlot(slot: Slots.workspace, position: SlotPosition.center, visible: true)], - )); + arrangement.applyPreset( + const LayoutPresetContribution( + id: 'test', + displayName: 'Test', + slots: [LayoutSlot(slot: Slots.workspace, position: SlotPosition.center, visible: true)], + ), + ); panels.contribute(_tab('claude', Slots.workspace)); panels.contribute(_tab('editor', Slots.workspace)); panels.activateTab(Slots.workspace, 'editor'); diff --git a/test/kernel/src/project_test.dart b/test/kernel/src/project_test.dart index 74dcfbe590f7991800277e936f4b3d798f7538eb..654069aa0ebc793e1ee108a7874f893b9d5f801f 100644 GIT binary patch delta 107 zcmV-x0F?i(WYlA@;|i0Y1qYLF2oMcoa$#;`XgVM#ZDDC{lm8bY2{kMrF)Sc4GAXlL z3q}nAEVH2)#{`p&85fg?9UK85laUP|lb#|ev;G}55t9!U6q8Ud8Iy4@E|cLM9g_(W N4wG;X7_;gxL;{*}BC!Af delta 180 zcmcbTye?(KV=i|t1t=&;EXlA^P}eU|*MW=WCFZ6A#gK)PiW2jZK?-vdGxHEiCv$RZ za=Po*#4 diff --git a/test/kernel/src/services_bigger_test.dart b/test/kernel/src/services_bigger_test.dart index 7db8f81b..23e1e16c 100644 --- a/test/kernel/src/services_bigger_test.dart +++ b/test/kernel/src/services_bigger_test.dart @@ -65,22 +65,21 @@ void main() { testWidgets('DialogHost renders the child + the dialog over a backdrop', (tester) async { final r = DialogRouter(); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: DialogHost( - router: r, - child: const ColoredBox(color: Color(0xFF111111), child: SizedBox.expand()), + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: DialogHost( + router: r, + child: const ColoredBox(color: Color(0xFF111111), child: SizedBox.expand()), + ), ), - )); + ); await tester.pump(); // No dialog open yet — backdrop + modal not present. expect(find.byType(GestureDetector), findsNothing); // Open one. final future = r.show((ctx, dismiss) { - return GestureDetector( - onTap: () => dismiss('inner'), - child: const SizedBox(width: 100, height: 100), - ); + return GestureDetector(onTap: () => dismiss('inner'), child: const SizedBox(width: 100, height: 100)); }); await tester.pump(); // Backdrop + inner-wrapper + my own each add a GestureDetector. @@ -129,7 +128,10 @@ void main() { test('fire emits an OsLifecycleEvent on the bus', () async { final bus = DaemonBus(); - final bridge = OsBridge(log: Logger(minLevel: LogLevel.error), events: bus); + final bridge = OsBridge( + log: Logger(minLevel: LogLevel.error), + events: bus, + ); final got = bus.on().first; bridge.fire('resumed'); final e = await got.timeout(const Duration(seconds: 1)); @@ -154,10 +156,7 @@ void main() { final wc = WindowControls(); // Pre-register a handler that throws MissingPluginException for // every call, exercising each method's catch clause. - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('clide/window'), - (call) async => throw MissingPluginException(), - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('clide/window'), (call) async => throw MissingPluginException()); await wc.startResize(ResizeEdge.bottomRight); await wc.startDrag(); await wc.minimize(); diff --git a/test/kernel/src/services_stubs_test.dart b/test/kernel/src/services_stubs_test.dart index 6090439f..085a12fc 100644 --- a/test/kernel/src/services_stubs_test.dart +++ b/test/kernel/src/services_stubs_test.dart @@ -35,17 +35,14 @@ void main() { testWidgets('writePlain + readPlain go through the platform clipboard channel', (tester) async { String? last; - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - SystemChannels.platform, - (call) async { - if (call.method == 'Clipboard.setData') { - last = (call.arguments as Map)['text'] as String?; - } else if (call.method == 'Clipboard.getData') { - return {'text': last}; - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') { + last = (call.arguments as Map)['text'] as String?; + } else if (call.method == 'Clipboard.getData') { + return {'text': last}; + } + return null; + }); final c = ClideClipboard(); await c.writePlain('hello'); expect(last, 'hello'); @@ -56,15 +53,12 @@ void main() { testWidgets('write with toPlain syncs to the OS clipboard', (tester) async { String? last; - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - SystemChannels.platform, - (call) async { - if (call.method == 'Clipboard.setData') { - last = (call.arguments as Map)['text'] as String?; - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') { + last = (call.arguments as Map)['text'] as String?; + } + return null; + }); final c = ClideClipboard(); await c.write(7, toPlain: (n) => 'n=$n'); expect(last, 'n=7'); @@ -260,12 +254,7 @@ void main() { n.error('e'); n.success('s', duration: const Duration(seconds: 1)); expect(n.active, hasLength(4)); - expect(n.active.map((x) => x.level), [ - NotificationLevel.info, - NotificationLevel.warning, - NotificationLevel.error, - NotificationLevel.success, - ]); + expect(n.active.map((x) => x.level), [NotificationLevel.info, NotificationLevel.warning, NotificationLevel.error, NotificationLevel.success]); n.dispose(); }); diff --git a/test/kernel/src/settings_test.dart b/test/kernel/src/settings_test.dart index 56d2d808..20dfe252 100644 --- a/test/kernel/src/settings_test.dart +++ b/test/kernel/src/settings_test.dart @@ -24,14 +24,8 @@ void main() { }); test('scope key validation — rejects non-standard prefixes', () async { - expect( - () => store.get('nothing.here'), - throwsA(isA()), - ); - expect( - () => store.set('notascope.key', 'v'), - throwsA(isA()), - ); + expect(() => store.get('nothing.here'), throwsA(isA())); + expect(() => store.set('notascope.key', 'v'), throwsA(isA())); }); test('app.* scope round-trips via YAML on disk', () async { @@ -56,10 +50,7 @@ void main() { }); test('setting a project.* key without an open project throws', () async { - expect( - () => store.set('project.thing', 'x'), - throwsA(isA()), - ); + expect(() => store.set('project.thing', 'x'), throwsA(isA())); }); test('project scope is isolated from app scope', () async { @@ -106,10 +97,7 @@ void main() { }); test('setting a project-scoped key without a project throws StateError', () async { - expect( - () async => store.set('project.unset', 1), - throwsA(isA()), - ); + expect(() async => store.set('project.unset', 1), throwsA(isA())); }); test('ext.* keys default to app scope; project overrides app for the same key', () async { diff --git a/test/kernel/src/syntax/tree_sitter_service_fake_test.dart b/test/kernel/src/syntax/tree_sitter_service_fake_test.dart index 09827452..bd160059 100644 --- a/test/kernel/src/syntax/tree_sitter_service_fake_test.dart +++ b/test/kernel/src/syntax/tree_sitter_service_fake_test.dart @@ -44,9 +44,9 @@ void main() { return TreeSitterLib.testing( wasmEngineNew: engineHandle, wasmEngineDelete: (_) {}, - wasmStoreNew: (_, __) => storeHandle(), + wasmStoreNew: (_, _) => storeHandle(), parserNew: parserHandle, - parserSetWasmStore: (_, __) {}, + parserSetWasmStore: (_, _) {}, queryCursorNew: cursorHandle, wasmStoreLoadLanguage: wasmStoreLoadLanguage, queryNew: queryNew, @@ -61,7 +61,7 @@ void main() { parserDelete: parserDelete, wasmStoreDelete: wasmStoreDelete, queryCursorDelete: queryCursorDelete, - parserSetLanguage: parserSetLanguage ?? ((_, __) => true), + parserSetLanguage: parserSetLanguage ?? ((_, _) => true), ); } @@ -71,11 +71,7 @@ void main() { group('TreeSitterService._init — FFI failure branches', () { test('wasmEngineNew returning nullptr → hasGrammar(.dart) is false', () async { - final svc = TreeSitterService( - lib: TreeSitterLib.testing(), - grammarBytes: okBytes, - grammarQuery: noQuery, - ); + final svc = TreeSitterService(lib: TreeSitterLib.testing(), grammarBytes: okBytes, grammarQuery: noQuery); expect(await svc.hasGrammar('foo.dart'), isFalse); }); @@ -93,7 +89,7 @@ void main() { final lib = TreeSitterLib.testing( wasmEngineNew: engineHandle, wasmEngineDelete: (_) {}, - wasmStoreNew: (_, __) => storeHandle(), + wasmStoreNew: (_, _) => storeHandle(), // parserNew default → nullptr ); final svc = TreeSitterService(lib: lib, grammarBytes: okBytes, grammarQuery: noQuery); @@ -119,7 +115,7 @@ void main() { group('TreeSitterService._loadGrammar — branches', () { test('grammarBytes throwing is caught → grammar marked unavailable', () async { final svc = TreeSitterService( - lib: initOkLib(wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle()), + lib: initOkLib(wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle()), grammarBytes: (_) async => throw StateError('bundle missing'), grammarQuery: noQuery, ); @@ -127,11 +123,7 @@ void main() { }); test('wasmStoreLoadLanguage returning nullptr → grammar marked unavailable', () async { - final svc = TreeSitterService( - lib: initOkLib(), - grammarBytes: okBytes, - grammarQuery: noQuery, - ); + final svc = TreeSitterService(lib: initOkLib(), grammarBytes: okBytes, grammarQuery: noQuery); expect(await svc.hasGrammar('foo.dart'), isFalse); // Once marked unavailable, languageFor also returns null. expect(await svc.languageFor('foo.dart'), isNull); @@ -139,9 +131,7 @@ void main() { test('grammarQuery returning null → grammar loads with query=nullptr', () async { final svc = TreeSitterService( - lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - ), + lib: initOkLib(wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle()), grammarBytes: okBytes, grammarQuery: noQuery, ); @@ -158,7 +148,7 @@ void main() { test('grammarQuery loads + queryNew fails → grammar still cached with query=nullptr', () async { final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), // queryNew default → nullptr; capture reflection block skipped. ), grammarBytes: okBytes, @@ -174,10 +164,10 @@ void main() { final nameNative = 'keyword'.toNativeUtf8(); final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - queryNew: (_, __, ___, ____, _____) => queryHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), + queryNew: (_, _, _, _, _) => queryHandle(), queryCaptureCount: (_) => 1, - queryCaptureNameForId: (_, __, lenOut) { + queryCaptureNameForId: (_, _, lenOut) { lenOut.value = nameNative.length; return nameNative; }, @@ -192,9 +182,7 @@ void main() { test('a second call for the same language returns the cached grammar', () async { var byteLoads = 0; final svc = TreeSitterService( - lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - ), + lib: initOkLib(wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle()), grammarBytes: (lang) async { byteLoads++; return Uint8List.fromList(const [0, 1, 2]); @@ -226,8 +214,8 @@ void main() { test('parserParseString returning nullptr → empty spans', () async { final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - queryNew: (_, __, ___, ____, _____) => queryHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), + queryNew: (_, _, _, _, _) => queryHandle(), // parserParseString default → nullptr ), grammarBytes: okBytes, @@ -250,14 +238,14 @@ void main() { var matchCalls = 0; final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - queryNew: (_, __, ___, ____, _____) => queryHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), + queryNew: (_, _, _, _, _) => queryHandle(), queryCaptureCount: (_) => 1, - queryCaptureNameForId: (_, __, lenOut) { + queryCaptureNameForId: (_, _, lenOut) { lenOut.value = nameNative.length; return nameNative; }, - parserParseString: (_, __, ___, ____) => treeHandle(), + parserParseString: (_, _, _, _) => treeHandle(), treeRootNode: (_) => rootNode.ref, queryCursorNextMatch: (_, match) { if (matchCalls > 0) return false; @@ -291,14 +279,14 @@ void main() { var matchCalls = 0; final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - queryNew: (_, __, ___, ____, _____) => queryHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), + queryNew: (_, _, _, _, _) => queryHandle(), queryCaptureCount: (_) => 1, - queryCaptureNameForId: (_, __, lenOut) { + queryCaptureNameForId: (_, _, lenOut) { lenOut.value = nameNative.length; return nameNative; }, - parserParseString: (_, __, ___, ____) => treeHandle(), + parserParseString: (_, _, _, _) => treeHandle(), treeRootNode: (_) => rootNode.ref, queryCursorNextMatch: (_, match) { if (matchCalls > 0) return false; @@ -325,8 +313,8 @@ void main() { var cursorDeletes = 0; final svc = TreeSitterService( lib: initOkLib( - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), - queryNew: (_, __, ___, ____, _____) => queryHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), + queryNew: (_, _, _, _, _) => queryHandle(), queryDelete: (_) => queryDeletes++, parserDelete: (_) => parserDeletes++, wasmStoreDelete: (_) => storeDeletes++, @@ -360,11 +348,11 @@ void main() { return engineHandle(); }, wasmEngineDelete: (_) {}, - wasmStoreNew: (_, __) => storeHandle(), + wasmStoreNew: (_, _) => storeHandle(), parserNew: parserHandle, - parserSetWasmStore: (_, __) {}, + parserSetWasmStore: (_, _) {}, queryCursorNew: cursorHandle, - wasmStoreLoadLanguage: (_, __, ___, ____, _____) => languageHandle(), + wasmStoreLoadLanguage: (_, _, _, _, _) => languageHandle(), ); final svc = TreeSitterService(lib: lib, grammarBytes: okBytes, grammarQuery: noQuery); await svc.hasGrammar('foo.dart'); diff --git a/test/kernel/src/theme/controller_test.dart b/test/kernel/src/theme/controller_test.dart index 7ba119f5..fc710fd0 100644 --- a/test/kernel/src/theme/controller_test.dart +++ b/test/kernel/src/theme/controller_test.dart @@ -4,53 +4,41 @@ import 'package:clide/kernel/kernel.dart'; import 'package:flutter_test/flutter_test.dart'; ThemeDefinition _def(String name, Color primary) => ThemeDefinition( - name: name, - displayName: name, - dark: true, - palette: Palette({ - 'primary': primary, - 'accent': primary, - 'background': const Color(0xFF000000), - 'surface': const Color(0xFF111111), - 'panel': const Color(0xFF222222), - 'foreground': const Color(0xFFFFFFFF), - 'success': const Color(0xFF00FF00), - 'warning': const Color(0xFFFFFF00), - 'error': const Color(0xFFFF0000), - }), - ); + name: name, + displayName: name, + dark: true, + palette: Palette({ + 'primary': primary, + 'accent': primary, + 'background': const Color(0xFF000000), + 'surface': const Color(0xFF111111), + 'panel': const Color(0xFF222222), + 'foreground': const Color(0xFFFFFFFF), + 'success': const Color(0xFF00FF00), + 'warning': const Color(0xFFFFFF00), + 'error': const Color(0xFFFF0000), + }), +); void main() { group('ThemeController', () { test('starts on first bundled theme', () { - final c = ThemeController(bundled: [ - _def('a', const Color(0xFF111111)), - _def('b', const Color(0xFF222222)), - ]); + final c = ThemeController(bundled: [_def('a', const Color(0xFF111111)), _def('b', const Color(0xFF222222))]); expect(c.currentName, 'a'); }); test('honors initialName when present', () { - final c = ThemeController( - bundled: [_def('a', const Color(0xFF000000)), _def('b', const Color(0xFF999999))], - initialName: 'b', - ); + final c = ThemeController(bundled: [_def('a', const Color(0xFF000000)), _def('b', const Color(0xFF999999))], initialName: 'b'); expect(c.currentName, 'b'); }); test('silently falls back to first when initialName is unknown', () { - final c = ThemeController( - bundled: [_def('a', const Color(0xFF000000))], - initialName: 'missing', - ); + final c = ThemeController(bundled: [_def('a', const Color(0xFF000000))], initialName: 'missing'); expect(c.currentName, 'a'); }); test('select changes current + notifies listeners', () { - final c = ThemeController(bundled: [ - _def('a', const Color(0xFF000000)), - _def('b', const Color(0xFF333333)), - ]); + final c = ThemeController(bundled: [_def('a', const Color(0xFF000000)), _def('b', const Color(0xFF333333))]); var count = 0; c.addListener(() => count++); c.select('b'); diff --git a/test/kernel/src/theme/loader_test.dart b/test/kernel/src/theme/loader_test.dart index 56bb6aba..f6f87357 100644 --- a/test/kernel/src/theme/loader_test.dart +++ b/test/kernel/src/theme/loader_test.dart @@ -48,31 +48,19 @@ surface: panel.background: "semantic.mainchrome" panel.border: red '''); - expect(def.surfaceOverride, { - 'panel.background': 'semantic.mainchrome', - 'panel.border': 'red', - }); + expect(def.surfaceOverride, {'panel.background': 'semantic.mainchrome', 'panel.border': 'red'}); }); test('throws when name is missing and no fallback', () { - expect( - () => loader.fromYamlString('palette: { fg: "#fff" }'), - throwsA(isA()), - ); + expect(() => loader.fromYamlString('palette: { fg: "#fff" }'), throwsA(isA())); }); test('throws when palette is missing', () { - expect( - () => loader.fromYamlString('name: t'), - throwsA(isA()), - ); + expect(() => loader.fromYamlString('name: t'), throwsA(isA())); }); test('fallback name is used when name is absent', () { - final def = loader.fromYamlString( - 'palette: { fg: "#fff" }', - fallbackName: 'inferred', - ); + final def = loader.fromYamlString('palette: { fg: "#fff" }', fallbackName: 'inferred'); expect(def.name, 'inferred'); }); }); diff --git a/test/kernel/src/theme/mop_up_test.dart b/test/kernel/src/theme/mop_up_test.dart index 5ca07e67..0536ed00 100644 --- a/test/kernel/src/theme/mop_up_test.dart +++ b/test/kernel/src/theme/mop_up_test.dart @@ -37,32 +37,40 @@ void main() { ); addTearDown(controller.dispose); late ClideThemeData captured; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: ClideTheme( - controller: controller, - child: Builder(builder: (ctx) { - captured = ClideTheme.of(ctx); - return const SizedBox(); - }), + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: ClideTheme( + controller: controller, + child: Builder( + builder: (ctx) { + captured = ClideTheme.of(ctx); + return const SizedBox(); + }, + ), + ), ), - )); + ); expect(captured, isNotNull); }); testWidgets('of() throws when no ClideTheme ancestor', (tester) async { late Object captured; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Builder(builder: (ctx) { - try { - ClideTheme.of(ctx); - } catch (e) { - captured = e; - } - return const SizedBox(); - }), - )); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Builder( + builder: (ctx) { + try { + ClideTheme.of(ctx); + } catch (e) { + captured = e; + } + return const SizedBox(); + }, + ), + ), + ); expect(captured, isA()); }); @@ -90,32 +98,40 @@ void main() { ); addTearDown(controller.dispose); late ThemeController captured; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: ClideTheme( - controller: controller, - child: Builder(builder: (ctx) { - captured = ClideTheme.controllerOf(ctx); - return const SizedBox(); - }), + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: ClideTheme( + controller: controller, + child: Builder( + builder: (ctx) { + captured = ClideTheme.controllerOf(ctx); + return const SizedBox(); + }, + ), + ), ), - )); + ); expect(captured, same(controller)); }); testWidgets('controllerOf() throws when no ClideTheme ancestor', (tester) async { late Object captured; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Builder(builder: (ctx) { - try { - ClideTheme.controllerOf(ctx); - } catch (e) { - captured = e; - } - return const SizedBox(); - }), - )); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Builder( + builder: (ctx) { + try { + ClideTheme.controllerOf(ctx); + } catch (e) { + captured = e; + } + return const SizedBox(); + }, + ), + ), + ); expect(captured, isA()); }); }); @@ -133,11 +149,7 @@ void main() { test('ContrastFailure.toString embeds the pair name, ratio, and minimum', () { const failure = ContrastFailure( - pair: ContrastPair( - name: 'global.text_on_background', - foreground: Color(0xFF888888), - background: Color(0xFF7F7F7F), - ), + pair: ContrastPair(name: 'global.text_on_background', foreground: Color(0xFF888888), background: Color(0xFF7F7F7F)), ratio: 1.23, minimum: 4.5, ); @@ -150,16 +162,11 @@ void main() { group('ThemeLoader error + file paths', () { test('fromYamlString throws when the root is not a map', () { - expect( - () => const ThemeLoader().fromYamlString('- this\n- is\n- a list'), - throwsA(isA()), - ); + expect(() => const ThemeLoader().fromYamlString('- this\n- is\n- a list'), throwsA(isA())); }); test('fromFile loads the same content as fromYamlString', () async { - final tmp = await File.fromUri( - Uri.file('${Directory.systemTemp.path}/clide-theme-${DateTime.now().microsecondsSinceEpoch}.yaml'), - ).create(); + final tmp = await File.fromUri(Uri.file('${Directory.systemTemp.path}/clide-theme-${DateTime.now().microsecondsSinceEpoch}.yaml')).create(); addTearDown(() async { if (tmp.existsSync()) await tmp.delete(); }); @@ -184,18 +191,12 @@ palette: group('Palette / SemanticRoles iterables', () { test('Palette.names yields every registered colour key', () { - final p = Palette(const { - 'primary': Color(0xFF000000), - 'accent': Color(0xFFFFFFFF), - }); + final p = Palette(const {'primary': Color(0xFF000000), 'accent': Color(0xFFFFFFFF)}); expect(p.names, containsAll(['primary', 'accent'])); }); test('SemanticRoles.roles yields every registered role key', () { - final s = SemanticRoles(const { - 'text': Color(0xFFFFFFFF), - 'background': Color(0xFF000000), - }); + final s = SemanticRoles(const {'text': Color(0xFFFFFFFF), 'background': Color(0xFF000000)}); expect(s.roles, containsAll(['text', 'background'])); }); }); diff --git a/test/kernel/src/theme/resolver_test.dart b/test/kernel/src/theme/resolver_test.dart index 9b254101..9064ec8c 100644 --- a/test/kernel/src/theme/resolver_test.dart +++ b/test/kernel/src/theme/resolver_test.dart @@ -82,9 +82,7 @@ void main() { 'err': Color(0xFFFF0000), 'info': Color(0xFF111111), }), - surfaceOverride: const { - 'panel.background': '#00FF00', - }, + surfaceOverride: const {'panel.background': '#00FF00'}, ); expect(tokens.panelBackground, const Color(0xFF00FF00)); }); @@ -103,9 +101,7 @@ void main() { 'err': Color(0xFFFF0000), 'info': Color(0xFF111111), }), - semanticOverride: const SemanticRoles({ - 'focus': Color(0xFF007777), - }), + semanticOverride: const SemanticRoles({'focus': Color(0xFF007777)}), ); // No 'accent' in palette, so globalFocus falls through to // semantic.focus which is overridden. @@ -122,9 +118,7 @@ void main() { 'textHi': Color(0xFFFFFFFF), 'accent': Color(0xFF111111), }), - extensionOverride: const { - 'ext.sqlite.table.background': '#ABCDEF', - }, + extensionOverride: const {'ext.sqlite.table.background': '#ABCDEF'}, ); expect(tokens.extensionTokens['ext.sqlite.table.background'], const Color(0xFFABCDEF)); }); diff --git a/test/kernel/src/theme_persistence_test.dart b/test/kernel/src/theme_persistence_test.dart index 46bdb0d0..c409641d 100644 --- a/test/kernel/src/theme_persistence_test.dart +++ b/test/kernel/src/theme_persistence_test.dart @@ -6,21 +6,21 @@ import 'package:flutter/widgets.dart' show Color; import 'package:flutter_test/flutter_test.dart'; ThemeDefinition _def(String name) => ThemeDefinition( - name: name, - displayName: name, - dark: true, - palette: Palette(const { - 'primary': Color(0xFF00A3D2), - 'accent': Color(0xFFFA5F8B), - 'background': Color(0xFF21262F), - 'surface': Color(0xFF393E48), - 'panel': Color(0xFF292E38), - 'foreground': Color(0xFFE2E8F5), - 'success': Color(0xFF00AB9A), - 'warning': Color(0xFFD08447), - 'error': Color(0xFFF06C6F), - }), - ); + name: name, + displayName: name, + dark: true, + palette: Palette(const { + 'primary': Color(0xFF00A3D2), + 'accent': Color(0xFFFA5F8B), + 'background': Color(0xFF21262F), + 'surface': Color(0xFF393E48), + 'panel': Color(0xFF292E38), + 'foreground': Color(0xFFE2E8F5), + 'success': Color(0xFF00AB9A), + 'warning': Color(0xFFD08447), + 'error': Color(0xFFF06C6F), + }), +); void main() { group('wireThemePersistence (T-293)', () { diff --git a/test/kernel/src/toolchain_paths_test.dart b/test/kernel/src/toolchain_paths_test.dart index 88913244..63cb9cd3 100644 --- a/test/kernel/src/toolchain_paths_test.dart +++ b/test/kernel/src/toolchain_paths_test.dart @@ -9,13 +9,9 @@ import 'package:test/test.dart'; void main() { group('ToolchainView.resolved', () { test('exposes the supplied paths verbatim', () { - final v = ToolchainView.resolved(const ResolvedPaths( - git: '/opt/git', - pql: '/opt/pql', - tmux: '/opt/tmux', - shell: '/usr/bin/zsh', - gitEnv: {'GIT_EXEC_PATH': '/opt/git-core'}, - )); + final v = ToolchainView.resolved( + const ResolvedPaths(git: '/opt/git', pql: '/opt/pql', tmux: '/opt/tmux', shell: '/usr/bin/zsh', gitEnv: {'GIT_EXEC_PATH': '/opt/git-core'}), + ); expect(v.git, '/opt/git'); expect(v.pql, '/opt/pql'); expect(v.tmux, '/opt/tmux'); @@ -39,10 +35,12 @@ void main() { }); test('missing reports only the unresolved tools', () { - final v = ToolchainView.resolved(const ResolvedPaths( - git: '/opt/git', - // pql + tmux null → missing. - )); + final v = ToolchainView.resolved( + const ResolvedPaths( + git: '/opt/git', + // pql + tmux null → missing. + ), + ); expect(v.missing, ['pql', 'tmux']); expect(v.allOk, isFalse); }); diff --git a/test/kernel/src/toolchain_test.dart b/test/kernel/src/toolchain_test.dart index 618d3e0c..e9d03871 100644 --- a/test/kernel/src/toolchain_test.dart +++ b/test/kernel/src/toolchain_test.dart @@ -24,13 +24,15 @@ void main() { final t = Toolchain(); var calls = 0; t.addListener(() => calls++); - t.applyResolved(const ResolvedPaths( - git: '/usr/bin/git', - pql: '/usr/bin/pql', - tmux: '/usr/bin/tmux', - shell: '/bin/bash', - gitEnv: {'GIT_EXEC_PATH': '/usr/lib/git-core'}, - )); + t.applyResolved( + const ResolvedPaths( + git: '/usr/bin/git', + pql: '/usr/bin/pql', + tmux: '/usr/bin/tmux', + shell: '/bin/bash', + gitEnv: {'GIT_EXEC_PATH': '/usr/lib/git-core'}, + ), + ); expect(t.resolved, isTrue); expect(t.allOk, isTrue); expect(t.missing, isEmpty); diff --git a/test/panes/registry_test.dart b/test/panes/registry_test.dart index 2a52b1bf..73d076a3 100644 --- a/test/panes/registry_test.dart +++ b/test/panes/registry_test.dart @@ -30,10 +30,7 @@ void main() { tearDown(() => registry.shutdown()); test('spawn → emits pane.spawned and lists the pane', () async { - final pane = await registry.spawn( - kind: PaneKind.terminal, - argv: const ['/bin/echo', 'hi'], - ); + final pane = await registry.spawn(kind: PaneKind.terminal, argv: const ['/bin/echo', 'hi']); expect(pane.id, startsWith('p_')); expect(pane.kind, PaneKind.terminal); @@ -64,19 +61,13 @@ void main() { argv: const ['/bin/sh', '-c', 'printf hello-panes; sleep 0.25'], ); - final decoded = await got.future.timeout( - ioTimeout, - onTimeout: () => fail('pane.output never carried "hello-panes" within ${ioTimeout.inSeconds}s'), - ); + final decoded = await got.future.timeout(ioTimeout, onTimeout: () => fail('pane.output never carried "hello-panes" within ${ioTimeout.inSeconds}s')); expect(decoded, contains('hello-panes')); expect(sink.ofKind('pane.output'), isNotEmpty); }); test('write + resize emit no spurious events, update state', () async { - final pane = await registry.spawn( - kind: PaneKind.terminal, - argv: const ['/bin/cat'], - ); + final pane = await registry.spawn(kind: PaneKind.terminal, argv: const ['/bin/cat']); final writeCount = registry.write(pane.id, utf8.encode('abc')); expect(writeCount, greaterThan(0)); @@ -89,10 +80,7 @@ void main() { }); test('close is idempotent + emits pane.closed once', () async { - final pane = await registry.spawn( - kind: PaneKind.terminal, - argv: const ['/bin/cat'], - ); + final pane = await registry.spawn(kind: PaneKind.terminal, argv: const ['/bin/cat']); await registry.close(pane.id); await registry.close(pane.id); // second call: no-op @@ -107,10 +95,7 @@ void main() { }); test('claude kind round-trips on the wire', () async { - final pane = await registry.spawn( - kind: PaneKind.claude, - argv: const ['/bin/sh', '-c', 'exit 0'], - ); + final pane = await registry.spawn(kind: PaneKind.claude, argv: const ['/bin/sh', '-c', 'exit 0']); expect(pane.kind, PaneKind.claude); expect(pane.toJson()['kind'], 'claude'); }); diff --git a/test/pql/client_test.dart b/test/pql/client_test.dart index b1acb534..f7507f0a 100644 --- a/test/pql/client_test.dart +++ b/test/pql/client_test.dart @@ -164,7 +164,10 @@ void main() { final f = File('${tmp.path}/pql'); await f.writeAsString('#!/bin/sh\n$body\n'); await Process.run('chmod', ['+x', f.path]); - return PqlClient(workDir: Directory.current, toolchain: ToolchainView.resolved(ResolvedPaths(pql: f.path))); + return PqlClient( + workDir: Directory.current, + toolchain: ToolchainView.resolved(ResolvedPaths(pql: f.path)), + ); } test('a genuine (non-busy) error surfaces immediately', () async { @@ -178,14 +181,18 @@ void main() { }); test('a transient db-busy (exit 69) recovers on retry', () async { - final p = await fakePql(r'c="$0.n"; n=$(cat "$c" 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > "$c"; ' - r'if [ "$n" -lt 3 ]; then exit 69; fi; echo "[]"'); + final p = await fakePql( + r'c="$0.n"; n=$(cat "$c" 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > "$c"; ' + r'if [ "$n" -lt 3 ]; then exit 69; fi; echo "[]"', + ); expect(await p.files(), isEmpty); // retried through two busies to success }); test('a "database is locked" stderr (non-69 exit) is also retried', () async { - final p = await fakePql(r'c="$0.n"; n=$(cat "$c" 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > "$c"; ' - r'if [ "$n" -lt 3 ]; then echo "database is locked" >&2; exit 1; fi; echo "[]"'); + final p = await fakePql( + r'c="$0.n"; n=$(cat "$c" 2>/dev/null || echo 0); n=$((n+1)); echo "$n" > "$c"; ' + r'if [ "$n" -lt 3 ]; then echo "database is locked" >&2; exit 1; fi; echo "[]"', + ); expect(await p.files(), isEmpty); }); }); diff --git a/test/pty/env_test.dart b/test/pty/env_test.dart index c037b7aa..8beb8a5b 100644 --- a/test/pty/env_test.dart +++ b/test/pty/env_test.dart @@ -36,29 +36,20 @@ void main() { test('on macOS, includes the well-known extras', () { if (!Platform.isMacOS) return; // Homebrew is the canonical one; at least one of these should appear. - expect( - expandedPath, - anyOf(contains('/opt/homebrew/bin'), contains('/usr/local/bin')), - ); + expect(expandedPath, anyOf(contains('/opt/homebrew/bin'), contains('/usr/local/bin'))); }); }); group('mergePtyEnv', () { test('clide defaults override the process env where they overlap', () { - final merged = mergePtyEnv(processEnv: { - 'TERM': 'dumb', - 'CUSTOM': 'preserved', - }); + final merged = mergePtyEnv(processEnv: {'TERM': 'dumb', 'CUSTOM': 'preserved'}); expect(merged['TERM'], 'xterm-256color'); // clide default wins expect(merged['COLORTERM'], 'truecolor'); expect(merged['CUSTOM'], 'preserved'); // process env retained }); test('overrides win over both process env and clide defaults', () { - final merged = mergePtyEnv( - processEnv: {'TERM': 'dumb'}, - overrides: {'TERM': 'screen-256color'}, - ); + final merged = mergePtyEnv(processEnv: {'TERM': 'dumb'}, overrides: {'TERM': 'screen-256color'}); expect(merged['TERM'], 'screen-256color'); }); diff --git a/test/pty/session_test.dart b/test/pty/session_test.dart index c27f7664..e47a0a59 100644 --- a/test/pty/session_test.dart +++ b/test/pty/session_test.dart @@ -33,10 +33,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); addTearDown(s.close); @@ -51,10 +48,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); addTearDown(s.close); @@ -68,10 +62,7 @@ void main() { }); addTearDown(sub.cancel); - await firstByte.future.timeout( - ioTimeout, - onTimeout: () => fail('shell never produced its first byte within ${ioTimeout.inSeconds}s'), - ); + await firstByte.future.timeout(ioTimeout, onTimeout: () => fail('shell never produced its first byte within ${ioTimeout.inSeconds}s')); s.write(utf8.encode('echo write-test-ok\n')); @@ -86,20 +77,14 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); final done = Completer(); s.output.listen((_) {}, onDone: () => done.complete()); await s.close(); - await done.future.timeout( - ioTimeout, - onTimeout: () => fail('output stream did not close within ${ioTimeout.inSeconds}s after s.close()'), - ); + await done.future.timeout(ioTimeout, onTimeout: () => fail('output stream did not close within ${ioTimeout.inSeconds}s after s.close()')); expect(s.isClosed, isTrue); }); @@ -111,10 +96,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); addTearDown(s.close); @@ -133,10 +115,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/tmp/clide-no-such-dir-${DateTime.now().microsecondsSinceEpoch}', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ), throwsA(isA().having((e) => e.errno, 'errno', 2)), ); @@ -153,10 +132,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ), throwsA(isA().having((e) => e.errno, 'errno', 2)), ); @@ -172,10 +148,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); addTearDown(s.close); expect(s.pid, greaterThan(0)); @@ -188,10 +161,7 @@ void main() { columns: 80, rows: 24, workingDirectory: '/', - environment: { - ...Platform.environment, - 'TERM': 'xterm-256color', - }, + environment: {...Platform.environment, 'TERM': 'xterm-256color'}, ); addTearDown(s.close); s.resize(cols: 120, rows: 30); diff --git a/test/search/grep_engine_test.dart b/test/search/grep_engine_test.dart index ab8dc7be..946212a3 100644 --- a/test/search/grep_engine_test.dart +++ b/test/search/grep_engine_test.dart @@ -74,10 +74,7 @@ void main() { }); test('invalid regex throws FormatException', () async { - expect( - () => run(const SearchQuery(pattern: '(unclosed', regex: true)), - throwsA(isA()), - ); + expect(() => run(const SearchQuery(pattern: '(unclosed', regex: true)), throwsA(isA())); }); test('include glob restricts to matching files', () async { diff --git a/test/search/replace_engine_test.dart b/test/search/replace_engine_test.dart index 869dfe50..828429a5 100644 --- a/test/search/replace_engine_test.dart +++ b/test/search/replace_engine_test.dart @@ -125,7 +125,11 @@ void main() { }); test('ReplacementEdit + FileReplacement round-trip JSON', () { - const fr = FileReplacement(path: 'a.dart', count: 1, edits: [ReplacementEdit(line: 2, before: 'a', after: 'b')]); + const fr = FileReplacement( + path: 'a.dart', + count: 1, + edits: [ReplacementEdit(line: 2, before: 'a', after: 'b')], + ); final back = FileReplacement.fromJson(fr.toJson()); expect(back.path, 'a.dart'); expect(back.count, 1); diff --git a/test/terminal/buffer/buffer_test.dart b/test/terminal/buffer/buffer_test.dart index 86da50c4..ffbb17a7 100644 --- a/test/terminal/buffer/buffer_test.dart +++ b/test/terminal/buffer/buffer_test.dart @@ -14,14 +14,7 @@ import 'package:clide/src/terminal/src/core/state.dart'; import 'package:test/test.dart'; class _State implements TerminalState { - _State({ - this.viewWidth = 10, - this.viewHeight = 5, - this.autoWrapMode = true, - this.lineFeedMode = false, - this.originMode = false, - this.reflowEnabled = false, - }); + _State({this.viewWidth = 10, this.viewHeight = 5, this.autoWrapMode = true, this.lineFeedMode = false, this.originMode = false, this.reflowEnabled = false}); @override int viewWidth; @@ -83,12 +76,7 @@ Buffer _newBuffer({ originMode: originMode, reflowEnabled: reflowEnabled, ); - return Buffer( - state, - maxLines: maxLines, - isAltBuffer: isAltBuffer, - wordSeparators: wordSeparators, - ); + return Buffer(state, maxLines: maxLines, isAltBuffer: isAltBuffer, wordSeparators: wordSeparators); } void main() { diff --git a/test/terminal/buffer/range_test.dart b/test/terminal/buffer/range_test.dart index 9af6b770..1592dec4 100644 --- a/test/terminal/buffer/range_test.dart +++ b/test/terminal/buffer/range_test.dart @@ -126,14 +126,8 @@ void main() { }); test('toString shape — bounded and unbounded', () { - expect( - const BufferSegment(_dummyRange, 4, 2, 8).toString(), - 'Segment(4, 2 -> 8)', - ); - expect( - const BufferSegment(_dummyRange, 0, null, null).toString(), - 'Segment(0, start -> end)', - ); + expect(const BufferSegment(_dummyRange, 4, 2, 8).toString(), 'Segment(4, 2 -> 8)'); + expect(const BufferSegment(_dummyRange, 0, null, null).toString(), 'Segment(0, start -> end)'); }); }); diff --git a/test/terminal/core/core_test.dart b/test/terminal/core/core_test.dart index f8f7ecbc..9952a99d 100644 --- a/test/terminal/core/core_test.dart +++ b/test/terminal/core/core_test.dart @@ -39,10 +39,7 @@ void main() { test('toString shape is the documented debug format', () { final c = CellData(foreground: 1, background: 2, flags: 4, content: 8); - expect( - c.toString(), - 'CellData{foreground: 1, background: 2, flags: 4, content: 8}', - ); + expect(c.toString(), 'CellData{foreground: 1, background: 2, flags: 4, content: 8}'); }); }); @@ -374,10 +371,14 @@ void main() { final src = line(12, 'short'); final endAnchor = src.createAnchor(12); // viewWidth, past trimmedLength final out = reflow(ring([src]), 12, 4); - expect(out.contains(endAnchor.line), isTrue, - reason: 'end anchor must land on a line that survives ' - 'lines.replaceWith — otherwise selection.attached is false ' - 'and the selection silently vanishes after resize.'); + expect( + out.contains(endAnchor.line), + isTrue, + reason: + 'end anchor must land on a line that survives ' + 'lines.replaceWith — otherwise selection.attached is false ' + 'and the selection silently vanishes after resize.', + ); }); test('inner wide-char clamp during _addPart leaves the wide cell to the next iteration', () { diff --git a/test/terminal/coverage_trivials_test.dart b/test/terminal/coverage_trivials_test.dart index f175ba78..b512be88 100644 --- a/test/terminal/coverage_trivials_test.dart +++ b/test/terminal/coverage_trivials_test.dart @@ -49,11 +49,7 @@ void main() { // CJK ideograph — terminal stores it as a width-2 cell, so paintLine // advances `i` twice and the wide-char branch fires. t.write('漢字'); - final p = TerminalPainter( - theme: TerminalThemes.defaultTheme, - textStyle: const TerminalStyle(), - textScaler: const TextScaler.linear(1.0), - ); + final p = TerminalPainter(theme: TerminalThemes.defaultTheme, textStyle: const TerminalStyle(), textScaler: const TextScaler.linear(1.0)); p.paintLine(Canvas(PictureRecorder()), Offset.zero, t.buffer.lines[0]); }); } diff --git a/test/terminal/custom_text_edit_test.dart b/test/terminal/custom_text_edit_test.dart index 966c2dbb..abda23f1 100644 --- a/test/terminal/custom_text_edit_test.dart +++ b/test/terminal/custom_text_edit_test.dart @@ -19,12 +19,12 @@ class _Recorder { } Widget _host(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Center(child: SizedBox(width: 200, height: 100, child: child)), - ), - ); + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center(child: SizedBox(width: 200, height: 100, child: child)), + ), +); CustomTextEdit _build({ required FocusNode focusNode, @@ -43,7 +43,8 @@ CustomTextEdit _build({ onDelete: () => r.deletes++, onComposing: r.composings.add, onAction: r.actions.add, - onKeyEvent: onKeyEvent ?? + onKeyEvent: + onKeyEvent ?? (node, event) { r.keys.add(event); return KeyEventResult.ignored; @@ -230,10 +231,7 @@ void main() { await tester.pump(); final state = tester.state(find.byType(CustomTextEdit)); // Send a value shorter than the init state (2 chars) directly. - state.updateEditingValue(const TextEditingValue( - text: '', - selection: TextSelection.collapsed(offset: 0), - )); + state.updateEditingValue(const TextEditingValue(text: '', selection: TextSelection.collapsed(offset: 0))); expect(r.deletes, 1); }); @@ -244,11 +242,7 @@ void main() { await tester.pumpWidget(_host(_build(focusNode: focus, r: r, autofocus: true))); await tester.pump(); final state = tester.state(find.byType(CustomTextEdit)); - state.updateEditingValue(const TextEditingValue( - text: 'hi', - selection: TextSelection.collapsed(offset: 2), - composing: TextRange(start: 0, end: 2), - )); + state.updateEditingValue(const TextEditingValue(text: 'hi', selection: TextSelection.collapsed(offset: 2), composing: TextRange(start: 0, end: 2))); expect(r.composings, contains('hi')); }); diff --git a/test/terminal/escape/parser_test.dart b/test/terminal/escape/parser_test.dart index 155398c4..ba8dd0fe 100644 --- a/test/terminal/escape/parser_test.dart +++ b/test/terminal/escape/parser_test.dart @@ -238,15 +238,8 @@ void main() { test('writes printable chars one-by-one to writeChar', () { final f = _newParser(); f.parser.write('Hi!'); - expect( - f.h.calls.map((c) => c.name).toList(), - ['writeChar', 'writeChar', 'writeChar'], - ); - expect(f.h.calls.map((c) => c.args.first).toList(), [ - 'H'.codeUnitAt(0), - 'i'.codeUnitAt(0), - '!'.codeUnitAt(0), - ]); + expect(f.h.calls.map((c) => c.name).toList(), ['writeChar', 'writeChar', 'writeChar']); + expect(f.h.calls.map((c) => c.args.first).toList(), ['H'.codeUnitAt(0), 'i'.codeUnitAt(0), '!'.codeUnitAt(0)]); }); test('chars beyond the SBC table go straight to writeChar', () { @@ -289,21 +282,13 @@ void main() { test('ESC 7 / ESC 8 → save / restore cursor', () { final f = _newParser(); f.parser.write('\x1b7\x1b8'); - expect(f.h.calls.map((c) => c.name).toList(), [ - 'saveCursor', - 'restoreCursor', - ]); + expect(f.h.calls.map((c) => c.name).toList(), ['saveCursor', 'restoreCursor']); }); test('ESC D / E / H / M', () { final f = _newParser(); f.parser.write('\x1bD\x1bE\x1bH\x1bM'); - expect(f.h.calls.map((c) => c.name).toList(), [ - 'index', - 'nextLine', - 'setTapStop', - 'reverseIndex', - ]); + expect(f.h.calls.map((c) => c.name).toList(), ['index', 'nextLine', 'setTapStop', 'reverseIndex']); }); test('ESC ( / ESC ) designate G0 / G1 charset', () { @@ -320,10 +305,7 @@ void main() { final f = _newParser(); // ESC = enables app keypad mode, ESC > disables it. f.parser.write('\x1b=\x1b>'); - expect( - f.h.named('setAppKeypadMode').map((c) => c.args.first).toList(), - [true, false], - ); + expect(f.h.named('setAppKeypadMode').map((c) => c.args.first).toList(), [true, false]); }); test('unknown ESC byte → unkownEscape', () { @@ -354,12 +336,7 @@ void main() { test('A / B / C / D — default amount = 1, missing param uses default', () { final f = _newParser(); f.parser.write('\x1b[A\x1b[B\x1b[C\x1b[D'); - expect(f.h.calls.map((c) => '${c.name}/${c.args}').toList(), [ - 'moveCursorY/[-1]', - 'moveCursorY/[1]', - 'moveCursorX/[1]', - 'moveCursorX/[-1]', - ]); + expect(f.h.calls.map((c) => '${c.name}/${c.args}').toList(), ['moveCursorY/[-1]', 'moveCursorY/[1]', 'moveCursorX/[1]', 'moveCursorX/[-1]']); }); test('A / B / C / D — explicit amount + 0-as-1 fallback', () { @@ -412,12 +389,7 @@ void main() { test('J 0/1/2/3 → erase display below/above/all/scrollback', () { final f = _newParser(); f.parser.write('\x1b[J\x1b[1J\x1b[2J\x1b[3J'); - expect(f.h.calls.map((c) => c.name).toList(), [ - 'eraseDisplayBelow', - 'eraseDisplayAbove', - 'eraseDisplay', - 'eraseScrollbackOnly', - ]); + expect(f.h.calls.map((c) => c.name).toList(), ['eraseDisplayBelow', 'eraseDisplayAbove', 'eraseDisplay', 'eraseScrollbackOnly']); }); test('K 0/1/2 → erase line right / left / all', () { @@ -483,11 +455,7 @@ void main() { test('c / >c / =c — primary, secondary, tertiary device attributes', () { final f = _newParser(); f.parser.write('\x1b[c\x1b[>c\x1b[=c'); - expect(f.h.calls.map((c) => c.name).toList(), [ - 'sendPrimaryDeviceAttributes', - 'sendSecondaryDeviceAttributes', - 'sendTertiaryDeviceAttributes', - ]); + expect(f.h.calls.map((c) => c.name).toList(), ['sendPrimaryDeviceAttributes', 'sendSecondaryDeviceAttributes', 'sendTertiaryDeviceAttributes']); }); test('n 5 → operating status; n 6 → cursor position', () { @@ -595,12 +563,7 @@ void main() { final f = _newParser(); f.parser.write('\x1b[?9h\x1b[?1000h\x1b[?1002h\x1b[?1003h'); final modes = f.h.named('setMouseMode').map((c) => c.args.first).toList(); - expect(modes, [ - MouseMode.clickOnly, - MouseMode.upDownScroll, - MouseMode.upDownScrollDrag, - MouseMode.upDownScrollMove, - ]); + expect(modes, [MouseMode.clickOnly, MouseMode.upDownScroll, MouseMode.upDownScrollDrag, MouseMode.upDownScrollMove]); }); test('CSI ?9 / ?1000 disabled → setMouseMode(none)', () { @@ -614,12 +577,7 @@ void main() { final f = _newParser(); f.parser.write('\x1b[?1005h\x1b[?1006h\x1b[?1015h\x1b[?1006l'); final modes = f.h.named('setMouseReportMode').map((c) => c.args.first).toList(); - expect(modes, [ - MouseReportMode.utf, - MouseReportMode.sgr, - MouseReportMode.urxvt, - MouseReportMode.normal, - ]); + expect(modes, [MouseReportMode.utf, MouseReportMode.sgr, MouseReportMode.urxvt, MouseReportMode.normal]); }); test('CSI ?3 / ?5 / ?6 / ?12 / ?66 / ?1004 / ?1007 — column/reverse/origin/blink/keypad/focus/altScroll', () { diff --git a/test/terminal/input/input_test.dart b/test/terminal/input/input_test.dart index e805f306..c7373111 100644 --- a/test/terminal/input/input_test.dart +++ b/test/terminal/input/input_test.dart @@ -19,10 +19,7 @@ import 'package:clide/src/terminal/src/core/state.dart'; import 'package:test/test.dart'; class _State implements TerminalState { - _State({ - this.lineFeedMode = false, - this.appKeypadMode = false, - }); + _State({this.lineFeedMode = false, this.appKeypadMode = false}); @override bool lineFeedMode; @@ -78,10 +75,7 @@ TerminalKeyboardEvent _evt( shift: shift, ctrl: ctrl, alt: alt, - state: _State( - lineFeedMode: lineFeedMode, - appKeypadMode: appKeypadMode, - ), + state: _State(lineFeedMode: lineFeedMode, appKeypadMode: appKeypadMode), altBuffer: altBuffer, platform: platform, ); @@ -161,10 +155,7 @@ void main() { group('tokenize', () { test('parses a keyboard-name line', () { final tokens = tokenize('keyboard "Default"').toList(); - expect(tokens.map((t) => t.type).toList(), [ - KeytabTokenType.keyboard, - KeytabTokenType.input, - ]); + expect(tokens.map((t) => t.type).toList(), [KeytabTokenType.keyboard, KeytabTokenType.input]); expect(tokens.last.value, 'Default'); }); @@ -200,24 +191,15 @@ key Tab : "\\t" # trailing comment '''; final tokens = tokenize(source).toList(); // Trailing-comment stripping leaves no broken tokens. - expect( - tokens.map((t) => t.type).toList(), - contains(KeytabTokenType.colon), - ); + expect(tokens.map((t) => t.type).toList(), contains(KeytabTokenType.colon)); }); test('tokenize throws TokenizeError on a malformed key line missing colon', () { - expect( - () => tokenize('key Tab "\\t"').toList(), - throwsA(isA()), - ); + expect(() => tokenize('key Tab "\\t"').toList(), throwsA(isA())); }); test('tokenize throws TokenizeError on unterminated keyboard line', () { - expect( - () => tokenize('keyboard X').toList(), - throwsA(isA()), - ); + expect(() => tokenize('keyboard X').toList(), throwsA(isA())); }); test('KeytabToken toString reflects type + value', () { @@ -291,14 +273,8 @@ key Up +Bogus : "x" group('KeytabRecord / KeytabAction toString', () { test('action toString quotes input and bare-prints shortcut', () { - expect( - KeytabAction(KeytabActionType.input, r'\E[A').toString(), - '"\\E[A"', - ); - expect( - KeytabAction(KeytabActionType.shortcut, 'scrollUp').toString(), - 'scrollUp', - ); + expect(KeytabAction(KeytabActionType.input, r'\E[A').toString(), '"\\E[A"'); + expect(KeytabAction(KeytabActionType.shortcut, 'scrollUp').toString(), 'scrollUp'); }); test('record toString writes +Mode for true and -Mode for false flags', () { @@ -341,10 +317,7 @@ key Up +Shift : scrollLineUp test('action unescapedValue returns the raw value for shortcut actions', () { // Input actions go through keytabUnescape; shortcut actions don't. - expect( - KeytabAction(KeytabActionType.shortcut, r'\Ehello').unescapedValue(), - r'\Ehello', - ); + expect(KeytabAction(KeytabActionType.shortcut, r'\Ehello').unescapedValue(), r'\Ehello'); }); test('record toString covers every supported mode flag when set', () { @@ -384,34 +357,20 @@ key Up +Shift : scrollLineUp group('KeytabParser — defensive error paths through hand-crafted tokens', () { test('addTokens throws ParseError on a stray non-keyboard / non-keyDefine token', () { final parser = KeytabParser(); - expect( - () => parser.addTokens([ - KeytabToken(KeytabTokenType.colon, ':'), - ]), - throwsA(isA()), - ); + expect(() => parser.addTokens([KeytabToken(KeytabTokenType.colon, ':')]), throwsA(isA())); }); test('_parseName throws when the second token is not an input token', () { final parser = KeytabParser(); expect( - () => parser.addTokens([ - KeytabToken(KeytabTokenType.keyboard, 'keyboard'), - KeytabToken(KeytabTokenType.keyName, 'Up'), - ]), + () => parser.addTokens([KeytabToken(KeytabTokenType.keyboard, 'keyboard'), KeytabToken(KeytabTokenType.keyName, 'Up')]), throwsA(isA()), ); }); test('_parseKeyDefine throws when the second token is not a keyName', () { final parser = KeytabParser(); - expect( - () => parser.addTokens([ - KeytabToken(KeytabTokenType.keyDefine, 'key'), - KeytabToken(KeytabTokenType.colon, ':'), - ]), - throwsA(isA()), - ); + expect(() => parser.addTokens([KeytabToken(KeytabTokenType.keyDefine, 'key'), KeytabToken(KeytabTokenType.colon, ':')]), throwsA(isA())); }); test('_parseKeyDefine throws on an unrecognised modeStatus value', () { @@ -470,10 +429,7 @@ key Up +Shift : scrollLineUp test('exact match: -Shift on a record with shift=null and no anyModifier', () { final t = build('key Up -Shift : "\\E[A"'); - expect( - t.find(TerminalKey.arrowUp, shift: false)?.action.value, - r'\E[A', - ); + expect(t.find(TerminalKey.arrowUp, shift: false)?.action.value, r'\E[A'); expect(t.find(TerminalKey.arrowUp, shift: true), isNull); }); @@ -495,15 +451,9 @@ key Up +NewLine+AppKeyPad+AppScreen+Mac : "match" key Up : "fallback" '''); // Wrong newLine mode falls through to fallback. - expect( - t.find(TerminalKey.arrowUp, newLineMode: false, appKeyPad: true, appScreen: true, macos: true)?.action.value, - 'fallback', - ); + expect(t.find(TerminalKey.arrowUp, newLineMode: false, appKeyPad: true, appScreen: true, macos: true)?.action.value, 'fallback'); // All matching → primary record. - expect( - t.find(TerminalKey.arrowUp, newLineMode: true, appKeyPad: true, appScreen: true, macos: true)?.action.value, - 'match', - ); + expect(t.find(TerminalKey.arrowUp, newLineMode: true, appKeyPad: true, appScreen: true, macos: true)?.action.value, 'match'); }); test('-Ansi records are skipped (VT52 not supported yet)', () { @@ -519,10 +469,7 @@ key Up : "fallback" test('appCursorKeys + keyPad gates also filter', () { final t = build('key Up +AppCuKeys+KeyPad : "\\EOA"\nkey Up : "fallback"'); expect(t.find(TerminalKey.arrowUp)?.action.value, 'fallback'); - expect( - t.find(TerminalKey.arrowUp, appCursorKeys: true, keyPad: true)?.action.value, - r'\EOA', - ); + expect(t.find(TerminalKey.arrowUp, appCursorKeys: true, keyPad: true)?.action.value, r'\EOA'); }); }); @@ -591,23 +538,11 @@ key Up : "fallback" (_evt(TerminalKey.arrowUp, alt: true), '\x1b[1;3A'), // alt → 3 (_evt(TerminalKey.arrowUp, ctrl: true), '\x1b[1;5A'), // ctrl → 5 // Pair codes. - ( - _evt(TerminalKey.arrowUp, shift: true, alt: true), - '\x1b[1;4A', - ), - ( - _evt(TerminalKey.arrowUp, shift: true, ctrl: true), - '\x1b[1;6A', - ), - ( - _evt(TerminalKey.arrowUp, ctrl: true, alt: true), - '\x1b[1;7A', - ), + (_evt(TerminalKey.arrowUp, shift: true, alt: true), '\x1b[1;4A'), + (_evt(TerminalKey.arrowUp, shift: true, ctrl: true), '\x1b[1;6A'), + (_evt(TerminalKey.arrowUp, ctrl: true, alt: true), '\x1b[1;7A'), // Triple. - ( - _evt(TerminalKey.arrowUp, shift: true, alt: true, ctrl: true), - '\x1b[1;8A', - ), + (_evt(TerminalKey.arrowUp, shift: true, alt: true, ctrl: true), '\x1b[1;8A'), ]; for (final c in cases) { expect(h(c.$1), c.$2, reason: '$c'); @@ -662,10 +597,7 @@ key Up : "fallback" }); test('returns null on macOS (Alt is reserved for char composition)', () { - expect( - h(_evt(TerminalKey.keyA, alt: true, platform: TerminalTargetPlatform.macos)), - isNull, - ); + expect(h(_evt(TerminalKey.keyA, alt: true, platform: TerminalTargetPlatform.macos)), isNull); }); test('returns null for non-letter keys', () { @@ -680,10 +612,7 @@ key Up : "fallback" test('routes Ctrl+C to 0x03 via CtrlInputHandler when keytab misses', () { // Default keytab has no entry for Ctrl+keyC, so CtrlInputHandler runs. - expect( - defaultInputHandler(_evt(TerminalKey.keyC, ctrl: true)), - '\x03', - ); + expect(defaultInputHandler(_evt(TerminalKey.keyC, ctrl: true)), '\x03'); }); }); } diff --git a/test/terminal/mouse/mouse_test.dart b/test/terminal/mouse/mouse_test.dart index 1c6f834c..115390cf 100644 --- a/test/terminal/mouse/mouse_test.dart +++ b/test/terminal/mouse/mouse_test.dart @@ -13,10 +13,7 @@ import 'package:clide/src/terminal/src/core/state.dart'; import 'package:test/test.dart'; class _State implements TerminalState { - _State({ - this.mouseMode = MouseMode.none, - this.mouseReportMode = MouseReportMode.normal, - }); + _State({this.mouseMode = MouseMode.none, this.mouseReportMode = MouseReportMode.normal}); @override MouseMode mouseMode; @@ -91,12 +88,7 @@ void main() { expect(TerminalMouseButton.wheelDown.id, 64 + 5); expect(TerminalMouseButton.wheelLeft.id, 64 + 6); expect(TerminalMouseButton.wheelRight.id, 64 + 7); - for (final b in [ - TerminalMouseButton.wheelUp, - TerminalMouseButton.wheelDown, - TerminalMouseButton.wheelLeft, - TerminalMouseButton.wheelRight, - ]) { + for (final b in [TerminalMouseButton.wheelUp, TerminalMouseButton.wheelDown, TerminalMouseButton.wheelLeft, TerminalMouseButton.wheelRight]) { expect(b.isWheel, isTrue, reason: '$b'); } }); @@ -134,14 +126,8 @@ void main() { group('MouseReporter (sgr mode)', () { test('M for press, m for release, with raw 1-based coords', () { - expect( - MouseReporter.report(TerminalMouseButton.middle, TerminalMouseButtonState.down, const CellOffset(10, 20), MouseReportMode.sgr), - '\x1b[<1;11;21M', - ); - expect( - MouseReporter.report(TerminalMouseButton.middle, TerminalMouseButtonState.up, const CellOffset(10, 20), MouseReportMode.sgr), - '\x1b[<1;11;21m', - ); + expect(MouseReporter.report(TerminalMouseButton.middle, TerminalMouseButtonState.down, const CellOffset(10, 20), MouseReportMode.sgr), '\x1b[<1;11;21M'); + expect(MouseReporter.report(TerminalMouseButton.middle, TerminalMouseButtonState.up, const CellOffset(10, 20), MouseReportMode.sgr), '\x1b[<1;11;21m'); }); }); @@ -171,21 +157,11 @@ void main() { group('CascadeMouseHandler', () { test('returns the first non-null result; null when all return null', () { - const cascade = CascadeMouseHandler([ - _NullHandler(), - _ConstHandler('first'), - _ConstHandler('second'), - ]); - expect( - cascade(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down)), - 'first', - ); + const cascade = CascadeMouseHandler([_NullHandler(), _ConstHandler('first'), _ConstHandler('second')]); + expect(cascade(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down)), 'first'); const allNull = CascadeMouseHandler([_NullHandler(), _NullHandler()]); - expect( - allNull(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down)), - isNull, - ); + expect(allNull(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down)), isNull); }); }); @@ -198,31 +174,16 @@ void main() { }); test('clickOnly mode + up → null (only down events report)', () { - expect( - h(_evt(TerminalMouseButton.middle, TerminalMouseButtonState.up, mode: MouseMode.clickOnly)), - isNull, - ); + expect(h(_evt(TerminalMouseButton.middle, TerminalMouseButtonState.up, mode: MouseMode.clickOnly)), isNull); }); test('clickOnly mode + button id >= 3 (wheel) → null', () { - expect( - h(_evt(TerminalMouseButton.wheelUp, TerminalMouseButtonState.down, mode: MouseMode.clickOnly)), - isNull, - ); + expect(h(_evt(TerminalMouseButton.wheelUp, TerminalMouseButtonState.down, mode: MouseMode.clickOnly)), isNull); }); test('non-clickOnly modes always return null', () { - for (final m in [ - MouseMode.none, - MouseMode.upDownScroll, - MouseMode.upDownScrollDrag, - MouseMode.upDownScrollMove, - ]) { - expect( - h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), - isNull, - reason: '$m', - ); + for (final m in [MouseMode.none, MouseMode.upDownScroll, MouseMode.upDownScrollDrag, MouseMode.upDownScrollMove]) { + expect(h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), isNull, reason: '$m'); } }); }); @@ -232,39 +193,22 @@ void main() { test('none / clickOnly modes return null', () { for (final m in [MouseMode.none, MouseMode.clickOnly]) { - expect( - h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), - isNull, - ); + expect(h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), isNull); } }); test('upDownScroll modes report regular button events', () { - for (final m in [ - MouseMode.upDownScroll, - MouseMode.upDownScrollDrag, - MouseMode.upDownScrollMove, - ]) { - expect( - h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), - isNotNull, - reason: '$m', - ); + for (final m in [MouseMode.upDownScroll, MouseMode.upDownScrollDrag, MouseMode.upDownScrollMove]) { + expect(h(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: m)), isNotNull, reason: '$m'); } }); test('wheel up events are silently dropped (no report on wheel release)', () { - expect( - h(_evt(TerminalMouseButton.wheelUp, TerminalMouseButtonState.up, mode: MouseMode.upDownScroll)), - isNull, - ); + expect(h(_evt(TerminalMouseButton.wheelUp, TerminalMouseButtonState.up, mode: MouseMode.upDownScroll)), isNull); }); test('wheel down events do report (one click per scroll tick)', () { - expect( - h(_evt(TerminalMouseButton.wheelDown, TerminalMouseButtonState.down, mode: MouseMode.upDownScroll)), - isNotNull, - ); + expect(h(_evt(TerminalMouseButton.wheelDown, TerminalMouseButtonState.down, mode: MouseMode.upDownScroll)), isNotNull); }); }); @@ -280,12 +224,7 @@ void main() { }); test('mode=none produces null (no handler reports)', () { - expect( - defaultMouseHandler( - _evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: MouseMode.none), - ), - isNull, - ); + expect(defaultMouseHandler(_evt(TerminalMouseButton.left, TerminalMouseButtonState.down, mode: MouseMode.none)), isNull); }); }); } diff --git a/test/terminal/painter_bold_metrics_test.dart b/test/terminal/painter_bold_metrics_test.dart index 80d8b5f1..3fee9f29 100644 --- a/test/terminal/painter_bold_metrics_test.dart +++ b/test/terminal/painter_bold_metrics_test.dart @@ -31,22 +31,19 @@ void main() { test('JetBrainsMono Bold advance width matches Regular (cell drift = 0)', () { final regularWidth = _measure(FontWeight.normal); final boldWidth = _measure(FontWeight.bold); - expect(boldWidth, regularWidth, - reason: 'Bold advance width must equal Regular at $_fontSize px ' - 'or the terminal cell grid drifts when bold flips on.'); + expect( + boldWidth, + regularWidth, + reason: + 'Bold advance width must equal Regular at $_fontSize px ' + 'or the terminal cell grid drifts when bold flips on.', + ); }); } double _measure(FontWeight weight) { - final builder = ui.ParagraphBuilder(ui.ParagraphStyle( - fontFamily: _family, - fontSize: _fontSize, - )) - ..pushStyle(ui.TextStyle( - fontFamily: _family, - fontWeight: weight, - fontSize: _fontSize, - )) + final builder = ui.ParagraphBuilder(ui.ParagraphStyle(fontFamily: _family, fontSize: _fontSize)) + ..pushStyle(ui.TextStyle(fontFamily: _family, fontWeight: weight, fontSize: _fontSize)) ..addText(_sample); final paragraph = builder.build()..layout(const ui.ParagraphConstraints(width: double.infinity)); final width = paragraph.maxIntrinsicWidth; diff --git a/test/terminal/painter_test.dart b/test/terminal/painter_test.dart index 4a8a295d..3790f382 100644 --- a/test/terminal/painter_test.dart +++ b/test/terminal/painter_test.dart @@ -18,16 +18,8 @@ import 'package:clide/src/terminal/src/ui/themes.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; -TerminalPainter _make({ - TerminalTheme? theme, - TerminalStyle? style, - TextScaler? scaler, -}) => - TerminalPainter( - theme: theme ?? TerminalThemes.defaultTheme, - textStyle: style ?? const TerminalStyle(), - textScaler: scaler ?? const TextScaler.linear(1.0), - ); +TerminalPainter _make({TerminalTheme? theme, TerminalStyle? style, TextScaler? scaler}) => + TerminalPainter(theme: theme ?? TerminalThemes.defaultTheme, textStyle: style ?? const TerminalStyle(), textScaler: scaler ?? const TextScaler.linear(1.0)); Canvas _canvas() => Canvas(PictureRecorder()); @@ -101,11 +93,7 @@ void main() { }); group('TerminalPainter — paintCursor', () { - final cases = { - TerminalCursorType.block, - TerminalCursorType.underline, - TerminalCursorType.verticalBar, - }; + final cases = {TerminalCursorType.block, TerminalCursorType.underline, TerminalCursorType.verticalBar}; for (final c in cases) { test('focused $c paints without throwing', () { _make().paintCursor(_canvas(), const Offset(0, 0), cursorType: c); @@ -114,12 +102,7 @@ void main() { test('unfocused (hasFocus=false) draws a stroked rect regardless of cursor type', () { // Should hit the early-return-with-stroke branch, not the switch. - _make().paintCursor( - _canvas(), - const Offset(0, 0), - cursorType: TerminalCursorType.verticalBar, - hasFocus: false, - ); + _make().paintCursor(_canvas(), const Offset(0, 0), cursorType: TerminalCursorType.verticalBar, hasFocus: false); }); }); @@ -149,12 +132,7 @@ void main() { test('paintCellForeground caches the layout — second call hits the cache', () { final p = _make(); - final cell = CellData( - foreground: CellColor.normal, - background: CellColor.normal, - flags: 0, - content: 'A'.codeUnitAt(0), - ); + final cell = CellData(foreground: CellColor.normal, background: CellColor.normal, flags: 0, content: 'A'.codeUnitAt(0)); // Two paints with the same cell → second one resolves through the cache. p.paintCellForeground(_canvas(), Offset.zero, cell); p.paintCellForeground(_canvas(), Offset.zero, cell); @@ -164,88 +142,48 @@ void main() { final p = _make(); // faint p.paintCellForeground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.normal, - flags: CellFlags.faint, - content: 'B'.codeUnitAt(0), - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.normal, background: CellColor.normal, flags: CellFlags.faint, content: 'B'.codeUnitAt(0)), + ); // inverse → uses background as foreground p.paintCellForeground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.named | 2, - flags: CellFlags.inverse, - content: 'C'.codeUnitAt(0), - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.normal, background: CellColor.named | 2, flags: CellFlags.inverse, content: 'C'.codeUnitAt(0)), + ); // underline + space → swaps to U+00A0 internally p.paintCellForeground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.normal, - flags: CellFlags.underline, - content: 0x20, - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.normal, background: CellColor.normal, flags: CellFlags.underline, content: 0x20), + ); // bold + italic — exercises the flag-driven TextStyle path. p.paintCellForeground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.normal, - flags: CellFlags.bold | CellFlags.italic, - content: 'D'.codeUnitAt(0), - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.normal, background: CellColor.normal, flags: CellFlags.bold | CellFlags.italic, content: 'D'.codeUnitAt(0)), + ); }); test('paintCellBackground covers normal early-return, inverse, and named/palette paths', () { final p = _make(); // normal + no inverse → early return, no draw. - p.paintCellBackground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.normal, - flags: 0, - content: 0, - )); + p.paintCellBackground(_canvas(), Offset.zero, CellData(foreground: CellColor.normal, background: CellColor.normal, flags: 0, content: 0)); // inverse → resolves foreground colour and draws. p.paintCellBackground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.named | 1, - background: CellColor.normal, - flags: CellFlags.inverse, - content: 0, - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.named | 1, background: CellColor.normal, flags: CellFlags.inverse, content: 0), + ); // explicit background colour → draws. - p.paintCellBackground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.palette | 5, - flags: 0, - content: 0, - )); + p.paintCellBackground(_canvas(), Offset.zero, CellData(foreground: CellColor.normal, background: CellColor.palette | 5, flags: 0, content: 0)); // double-width cell → exercises the widthScale=2 branch. p.paintCellBackground( - _canvas(), - Offset.zero, - CellData( - foreground: CellColor.normal, - background: CellColor.named | 4, - flags: 0, - content: 'A'.codeUnitAt(0) | (2 << CellContent.widthShift), - )); + _canvas(), + Offset.zero, + CellData(foreground: CellColor.normal, background: CellColor.named | 4, flags: 0, content: 'A'.codeUnitAt(0) | (2 << CellContent.widthShift)), + ); }); }); diff --git a/test/terminal/parser_and_scroll_test.dart b/test/terminal/parser_and_scroll_test.dart index 9d4cadf9..5080cc6f 100644 --- a/test/terminal/parser_and_scroll_test.dart +++ b/test/terminal/parser_and_scroll_test.dart @@ -40,12 +40,12 @@ class _TestViewportOffset extends ViewportOffset { } Widget _host(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Center(child: SizedBox(width: 400, height: 200, child: child)), - ), - ); + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center(child: SizedBox(width: 400, height: 200, child: child)), + ), +); void main() { group('EscapeParser — explicit-param CSI cursor moves', () { diff --git a/test/terminal/render_test.dart b/test/terminal/render_test.dart index fd2273dd..5deb8548 100644 --- a/test/terminal/render_test.dart +++ b/test/terminal/render_test.dart @@ -15,12 +15,12 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; Widget _host(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Center(child: SizedBox(width: 400, height: 200, child: child)), - ), - ); + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center(child: SizedBox(width: 400, height: 200, child: child)), + ), +); void main() { group('RenderTerminal — TerminalView-driven setter updates', () { @@ -31,16 +31,20 @@ void main() { // Pump again with a different value for every TerminalView-exposed // prop that maps to a RenderTerminal setter — exercises the // "value != current" branch on each. - await tester.pumpWidget(_host(TerminalView( - t, - theme: TerminalThemes.whiteOnBlack, - textStyle: const TerminalStyle(fontSize: 18), - textScaler: const TextScaler.linear(1.2), - padding: const EdgeInsets.all(4), - autoResize: false, - cursorType: TerminalCursorType.underline, - alwaysShowCursor: true, - ))); + await tester.pumpWidget( + _host( + TerminalView( + t, + theme: TerminalThemes.whiteOnBlack, + textStyle: const TerminalStyle(fontSize: 18), + textScaler: const TextScaler.linear(1.2), + padding: const EdgeInsets.all(4), + autoResize: false, + cursorType: TerminalCursorType.underline, + alwaysShowCursor: true, + ), + ), + ); await tester.pump(); final state = tester.state(find.byType(TerminalView)); expect(state.renderTerminal, isNotNull); @@ -160,7 +164,7 @@ void main() { await tester.pump(); final state = tester.state(find.byType(TerminalView)); var callbackFired = 0; - state.renderTerminal.onEditableRect = (_, __) => callbackFired++; + state.renderTerminal.onEditableRect = (_, _) => callbackFired++; // Triggering a layout-affecting change should drive // _notifyEditableRect on the next layout pump. t.write('layout-trigger'); diff --git a/test/terminal/shortcut_event_test.dart b/test/terminal/shortcut_event_test.dart index 90344cf9..341ae069 100644 --- a/test/terminal/shortcut_event_test.dart +++ b/test/terminal/shortcut_event_test.dart @@ -49,12 +49,7 @@ void main() { tearDown(() => debugDefaultTargetPlatformOverride = null); test('Linux/Windows/Android/Fuchsia use Ctrl-modified bindings', () { - for (final p in [ - TargetPlatform.linux, - TargetPlatform.windows, - TargetPlatform.android, - TargetPlatform.fuchsia, - ]) { + for (final p in [TargetPlatform.linux, TargetPlatform.windows, TargetPlatform.android, TargetPlatform.fuchsia]) { debugDefaultTargetPlatformOverride = p; final map = defaultTerminalShortcuts; // Three entries: copy / paste / select-all. @@ -83,10 +78,7 @@ void main() { }); group('TerminalActions', () { - Widget host(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: child, - ); + Widget host(Widget child) => Directionality(textDirection: TextDirection.ltr, child: child); testWidgets('CopySelectionTextIntent writes selection text to the clipboard', (tester) async { final outputs = []; @@ -95,32 +87,32 @@ void main() { final controller = TerminalController(); addTearDown(controller.dispose); // Set a selection covering 'hello'. - controller.setSelection( - terminal.buffer.createAnchor(0, 0), - terminal.buffer.createAnchor(5, 0), - ); + controller.setSelection(terminal.buffer.createAnchor(0, 0), terminal.buffer.createAnchor(5, 0)); String? clipboardText; - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - SystemChannels.platform, - (call) async { - if (call.method == 'Clipboard.setData') { - final args = call.arguments as Map; - clipboardText = args['text'] as String?; - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') { + final args = call.arguments as Map; + clipboardText = args['text'] as String?; + } + return null; + }); late BuildContext capturedContext; - await tester.pumpWidget(host(TerminalActions( - terminal: terminal, - controller: controller, - child: Builder(builder: (context) { - capturedContext = context; - return const SizedBox(); - }), - ))); + await tester.pumpWidget( + host( + TerminalActions( + terminal: terminal, + controller: controller, + child: Builder( + builder: (context) { + capturedContext = context; + return const SizedBox(); + }, + ), + ), + ), + ); Actions.invoke(capturedContext, CopySelectionTextIntent.copy); await tester.pump(); @@ -132,23 +124,26 @@ void main() { final controller = TerminalController(); addTearDown(controller.dispose); var setDataCalls = 0; - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - SystemChannels.platform, - (call) async { - if (call.method == 'Clipboard.setData') setDataCalls++; - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.setData') setDataCalls++; + return null; + }); late BuildContext ctx; - await tester.pumpWidget(host(TerminalActions( - terminal: terminal, - controller: controller, - child: Builder(builder: (c) { - ctx = c; - return const SizedBox(); - }), - ))); + await tester.pumpWidget( + host( + TerminalActions( + terminal: terminal, + controller: controller, + child: Builder( + builder: (c) { + ctx = c; + return const SizedBox(); + }, + ), + ), + ), + ); Actions.invoke(ctx, CopySelectionTextIntent.copy); await tester.pump(); @@ -160,31 +155,31 @@ void main() { final terminal = Terminal(maxLines: 100, onOutput: outputs.add); final controller = TerminalController(); addTearDown(controller.dispose); - controller.setSelection( - terminal.buffer.createAnchor(0, 0), - terminal.buffer.createAnchor(2, 0), - ); + controller.setSelection(terminal.buffer.createAnchor(0, 0), terminal.buffer.createAnchor(2, 0)); expect(controller.selection, isNotNull); - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( - SystemChannels.platform, - (call) async { - if (call.method == 'Clipboard.getData') { - return {'text': 'pasted-payload'}; - } - return null; - }, - ); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async { + if (call.method == 'Clipboard.getData') { + return {'text': 'pasted-payload'}; + } + return null; + }); late BuildContext ctx; - await tester.pumpWidget(host(TerminalActions( - terminal: terminal, - controller: controller, - child: Builder(builder: (c) { - ctx = c; - return const SizedBox(); - }), - ))); + await tester.pumpWidget( + host( + TerminalActions( + terminal: terminal, + controller: controller, + child: Builder( + builder: (c) { + ctx = c; + return const SizedBox(); + }, + ), + ), + ), + ); Actions.invoke(ctx, const PasteTextIntent(SelectionChangedCause.keyboard)); await tester.pumpAndSettle(); @@ -201,14 +196,20 @@ void main() { expect(controller.selection, isNull); late BuildContext ctx; - await tester.pumpWidget(host(TerminalActions( - terminal: terminal, - controller: controller, - child: Builder(builder: (c) { - ctx = c; - return const SizedBox(); - }), - ))); + await tester.pumpWidget( + host( + TerminalActions( + terminal: terminal, + controller: controller, + child: Builder( + builder: (c) { + ctx = c; + return const SizedBox(); + }, + ), + ), + ), + ); Actions.invoke(ctx, const SelectAllTextIntent(SelectionChangedCause.keyboard)); await tester.pump(); diff --git a/test/terminal/terminal_test.dart b/test/terminal/terminal_test.dart index 21330168..d1d586c7 100644 --- a/test/terminal/terminal_test.dart +++ b/test/terminal/terminal_test.dart @@ -24,11 +24,7 @@ class _Recorder { String? lastOscPs; List? lastOscArgs; - Terminal build({ - int maxLines = 100, - TerminalTargetPlatform platform = TerminalTargetPlatform.linux, - bool reflow = true, - }) { + Terminal build({int maxLines = 100, TerminalTargetPlatform platform = TerminalTargetPlatform.linux, bool reflow = true}) { return Terminal( maxLines: maxLines, platform: platform, diff --git a/test/terminal/terminal_view_test.dart b/test/terminal/terminal_view_test.dart index 626f5d96..6890bf27 100644 --- a/test/terminal/terminal_view_test.dart +++ b/test/terminal/terminal_view_test.dart @@ -53,11 +53,7 @@ void main() { focus.dispose(); }); - await tester.pumpWidget(_host(TerminalView( - t, - controller: controller, - focusNode: focus, - ))); + await tester.pumpWidget(_host(TerminalView(t, controller: controller, focusNode: focus))); // Tear down via removing the widget; supplied controllers should NOT // throw on later use (the state didn't dispose them). await tester.pumpWidget(_host(const SizedBox())); @@ -89,10 +85,7 @@ void main() { await testGesture.addPointer(location: viewCenter); // A scroll event with a positive dy → page-down keys; a negative // dy → page-up. Use a magnitude that comfortably rounds to 1+ lines. - await tester.sendEventToBinding(PointerScrollEvent( - position: viewCenter, - scrollDelta: const Offset(0, 100), - )); + await tester.sendEventToBinding(PointerScrollEvent(position: viewCenter, scrollDelta: const Offset(0, 100))); await tester.pump(); // Output should contain at least one PgDown escape. expect(r.outputs, isNotEmpty); @@ -108,10 +101,7 @@ void main() { final viewCenter = tester.getCenter(find.byType(TerminalView)); final testGesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await testGesture.addPointer(location: viewCenter); - await tester.sendEventToBinding(PointerScrollEvent( - position: viewCenter, - scrollDelta: const Offset(0, 100), - )); + await tester.sendEventToBinding(PointerScrollEvent(position: viewCenter, scrollDelta: const Offset(0, 100))); await tester.pump(); expect(r.outputs, isNotEmpty); // Wheel-down id is 64+5=69; normal-mode reporter encodes button @@ -131,10 +121,7 @@ void main() { final viewCenter = tester.getCenter(find.byType(TerminalView)); // PointerScaleEvent is a sibling of PointerScrollEvent; the handler // ignores anything other than PointerScrollEvent. - await tester.sendEventToBinding(PointerScaleEvent( - position: viewCenter, - scale: 1.2, - )); + await tester.sendEventToBinding(PointerScaleEvent(position: viewCenter, scale: 1.2)); await tester.pump(); expect(r.outputs, isEmpty); }); @@ -188,11 +175,15 @@ void main() { testWidgets('hardware key event with a non-modifier key reaches Terminal.keyInput', (tester) async { final r = _OutputRecorder(); final t = r.build(); - await tester.pumpWidget(_host(TerminalView( - t, - autofocus: true, - // hardwareKeyboardOnly: true, - ))); + await tester.pumpWidget( + _host( + TerminalView( + t, + autofocus: true, + // hardwareKeyboardOnly: true, + ), + ), + ); // Wait one frame for autofocus to settle. await tester.pump(); // Send a Down arrow keypress through the binding. @@ -241,14 +232,18 @@ void main() { final r = _OutputRecorder(); final t = r.build(); var overrideCalls = 0; - await tester.pumpWidget(_host(TerminalView( - t, - autofocus: true, - onKeyEvent: (focusNode, event) { - overrideCalls++; - return KeyEventResult.handled; - }, - ))); + await tester.pumpWidget( + _host( + TerminalView( + t, + autofocus: true, + onKeyEvent: (focusNode, event) { + overrideCalls++; + return KeyEventResult.handled; + }, + ), + ), + ); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pump(); @@ -259,11 +254,7 @@ void main() { testWidgets('onKeyEvent returning ignored falls through to the default handler', (tester) async { final r = _OutputRecorder(); final t = r.build(); - await tester.pumpWidget(_host(TerminalView( - t, - autofocus: true, - onKeyEvent: (_, __) => KeyEventResult.ignored, - ))); + await tester.pumpWidget(_host(TerminalView(t, autofocus: true, onKeyEvent: (_, _) => KeyEventResult.ignored))); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pump(); @@ -308,10 +299,7 @@ void main() { final r = _OutputRecorder(); final t = r.build(); var tappedAt = 0; - await tester.pumpWidget(_host(TerminalView( - t, - onTapUp: (_, __) => tappedAt++, - ))); + await tester.pumpWidget(_host(TerminalView(t, onTapUp: (_, _) => tappedAt++))); await tester.tapAt(tester.getCenter(find.byType(TerminalView))); // The gesture detector arms a 300 ms double-tap timer after the tap; // explicit duration flushes it (pumpAndSettle waits on animations, @@ -324,10 +312,7 @@ void main() { final t = _OutputRecorder().build(); final controller = TerminalController(); addTearDown(controller.dispose); - controller.setSelection( - t.buffer.createAnchor(0, 0), - t.buffer.createAnchor(2, 0), - ); + controller.setSelection(t.buffer.createAnchor(0, 0), t.buffer.createAnchor(2, 0)); expect(controller.selection, isNotNull); await tester.pumpWidget(_host(TerminalView(t, controller: controller))); await tester.tapAt(tester.getCenter(find.byType(TerminalView))); @@ -340,11 +325,7 @@ void main() { final t = r.build(); var downCalls = 0; var upCalls = 0; - await tester.pumpWidget(_host(TerminalView( - t, - onSecondaryTapDown: (_, __) => downCalls++, - onSecondaryTapUp: (_, __) => upCalls++, - ))); + await tester.pumpWidget(_host(TerminalView(t, onSecondaryTapDown: (_, _) => downCalls++, onSecondaryTapUp: (_, _) => upCalls++))); final pos = tester.getCenter(find.byType(TerminalView)); final gesture = await tester.startGesture(pos, buttons: kSecondaryButton); await gesture.up(); @@ -448,10 +429,7 @@ void main() { // deleteDetection seeds the editing value with two spaces; shrinking // it below that length triggers CustomTextEdit.onDelete, which fires // _scrollToBottom + Terminal.keyInput(backspace) in TerminalView. - tester.testTextInput.updateEditingValue(const TextEditingValue( - text: '', - selection: TextSelection.collapsed(offset: 0), - )); + tester.testTextInput.updateEditingValue(const TextEditingValue(text: '', selection: TextSelection.collapsed(offset: 0))); await tester.pump(); // Backspace produces a non-empty escape sequence (^?). expect(r.outputs, isNotEmpty); @@ -461,11 +439,7 @@ void main() { final t = _OutputRecorder().build(); final focus = FocusNode(); addTearDown(focus.dispose); - await tester.pumpWidget(_host(TerminalView( - t, - hardwareKeyboardOnly: true, - focusNode: focus, - ))); + await tester.pumpWidget(_host(TerminalView(t, hardwareKeyboardOnly: true, focusNode: focus))); // Tap with no selection-clear short-circuit — the else-branch of // _onTapUp routes through _focusNode.requestFocus when // hardwareKeyboardOnly is set (no CustomTextEdit to delegate to). diff --git a/test/terminal/ui/ui_pure_test.dart b/test/terminal/ui/ui_pure_test.dart index 931dcbd5..363ec17e 100644 --- a/test/terminal/ui/ui_pure_test.dart +++ b/test/terminal/ui/ui_pure_test.dart @@ -142,9 +142,7 @@ void main() { }); test('fromTextStyle picks fontSize / height / fontFamilyFallback through to TerminalStyle', () { - final s = TerminalStyle.fromTextStyle( - const TextStyle(fontSize: 18, height: 1.5, fontFamilyFallback: ['Mono']), - ); + final s = TerminalStyle.fromTextStyle(const TextStyle(fontSize: 18, height: 1.5, fontFamilyFallback: ['Mono'])); expect(s.fontSize, 18); expect(s.height, 1.5); expect(s.fontFamilyFallback, ['Mono']); @@ -152,12 +150,7 @@ void main() { test('toTextStyle threads attribute flags into the resulting TextStyle', () { const s = TerminalStyle(fontSize: 16); - final t = s.toTextStyle( - color: const Color(0xFF112233), - bold: true, - italic: true, - underline: true, - ); + final t = s.toTextStyle(color: const Color(0xFF112233), bold: true, italic: true, underline: true); expect(t.fontSize, 16); expect(t.fontWeight, FontWeight.bold); expect(t.fontStyle, FontStyle.italic); @@ -244,11 +237,7 @@ void main() { group('SelectionMode + TerminalCursorType enums', () { test('all enum values exist and are distinct', () { expect(SelectionMode.values, [SelectionMode.line, SelectionMode.block]); - expect(TerminalCursorType.values, [ - TerminalCursorType.block, - TerminalCursorType.underline, - TerminalCursorType.verticalBar, - ]); + expect(TerminalCursorType.values, [TerminalCursorType.block, TerminalCursorType.underline, TerminalCursorType.verticalBar]); }); }); diff --git a/test/terminal/ui/ui_widget_test.dart b/test/terminal/ui/ui_widget_test.dart index fb93f53f..d0f33a68 100644 --- a/test/terminal/ui/ui_widget_test.dart +++ b/test/terminal/ui/ui_widget_test.dart @@ -28,16 +28,18 @@ void main() { final focus = FocusNode(); addTearDown(focus.dispose); - await tester.pumpWidget(_host( - CustomKeyboardListener( - focusNode: focus, - autofocus: true, - onInsert: inserts.add, - onComposing: composings.add, - onKeyEvent: (_, __) => KeyEventResult.ignored, - child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + await tester.pumpWidget( + _host( + CustomKeyboardListener( + focusNode: focus, + autofocus: true, + onInsert: inserts.add, + onComposing: composings.add, + onKeyEvent: (_, _) => KeyEventResult.ignored, + child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + ), ), - )); + ); await tester.pump(); // A character key with non-empty `character` triggers the insert path. await tester.sendKeyEvent(LogicalKeyboardKey.keyA, character: 'a'); @@ -50,16 +52,18 @@ void main() { final focus = FocusNode(); addTearDown(focus.dispose); - await tester.pumpWidget(_host( - CustomKeyboardListener( - focusNode: focus, - autofocus: true, - onInsert: inserts.add, - onComposing: (_) {}, - onKeyEvent: (_, __) => KeyEventResult.handled, - child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + await tester.pumpWidget( + _host( + CustomKeyboardListener( + focusNode: focus, + autofocus: true, + onInsert: inserts.add, + onComposing: (_) {}, + onKeyEvent: (_, _) => KeyEventResult.handled, + child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + ), ), - )); + ); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.keyA, character: 'a'); await tester.pump(); @@ -71,16 +75,18 @@ void main() { final focus = FocusNode(); addTearDown(focus.dispose); - await tester.pumpWidget(_host( - CustomKeyboardListener( - focusNode: focus, - autofocus: true, - onInsert: inserts.add, - onComposing: (_) {}, - onKeyEvent: (_, __) => KeyEventResult.ignored, - child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + await tester.pumpWidget( + _host( + CustomKeyboardListener( + focusNode: focus, + autofocus: true, + onInsert: inserts.add, + onComposing: (_) {}, + onKeyEvent: (_, _) => KeyEventResult.ignored, + child: const ColoredBox(color: Color(0xFF000000), child: SizedBox.expand()), + ), ), - )); + ); await tester.pump(); // ArrowUp has no `character` — ignored result + no character means // _onKeyEvent returns ignored without firing onInsert. diff --git a/test/widgets/clide_lightbox_test.dart b/test/widgets/clide_lightbox_test.dart index f4450e59..50b41a9a 100644 --- a/test/widgets/clide_lightbox_test.dart +++ b/test/widgets/clide_lightbox_test.dart @@ -18,9 +18,9 @@ void main() { tearDown(() => f.dispose()); Widget box(VoidCallback onDismiss) => MediaQuery( - data: const MediaQueryData(size: Size(800, 600)), - child: ClideLightbox(onDismiss: onDismiss, child: const SizedBox(width: 200, height: 150)), - ); + data: const MediaQueryData(size: Size(800, 600)), + child: ClideLightbox(onDismiss: onDismiss, child: const SizedBox(width: 200, height: 150)), + ); testWidgets('renders the zoom hint and an InteractiveViewer', (tester) async { await tester.pumpWidget(harness(f, box(() {}))); @@ -63,13 +63,18 @@ void main() { testWidgets('a tap on the dimmed canvas dismisses; a tap on the image does not (T-309)', (tester) async { final img = (await tester.runAsync(() => createTestImage(width: 100, height: 100)))!; // 1:1; real async var dismissed = false; - await tester.pumpWidget(harness( - f, - MediaQuery( - data: const MediaQueryData(size: Size(800, 600)), - child: ClideLightbox(onDismiss: () => dismissed = true, child: RawImage(image: img, fit: BoxFit.contain)), + await tester.pumpWidget( + harness( + f, + MediaQuery( + data: const MediaQueryData(size: Size(800, 600)), + child: ClideLightbox( + onDismiss: () => dismissed = true, + child: RawImage(image: img, fit: BoxFit.contain), + ), + ), ), - )); + ); await tester.pumpAndSettle(); // The 94% box is ~752×564; a 1:1 image fits to 564×564 centred, leaving diff --git a/test/widgets/clide_pane_chrome_test.dart b/test/widgets/clide_pane_chrome_test.dart index 56d73482..6263a62a 100644 --- a/test/widgets/clide_pane_chrome_test.dart +++ b/test/widgets/clide_pane_chrome_test.dart @@ -14,30 +14,13 @@ void main() { tearDown(() => f.dispose()); testWidgets('renders title + subtitle', (tester) async { - await tester.pumpWidget( - harness( - f, - const ClidePaneChrome( - title: 'terminal — ~/clide', - subtitle: 'bash · 80×24', - child: SizedBox.shrink(), - ), - ), - ); + await tester.pumpWidget(harness(f, const ClidePaneChrome(title: 'terminal — ~/clide', subtitle: 'bash · 80×24', child: SizedBox.shrink()))); expect(find.text('terminal — ~/clide'), findsOneWidget); expect(find.text('bash · 80×24'), findsOneWidget); }); testWidgets('no close button when onClose is null', (tester) async { - await tester.pumpWidget( - harness( - f, - const ClidePaneChrome( - title: 'primary claude', - child: SizedBox.shrink(), - ), - ), - ); + await tester.pumpWidget(harness(f, const ClidePaneChrome(title: 'primary claude', child: SizedBox.shrink()))); final handle = tester.ensureSemantics(); expect(find.bySemanticsLabel('Close pane'), findsNothing); handle.dispose(); @@ -45,16 +28,7 @@ void main() { testWidgets('close button invokes onClose when present', (tester) async { var pressed = false; - await tester.pumpWidget( - harness( - f, - ClidePaneChrome( - title: 'terminal', - onClose: () => pressed = true, - child: const SizedBox.shrink(), - ), - ), - ); + await tester.pumpWidget(harness(f, ClidePaneChrome(title: 'terminal', onClose: () => pressed = true, child: const SizedBox.shrink()))); final handle = tester.ensureSemantics(); expect(find.bySemanticsLabel('Close pane'), findsOneWidget); await tester.tap(find.bySemanticsLabel('Close pane')); diff --git a/test/widgets/more_widgets_test.dart b/test/widgets/more_widgets_test.dart index 94ff06c6..0271cff7 100644 --- a/test/widgets/more_widgets_test.dart +++ b/test/widgets/more_widgets_test.dart @@ -72,10 +72,7 @@ After. testWidgets('record-id link fires onRecordTap callback', (tester) async { var tapped = ''; const src = 'See [D-1](#anchor) for details.'; - await tester.pumpWidget(harness( - f, - ClideMarkdown(src, onRecordTap: (id) => tapped = id), - )); + await tester.pumpWidget(harness(f, ClideMarkdown(src, onRecordTap: (id) => tapped = id))); await tester.pumpAndSettle(); // The record-link path matches D-1 / Q-N / R-N / T-N and produces // a tappable span. We can't easily simulate a TextSpan tap from @@ -88,9 +85,7 @@ After. testWidgets('record-id link tap actually invokes onRecordTap', (tester) async { var tapped = ''; const src = '[D-1](#anchor)'; - await tester.pumpWidget( - harness(f, ClideMarkdown(src, onRecordTap: (id) => tapped = id)), - ); + await tester.pumpWidget(harness(f, ClideMarkdown(src, onRecordTap: (id) => tapped = id))); await tester.pumpAndSettle(); // The link renders as a ClideTappable embedded in a WidgetSpan. await tester.tap(find.text('D-1')); @@ -200,10 +195,7 @@ After. }); testWidgets('handles a language whose grammar is unavailable (falls back to plain text)', (tester) async { - await tester.pumpWidget(harness( - f, - const ClideCodeBlock(source: 'fn main() {}', language: 'rust'), - )); + await tester.pumpWidget(harness(f, const ClideCodeBlock(source: 'fn main() {}', language: 'rust'))); await tester.pumpAndSettle(); // TreeSitterService can't load the grammar in tests → falls back // to plain rendering. @@ -226,16 +218,12 @@ After. testWidgets('collapsed accordion does not render children; tap toggles', (tester) async { var toggled = 0; - await tester.pumpWidget(harness( - f, - ClideAccordion( - label: 'Group', - count: 2, - expanded: false, - onToggle: () => toggled++, - children: const [Text('child-one'), Text('child-two')], + await tester.pumpWidget( + harness( + f, + ClideAccordion(label: 'Group', count: 2, expanded: false, onToggle: () => toggled++, children: const [Text('child-one'), Text('child-two')]), ), - )); + ); await tester.pumpAndSettle(); expect(find.text('Group · 2'), findsOneWidget); expect(find.text('child-one'), findsNothing); @@ -244,17 +232,9 @@ After. }); testWidgets('expanded accordion shows children; leading widget renders', (tester) async { - await tester.pumpWidget(harness( - f, - ClideAccordion( - label: 'Open', - count: 1, - expanded: true, - onToggle: () {}, - leading: const Text('LEAD'), - children: const [Text('only-child')], - ), - )); + await tester.pumpWidget( + harness(f, ClideAccordion(label: 'Open', count: 1, expanded: true, onToggle: () {}, leading: const Text('LEAD'), children: const [Text('only-child')])), + ); await tester.pumpAndSettle(); expect(find.text('only-child'), findsOneWidget); expect(find.text('LEAD'), findsOneWidget); @@ -269,36 +249,25 @@ After. testWidgets('wraps a Scrollable child and exposes ScrollbarTheme', (tester) async { final controller = ScrollController(); addTearDown(controller.dispose); - await tester.pumpWidget(harness( - f, - SizedBox( - height: 100, - child: ClideScrollbar( - controller: controller, - child: SingleChildScrollView( + await tester.pumpWidget( + harness( + f, + SizedBox( + height: 100, + child: ClideScrollbar( controller: controller, - child: const SizedBox(height: 1000), + child: SingleChildScrollView(controller: controller, child: const SizedBox(height: 1000)), ), ), ), - )); + ); await tester.pumpAndSettle(); expect(find.byType(ClideScrollbar), findsOneWidget); }); test('ScrollbarTheme.updateShouldNotify detects colour changes', () { - const a = ScrollbarTheme( - slider: Color(0xFF000000), - sliderHover: Color(0xFF111111), - track: Color(0xFF222222), - child: SizedBox.shrink(), - ); - const b = ScrollbarTheme( - slider: Color(0xFFFFFFFF), - sliderHover: Color(0xFF111111), - track: Color(0xFF222222), - child: SizedBox.shrink(), - ); + const a = ScrollbarTheme(slider: Color(0xFF000000), sliderHover: Color(0xFF111111), track: Color(0xFF222222), child: SizedBox.shrink()); + const b = ScrollbarTheme(slider: Color(0xFFFFFFFF), sliderHover: Color(0xFF111111), track: Color(0xFF222222), child: SizedBox.shrink()); expect(a.updateShouldNotify(b), isTrue); expect(a.updateShouldNotify(a), isFalse); }); @@ -311,10 +280,16 @@ After. testWidgets('renders Semantics live region wrapping a TerminalView', (tester) async { final terminal = Terminal(maxLines: 100, onOutput: (_) {}); - await tester.pumpWidget(harness( - f, - SizedBox(width: 400, height: 200, child: ClidePtyView(terminal: terminal, label: 'test-pane')), - )); + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 400, + height: 200, + child: ClidePtyView(terminal: terminal, label: 'test-pane'), + ), + ), + ); await tester.pumpAndSettle(); expect(find.byType(ClidePtyView), findsOneWidget); // The TerminalView inside renders the actual viewport. diff --git a/test/widgets/multitab_controller_test.dart b/test/widgets/multitab_controller_test.dart index 0dedb752..15ac5836 100644 --- a/test/widgets/multitab_controller_test.dart +++ b/test/widgets/multitab_controller_test.dart @@ -2,13 +2,7 @@ import 'package:clide/widgets/src/multitab_controller.dart'; import 'package:flutter_test/flutter_test.dart'; MultitabEntry entry(String id, {bool closeable = true, bool reorderable = true}) { - return MultitabEntry( - id: id, - title: id, - payload: id, - closeable: closeable, - reorderable: reorderable, - ); + return MultitabEntry(id: id, title: id, payload: id, closeable: closeable, reorderable: reorderable); } void main() { @@ -94,31 +88,20 @@ void main() { }); test('reorder no-ops on non-reorderable entries', () { - final c = MultitabController(initial: [ - entry('p', reorderable: false), - entry('a'), - ]); + final c = MultitabController(initial: [entry('p', reorderable: false), entry('a')]); c.reorder('p', 1); expect(c.entries.map((e) => e.id), ['p', 'a']); }); test('reorder cannot move a tab past a pinned barrier', () { - final c = MultitabController(initial: [ - entry('p', reorderable: false), - entry('a'), - entry('b'), - ]); + final c = MultitabController(initial: [entry('p', reorderable: false), entry('a'), entry('b')]); // 'a' tries to land at index 0 — blocked by pinned 'p'. c.reorder('a', 0); expect(c.entries.map((e) => e.id), ['p', 'a', 'b']); }); test('reorder respects barriers on the right', () { - final c = MultitabController(initial: [ - entry('a'), - entry('b'), - entry('p', reorderable: false), - ]); + final c = MultitabController(initial: [entry('a'), entry('b'), entry('p', reorderable: false)]); // 'a' tries to land past pinned 'p' — clamped to before it. c.reorder('a', 2); expect(c.entries.map((e) => e.id), ['b', 'a', 'p']); @@ -146,10 +129,7 @@ void main() { test('replace rejects id changes', () { final c = MultitabController(initial: [entry('a')]); - expect( - () => c.replace('a', MultitabEntry(id: 'b', title: 'b', payload: 'b')), - throwsStateError, - ); + expect(() => c.replace('a', MultitabEntry(id: 'b', title: 'b', payload: 'b')), throwsStateError); }); test('notifies listeners on every mutation', () { diff --git a/test/widgets/multitab_pane_test.dart b/test/widgets/multitab_pane_test.dart index acfa0553..48a9e54f 100644 --- a/test/widgets/multitab_pane_test.dart +++ b/test/widgets/multitab_pane_test.dart @@ -5,18 +5,10 @@ import 'package:flutter_test/flutter_test.dart'; import '../helpers/kernel_fixture.dart'; import '../helpers/widget_harness.dart'; -Finder _closeIcons() => find.byWidgetPredicate( - (w) => w is ClideIcon && w.painter is CloseIcon, - ); +Finder _closeIcons() => find.byWidgetPredicate((w) => w is ClideIcon && w.painter is CloseIcon); MultitabEntry entry(String id, {bool closeable = true, bool reorderable = true}) { - return MultitabEntry( - id: id, - title: id, - payload: id, - closeable: closeable, - reorderable: reorderable, - ); + return MultitabEntry(id: id, title: id, payload: id, closeable: closeable, reorderable: reorderable); } Widget body(BuildContext _, MultitabEntry e) => SizedBox(key: ValueKey('body-${e.id}'), child: Text('body:${e.payload}')); @@ -39,12 +31,7 @@ class _CountingBodyState extends State<_CountingBody> { @override Widget build(BuildContext context) { - return GestureDetector( - key: ValueKey('counting-${widget.id}'), - behavior: HitTestBehavior.opaque, - onTap: _bump, - child: const SizedBox.expand(), - ); + return GestureDetector(key: ValueKey('counting-${widget.id}'), behavior: HitTestBehavior.opaque, onTap: _bump, child: const SizedBox.expand()); } } @@ -55,12 +42,8 @@ void main() { tearDown(() => f.dispose()); testWidgets('renders one tab per entry and the active body', (tester) async { - final c = MultitabController( - initial: [entry('primary', closeable: false), entry('s1'), entry('s2')], - ); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + final c = MultitabController(initial: [entry('primary', closeable: false), entry('s1'), entry('s2')]); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); expect(find.text('primary'), findsOneWidget); expect(find.text('s1'), findsOneWidget); @@ -72,9 +55,7 @@ void main() { testWidgets('tapping a tab activates it and swaps the body', (tester) async { final c = MultitabController(initial: [entry('a'), entry('b')]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); expect(find.byKey(const ValueKey('body-a')), findsOneWidget); await tester.tap(find.text('b')); @@ -88,16 +69,7 @@ void main() { testWidgets('add button calls onAddRequested when set', (tester) async { final c = MultitabController(initial: [entry('a')]); var added = 0; - await tester.pumpWidget( - harness( - f, - MultitabPane( - controller: c, - bodyBuilder: body, - onAddRequested: () => added++, - ), - ), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body, onAddRequested: () => added++))); await tester.tap(find.bySemanticsLabel('New tab')); await tester.pumpAndSettle(); @@ -106,19 +78,13 @@ void main() { testWidgets('add button is absent when onAddRequested is null', (tester) async { final c = MultitabController(initial: [entry('a')]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); expect(find.bySemanticsLabel('New tab'), findsNothing); }); testWidgets('non-closeable tabs do not render a close glyph', (tester) async { - final c = MultitabController( - initial: [entry('p', closeable: false), entry('s')], - ); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + final c = MultitabController(initial: [entry('p', closeable: false), entry('s')]); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); // A pinned tab has no close target; a closeable one does (it's // hidden via Opacity until hover, but still in the tree). // Two tabs total, one × glyph for 's'. @@ -126,12 +92,8 @@ void main() { }); testWidgets('default close behavior removes the entry', (tester) async { - final c = MultitabController( - initial: [entry('p', closeable: false), entry('s')], - ); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + final c = MultitabController(initial: [entry('p', closeable: false), entry('s')]); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); await tester.tap(_closeIcons()); await tester.pumpAndSettle(); @@ -142,16 +104,7 @@ void main() { testWidgets('onCloseRequested overrides default removal', (tester) async { final c = MultitabController(initial: [entry('a'), entry('b')]); MultitabEntry? closed; - await tester.pumpWidget( - harness( - f, - MultitabPane( - controller: c, - bodyBuilder: body, - onCloseRequested: (e) => closed = e, - ), - ), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body, onCloseRequested: (e) => closed = e))); // Both tabs are closeable; tap the first × encountered. await tester.tap(_closeIcons().first); @@ -164,9 +117,7 @@ void main() { testWidgets('rebuilds when the controller notifies', (tester) async { final c = MultitabController(initial: [entry('a')]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); expect(find.bySemanticsLabel('b'), findsNothing); c.add(entry('b')); @@ -176,17 +127,13 @@ void main() { testWidgets('empty controller renders no body', (tester) async { final c = MultitabController(); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); expect(find.byKey(const ValueKey('body-a')), findsNothing); }); testWidgets('drag a tab onto another to reorder', (tester) async { final c = MultitabController(initial: [entry('a'), entry('b'), entry('c')]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); // Drag tab 'a' to where tab 'c' sits. final from = tester.getCenter(find.text('a')); @@ -202,14 +149,8 @@ void main() { }); testWidgets('drag respects pinned barrier', (tester) async { - final c = MultitabController(initial: [ - entry('p', reorderable: false), - entry('a'), - entry('b'), - ]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + final c = MultitabController(initial: [entry('p', reorderable: false), entry('a'), entry('b')]); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); // Try to drag 'a' before pinned 'p' — controller's barrier // logic should reject and the order stays. @@ -226,13 +167,8 @@ void main() { }); testWidgets('pinned tabs are not draggable', (tester) async { - final c = MultitabController(initial: [ - entry('p', reorderable: false), - entry('a'), - ]); - await tester.pumpWidget( - harness(f, MultitabPane(controller: c, bodyBuilder: body)), - ); + final c = MultitabController(initial: [entry('p', reorderable: false), entry('a')]); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body))); // Attempt to drag pinned 'p' to position of 'a'. final from = tester.getCenter(find.text('p')); @@ -302,16 +238,7 @@ void main() { testWidgets('allowReorder=false disables drag entirely', (tester) async { final c = MultitabController(initial: [entry('a'), entry('b')]); - await tester.pumpWidget( - harness( - f, - MultitabPane( - controller: c, - bodyBuilder: body, - allowReorder: false, - ), - ), - ); + await tester.pumpWidget(harness(f, MultitabPane(controller: c, bodyBuilder: body, allowReorder: false))); final from = tester.getCenter(find.text('a')); final to = tester.getCenter(find.text('b')); diff --git a/test/widgets/quick_open_overlay_test.dart b/test/widgets/quick_open_overlay_test.dart index d0423e54..04e98edc 100644 --- a/test/widgets/quick_open_overlay_test.dart +++ b/test/widgets/quick_open_overlay_test.dart @@ -19,11 +19,15 @@ void main() { setUp(() async { f = await KernelFixture.create(); f.ipc.stub( - 'files.walk', - (_) async => IpcResponse.ok(id: '1', data: const { - 'files': ['lib/main.dart', 'lib/app.dart', 'README.md'], - 'truncated': false, - })); + 'files.walk', + (_) async => IpcResponse.ok( + id: '1', + data: const { + 'files': ['lib/main.dart', 'lib/app.dart', 'README.md'], + 'truncated': false, + }, + ), + ); f.ipc.stub('editor.open', (args) async => IpcResponse.ok(id: '1', data: {'path': args['path']})); }); tearDown(() => f.dispose()); @@ -102,11 +106,15 @@ void main() { testWidgets('truncated walk surfaces the limited-results hint', (tester) async { f.ipc.stub( - 'files.walk', - (_) async => IpcResponse.ok(id: '1', data: const { - 'files': ['lib/main.dart'], - 'truncated': true, - })); + 'files.walk', + (_) async => IpcResponse.ok( + id: '1', + data: const { + 'files': ['lib/main.dart'], + 'truncated': true, + }, + ), + ); await tester.pumpWidget(harness(f, const QuickOpenOverlay())); f.services.quickOpen.open(); await pumpAsync(tester); @@ -151,11 +159,12 @@ void main() { testWidgets('files.walk failure leaves the list empty (no crash)', (tester) async { f.ipc.stub( - 'files.walk', - (_) async => IpcResponse.err( - id: '1', - error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), - )); + 'files.walk', + (_) async => IpcResponse.err( + id: '1', + error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'), + ), + ); await tester.pumpWidget(harness(f, const QuickOpenOverlay())); f.services.quickOpen.open(); await pumpAsync(tester); diff --git a/test/widgets/src/clide_anchored_test.dart b/test/widgets/src/clide_anchored_test.dart index 39e29717..d7767fa8 100644 --- a/test/widgets/src/clide_anchored_test.dart +++ b/test/widgets/src/clide_anchored_test.dart @@ -21,7 +21,7 @@ void main() { f, ValueListenableBuilder( valueListenable: present, - builder: (_, show, __) => show + builder: (_, show, _) => show ? Align( alignment: Alignment.center, child: ClideAnchoredOverlay( @@ -30,7 +30,7 @@ void main() { centered: centered, autoFlip: false, anchor: const SizedBox(width: 60, height: 24, child: ClideText('anchor')), - overlayBuilder: (_, __) => const SizedBox(width: 120, height: 60, child: ClideText('panel')), + overlayBuilder: (_, _) => const SizedBox(width: 120, height: 60, child: ClideText('panel')), ), ) : const SizedBox.shrink(), @@ -143,11 +143,7 @@ void main() { addTearDown(c.dispose); var picked = ''; // Right-edge trigger + end alignment — the menu extends left, on-screen. - await tester.pumpWidget(anchoredHarness( - f, - anchoredMenu(c, (v) => picked = v, align: ClideAnchorAlign.end), - alignment: Alignment.topRight, - )); + await tester.pumpWidget(anchoredHarness(f, anchoredMenu(c, (v) => picked = v, align: ClideAnchorAlign.end), alignment: Alignment.topRight)); c.open(); await tester.pumpAndSettle(); await tester.tap(find.text('C')); @@ -158,11 +154,7 @@ void main() { testWidgets('autoFlip flips below to above when the anchor is near the bottom', (tester) async { final c = ClideOverlayController(); addTearDown(c.dispose); - await tester.pumpWidget(anchoredHarness( - f, - anchoredMenu(c, (_) {}, side: ClideAnchorSide.below, autoFlip: true), - alignment: Alignment.bottomLeft, - )); + await tester.pumpWidget(anchoredHarness(f, anchoredMenu(c, (_) {}, side: ClideAnchorSide.below, autoFlip: true), alignment: Alignment.bottomLeft)); c.open(); await tester.pumpAndSettle(); // The panel (its first item) sits ABOVE the trigger, not below. diff --git a/test/widgets/src/clide_button_test.dart b/test/widgets/src/clide_button_test.dart index 982467ce..417fe864 100644 --- a/test/widgets/src/clide_button_test.dart +++ b/test/widgets/src/clide_button_test.dart @@ -18,71 +18,38 @@ void main() { }); testWidgets('renders the label text', (tester) async { - await tester.pumpWidget( - harness(f, const ClideButton(label: 'Save', onPressed: null)), - ); + await tester.pumpWidget(harness(f, const ClideButton(label: 'Save', onPressed: null))); expect(find.text('Save'), findsOneWidget); }); testWidgets('emits a Semantics node with button: true + label', (tester) async { - await tester.pumpWidget( - harness(f, ClideButton(label: 'Commit', onPressed: () {})), - ); + await tester.pumpWidget(harness(f, ClideButton(label: 'Commit', onPressed: () {}))); final semantics = tester.getSemantics(find.byType(ClideButton)); expect(semantics.label, 'Commit'); - expect( - semantics.getSemanticsData().hasAction(SemanticsAction.tap), - isTrue, - reason: 'enabled button must expose tap action', - ); + expect(semantics.getSemanticsData().hasAction(SemanticsAction.tap), isTrue, reason: 'enabled button must expose tap action'); }); testWidgets('semanticLabel overrides the visible label for a11y', (tester) async { - await tester.pumpWidget( - harness( - f, - ClideButton( - label: 'Save', - semanticLabel: 'Save document', - onPressed: () {}, - ), - ), - ); + await tester.pumpWidget(harness(f, ClideButton(label: 'Save', semanticLabel: 'Save document', onPressed: () {}))); final semantics = tester.getSemantics(find.byType(ClideButton)); expect(semantics.label, 'Save document'); }); testWidgets('semanticHint propagates to the Semantics node', (tester) async { - await tester.pumpWidget( - harness( - f, - ClideButton( - label: 'Pick', - semanticHint: 'Select a theme', - onPressed: () {}, - ), - ), - ); + await tester.pumpWidget(harness(f, ClideButton(label: 'Pick', semanticHint: 'Select a theme', onPressed: () {}))); final semantics = tester.getSemantics(find.byType(ClideButton)); expect(semantics.hint, 'Select a theme'); }); testWidgets('disabled button drops the tap action', (tester) async { - await tester.pumpWidget( - harness(f, const ClideButton(label: 'Nope', onPressed: null)), - ); + await tester.pumpWidget(harness(f, const ClideButton(label: 'Nope', onPressed: null))); final semantics = tester.getSemantics(find.byType(ClideButton)); expect(semantics.getSemanticsData().hasAction(SemanticsAction.tap), isFalse); }); testWidgets('tap invokes onPressed', (tester) async { var pressed = 0; - await tester.pumpWidget( - harness( - f, - ClideButton(label: 'Go', onPressed: () => pressed++), - ), - ); + await tester.pumpWidget(harness(f, ClideButton(label: 'Go', onPressed: () => pressed++))); await tester.tap(find.byType(ClideButton)); expect(pressed, 1); }); diff --git a/test/widgets/src/clide_collapser_card_test.dart b/test/widgets/src/clide_collapser_card_test.dart index cd62cf80..869affa3 100644 --- a/test/widgets/src/clide_collapser_card_test.dart +++ b/test/widgets/src/clide_collapser_card_test.dart @@ -27,24 +27,26 @@ void main() { Color? color, bool expanded = false, }) async { - await tester.pumpWidget(harness( - f, - Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 400, - child: ClideCollapserCard( - label: label, - collapsedSummary: summary, - counter: counter, - status: status, - color: color, - initiallyExpanded: expanded, - children: children ?? const [Text('item body', textDirection: TextDirection.ltr)], + await tester.pumpWidget( + harness( + f, + Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: ClideCollapserCard( + label: label, + collapsedSummary: summary, + counter: counter, + status: status, + color: color, + initiallyExpanded: expanded, + children: children ?? const [Text('item body', textDirection: TextDirection.ltr)], + ), ), ), ), - )); + ); await tester.pump(); } @@ -70,7 +72,12 @@ void main() { }); testWidgets('a single item still sits in the list (1-item collapser)', (tester) async { - await pump(tester, counter: '1 edit', children: const [Text('only item', textDirection: TextDirection.ltr)], expanded: true); + await pump( + tester, + counter: '1 edit', + children: const [Text('only item', textDirection: TextDirection.ltr)], + expanded: true, + ); expect(find.text('only item'), findsOneWidget); expect(find.text('1 edit'), findsOneWidget); }); @@ -98,15 +105,19 @@ void main() { testWidgets('a deeper interactive control inside an item still fires (not swallowed)', (tester) async { var tapped = false; - await pump(tester, expanded: true, children: [ - ClideTappable( - onTap: () => tapped = true, - builder: (_, __, ___) => const Padding( - padding: EdgeInsets.all(8), - child: Text('press me', textDirection: TextDirection.ltr), + await pump( + tester, + expanded: true, + children: [ + ClideTappable( + onTap: () => tapped = true, + builder: (_, _, _) => const Padding( + padding: EdgeInsets.all(8), + child: Text('press me', textDirection: TextDirection.ltr), + ), ), - ), - ]); + ], + ); await tester.tap(find.text('press me')); await tester.pump(); expect(tapped, isTrue); // the item's own control won the hit @@ -115,9 +126,7 @@ void main() { testWidgets('the ticker is keyboard-focusable and toggles on Activate (a11y)', (tester) async { await pump(tester); - final focusWidget = tester.widget( - find.ancestor(of: find.text('clide_markdown.dart'), matching: find.byType(Focus)).first, - ); + final focusWidget = tester.widget(find.ancestor(of: find.text('clide_markdown.dart'), matching: find.byType(Focus)).first); focusWidget.focusNode!.requestFocus(); await tester.pump(); expect(focusWidget.focusNode!.hasFocus, isTrue); diff --git a/test/widgets/src/clide_divider_test.dart b/test/widgets/src/clide_divider_test.dart index 8b47d696..abe93628 100644 --- a/test/widgets/src/clide_divider_test.dart +++ b/test/widgets/src/clide_divider_test.dart @@ -19,9 +19,7 @@ void main() { }); testWidgets('vertical axis yields a width-constrained container', (tester) async { - await tester.pumpWidget( - harness(f, const ClideDivider(axis: Axis.vertical, thickness: 2)), - ); + await tester.pumpWidget(harness(f, const ClideDivider(axis: Axis.vertical, thickness: 2))); final c = tester.widget(find.byType(Container)); expect(c.constraints?.maxWidth, 2.0); }); diff --git a/test/widgets/src/clide_icon_test.dart b/test/widgets/src/clide_icon_test.dart index f870765d..b9f7fbda 100644 --- a/test/widgets/src/clide_icon_test.dart +++ b/test/widgets/src/clide_icon_test.dart @@ -12,9 +12,7 @@ void main() { tearDown(() async => f.dispose()); testWidgets('sizes a SizedBox + CustomPaint to the given size', (tester) async { - await tester.pumpWidget( - harness(f, const ClideIcon(FolderIcon(), size: 24)), - ); + await tester.pumpWidget(harness(f, const ClideIcon(FolderIcon(), size: 24))); final sb = tester.widget(find.byType(SizedBox).first); expect(sb.width, 24); expect(sb.height, 24); @@ -22,9 +20,7 @@ void main() { }); testWidgets('default color is globalForeground', (tester) async { - await tester.pumpWidget( - harness(f, const ClideIcon(CheckIcon())), - ); + await tester.pumpWidget(harness(f, const ClideIcon(CheckIcon()))); // Color is inaccessible after painting; ensure it renders without crashing. expect(find.byType(ClideIcon), findsOneWidget); }); diff --git a/test/widgets/src/clide_markdown_file_ref_test.dart b/test/widgets/src/clide_markdown_file_ref_test.dart index cad585fd..37695e68 100644 --- a/test/widgets/src/clide_markdown_file_ref_test.dart +++ b/test/widgets/src/clide_markdown_file_ref_test.dart @@ -17,21 +17,23 @@ void main() { // Resolves only the one known repo path; everything else is "not a file". String? resolve(String p) => p == 'lib/app.dart' ? '/repo/lib/app.dart' : null; - ClideMarkdown md(String src, {void Function(String, int?)? onOpen}) => ClideMarkdown( - src, - resolveFileRef: resolve, - onOpenFile: onOpen ?? (_, __) {}, - ); + ClideMarkdown md(String src, {void Function(String, int?)? onOpen}) => ClideMarkdown(src, resolveFileRef: resolve, onOpenFile: onOpen ?? (_, _) {}); testWidgets('a bare repo path in prose is tappable and opens with no line', (tester) async { String? path; int? line = -1; - await tester.pumpWidget(harness( + await tester.pumpWidget( + harness( f, - md('see lib/app.dart for the entry point', onOpen: (p, l) { - path = p; - line = l; - }))); + md( + 'see lib/app.dart for the entry point', + onOpen: (p, l) { + path = p; + line = l; + }, + ), + ), + ); await tester.pump(); await tester.tap(find.text('lib/app.dart')); @@ -43,12 +45,18 @@ void main() { testWidgets('a path:line ref opens at the line', (tester) async { String? path; int? line; - await tester.pumpWidget(harness( + await tester.pumpWidget( + harness( f, - md('crash at lib/app.dart:42 today', onOpen: (p, l) { - path = p; - line = l; - }))); + md( + 'crash at lib/app.dart:42 today', + onOpen: (p, l) { + path = p; + line = l; + }, + ), + ), + ); await tester.pump(); await tester.tap(find.text('lib/app.dart:42')); @@ -70,12 +78,18 @@ void main() { testWidgets('a backticked path is clickable', (tester) async { String? path; int? line; - await tester.pumpWidget(harness( + await tester.pumpWidget( + harness( f, - md('open `lib/app.dart:7`', onOpen: (p, l) { - path = p; - line = l; - }))); + md( + 'open `lib/app.dart:7`', + onOpen: (p, l) { + path = p; + line = l; + }, + ), + ), + ); await tester.pump(); await tester.tap(find.text('lib/app.dart:7')); @@ -96,7 +110,7 @@ void main() { testWidgets('a path that does not resolve stays literal (no link, no fire)', (tester) async { var calls = 0; - await tester.pumpWidget(harness(f, md('nothing at lib/ghost.dart here', onOpen: (_, __) => calls++))); + await tester.pumpWidget(harness(f, md('nothing at lib/ghost.dart here', onOpen: (_, _) => calls++))); await tester.pumpAndSettle(); // Rendered as plain text — tapping it does nothing. @@ -107,7 +121,7 @@ void main() { testWidgets('dotted prose (a version number) does not linkify', (tester) async { var calls = 0; - await tester.pumpWidget(harness(f, md('shipped version 2.2.0 today', onOpen: (_, __) => calls++))); + await tester.pumpWidget(harness(f, md('shipped version 2.2.0 today', onOpen: (_, _) => calls++))); await tester.pumpAndSettle(); await tester.tap(find.textContaining('2.2.0'), warnIfMissed: false); diff --git a/test/widgets/src/clide_marquee_test.dart b/test/widgets/src/clide_marquee_test.dart index 347231ba..4d678496 100644 --- a/test/widgets/src/clide_marquee_test.dart +++ b/test/widgets/src/clide_marquee_test.dart @@ -9,16 +9,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../../helpers/widget_harness.dart'; Widget _boxed(double width, Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: SizedBox(width: width, height: 16, child: child), - ), - ); + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox(width: width, height: 16, child: child), + ), +); -Widget _reducedMotion(double width, Widget child) => MediaQuery( - data: const MediaQueryData(disableAnimations: true), - child: _boxed(width, child), - ); +Widget _reducedMotion(double width, Widget child) => MediaQuery(data: const MediaQueryData(disableAnimations: true), child: _boxed(width, child)); void main() { testWidgets('shows the child statically when it fits', (tester) async { diff --git a/test/widgets/src/clide_menu_test.dart b/test/widgets/src/clide_menu_test.dart index affd2e0b..0ffb3166 100644 --- a/test/widgets/src/clide_menu_test.dart +++ b/test/widgets/src/clide_menu_test.dart @@ -59,17 +59,19 @@ void main() { testWidgets('renders items + separator; tapping an item fires onSelect + onClose', (tester) async { var picked = ''; var closed = 0; - await tester.pumpWidget(harness( - f, - ClideMenu( - onClose: () => closed++, - entries: [ - ClideMenuItem(label: 'Alpha', onSelect: () => picked = 'Alpha'), - const ClideMenuSeparator(), - ClideMenuItem(label: 'Beta', onSelect: () => picked = 'Beta'), - ], + await tester.pumpWidget( + harness( + f, + ClideMenu( + onClose: () => closed++, + entries: [ + ClideMenuItem(label: 'Alpha', onSelect: () => picked = 'Alpha'), + const ClideMenuSeparator(), + ClideMenuItem(label: 'Beta', onSelect: () => picked = 'Beta'), + ], + ), ), - )); + ); await tester.pump(); expect(find.text('Alpha'), findsOneWidget); expect(find.text('Beta'), findsOneWidget); @@ -81,16 +83,18 @@ void main() { testWidgets('arrow-down then Enter activates the highlighted item', (tester) async { var picked = ''; - await tester.pumpWidget(harness( - f, - ClideMenu( - onClose: () {}, - entries: [ - ClideMenuItem(label: 'One', onSelect: () => picked = 'One'), - ClideMenuItem(label: 'Two', onSelect: () => picked = 'Two'), - ], + await tester.pumpWidget( + harness( + f, + ClideMenu( + onClose: () {}, + entries: [ + ClideMenuItem(label: 'One', onSelect: () => picked = 'One'), + ClideMenuItem(label: 'Two', onSelect: () => picked = 'Two'), + ], + ), ), - )); + ); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); @@ -101,17 +105,19 @@ void main() { testWidgets('disabled item is inert and skipped by arrows', (tester) async { var picked = ''; - await tester.pumpWidget(harness( - f, - ClideMenu( - onClose: () {}, - entries: [ - ClideMenuItem(label: 'Able', onSelect: () => picked = 'Able'), - ClideMenuItem(label: 'Off', enabled: false, onSelect: () => picked = 'Off'), - ClideMenuItem(label: 'Last', onSelect: () => picked = 'Last'), - ], + await tester.pumpWidget( + harness( + f, + ClideMenu( + onClose: () {}, + entries: [ + ClideMenuItem(label: 'Able', onSelect: () => picked = 'Able'), + ClideMenuItem(label: 'Off', enabled: false, onSelect: () => picked = 'Off'), + ClideMenuItem(label: 'Last', onSelect: () => picked = 'Last'), + ], + ), ), - )); + ); await tester.pump(); await tester.tap(find.text('Off')); await tester.pump(); @@ -125,7 +131,15 @@ void main() { testWidgets('Escape calls onClose', (tester) async { var closed = 0; - await tester.pumpWidget(harness(f, ClideMenu(onClose: () => closed++, entries: [ClideMenuItem(label: 'X', onSelect: () {})]))); + await tester.pumpWidget( + harness( + f, + ClideMenu( + onClose: () => closed++, + entries: [ClideMenuItem(label: 'X', onSelect: () {})], + ), + ), + ); await tester.pump(); await tester.sendKeyEvent(LogicalKeyboardKey.escape); await tester.pump(); @@ -135,13 +149,15 @@ void main() { testWidgets('keepOpenOnSelect item fires onSelect but not onClose', (tester) async { var picked = 0; var closed = 0; - await tester.pumpWidget(harness( - f, - ClideMenu( - onClose: () => closed++, - entries: [ClideMenuItem(label: 'Toggle', keepOpenOnSelect: true, onSelect: () => picked++)], + await tester.pumpWidget( + harness( + f, + ClideMenu( + onClose: () => closed++, + entries: [ClideMenuItem(label: 'Toggle', keepOpenOnSelect: true, onSelect: () => picked++)], + ), ), - )); + ); await tester.pump(); await tester.tap(find.text('Toggle')); await tester.pump(); diff --git a/test/widgets/src/clide_pane_test.dart b/test/widgets/src/clide_pane_test.dart index 08f132bc..be67f5a0 100644 --- a/test/widgets/src/clide_pane_test.dart +++ b/test/widgets/src/clide_pane_test.dart @@ -17,14 +17,16 @@ void main() { tearDown(() => f.dispose()); testWidgets('conveys its status while its contribution is focused, clears on blur', (tester) async { - await tester.pumpWidget(harness( - f, - const ClidePane( - contributionId: 'pane.x', - statusWidget: Text('S', textDirection: TextDirection.ltr), - child: SizedBox(), + await tester.pumpWidget( + harness( + f, + const ClidePane( + contributionId: 'pane.x', + statusWidget: Text('S', textDirection: TextDirection.ltr), + child: SizedBox(), + ), ), - )); + ); final focus = f.services.focus; expect(focus.activeStatusWidget, isNull); // not focused yet @@ -43,34 +45,38 @@ void main() { // mid-build ("markNeedsBuild during build"). A focus listener must be in // the tree to reproduce it. f.services.focus.setActive(slot: Slots.workspace, contributionId: 'pane.x'); - await tester.pumpWidget(harness( - f, - const Column( - children: [ - PaneContextStatusItem(), - ClidePane( - contributionId: 'pane.x', - statusWidget: Text('S', textDirection: TextDirection.ltr), - child: SizedBox(), - ), - ], + await tester.pumpWidget( + harness( + f, + const Column( + children: [ + PaneContextStatusItem(), + ClidePane( + contributionId: 'pane.x', + statusWidget: Text('S', textDirection: TextDirection.ltr), + child: SizedBox(), + ), + ], + ), ), - )); + ); await tester.pump(); // run the deferred post-frame convey expect(tester.takeException(), isNull); expect(f.services.focus.activeStatusWidget, isA()); }); testWidgets('an inactive pane never conveys', (tester) async { - await tester.pumpWidget(harness( - f, - const ClidePane( - contributionId: 'pane.x', - active: false, - statusWidget: Text('S', textDirection: TextDirection.ltr), - child: SizedBox(), + await tester.pumpWidget( + harness( + f, + const ClidePane( + contributionId: 'pane.x', + active: false, + statusWidget: Text('S', textDirection: TextDirection.ltr), + child: SizedBox(), + ), ), - )); + ); f.services.focus.setActive(slot: Slots.workspace, contributionId: 'pane.x'); await tester.pump(); expect(f.services.focus.activeStatusWidget, isNull); @@ -80,19 +86,21 @@ void main() { f.services.focus.setActive(slot: Slots.workspace, contributionId: 'pane.x'); var label = 'A'; late StateSetter setLabel; - await tester.pumpWidget(harness( - f, - StatefulBuilder( - builder: (ctx, setState) { - setLabel = setState; - return ClidePane( - contributionId: 'pane.x', - statusWidget: Text(label, textDirection: TextDirection.ltr), - child: const SizedBox(), - ); - }, + await tester.pumpWidget( + harness( + f, + StatefulBuilder( + builder: (ctx, setState) { + setLabel = setState; + return ClidePane( + contributionId: 'pane.x', + statusWidget: Text(label, textDirection: TextDirection.ltr), + child: const SizedBox(), + ); + }, + ), ), - )); + ); await tester.pump(); expect((f.services.focus.activeStatusWidget! as Text).data, 'A'); diff --git a/test/widgets/src/clide_selectable_test.dart b/test/widgets/src/clide_selectable_test.dart index c2d27e98..d76ee2d9 100644 --- a/test/widgets/src/clide_selectable_test.dart +++ b/test/widgets/src/clide_selectable_test.dart @@ -137,12 +137,20 @@ void main() { // Both the paragraph text and the code text should appear in the // clipboard. SelectableRegion concatenates selectables with newlines. final copied = clipboard.text ?? ''; - expect(copied, contains(paraText), - reason: 'paragraph text must be selectable via SelectableRegion after ' - 'converting ClideMarkdown from RichText to Text.rich'); - expect(copied, contains(codeText), - reason: 'code block text must be selectable via SelectableRegion after ' - 'converting ClideCodeBlock from RichText to Text.rich'); + expect( + copied, + contains(paraText), + reason: + 'paragraph text must be selectable via SelectableRegion after ' + 'converting ClideMarkdown from RichText to Text.rich', + ); + expect( + copied, + contains(codeText), + reason: + 'code block text must be selectable via SelectableRegion after ' + 'converting ClideCodeBlock from RichText to Text.rich', + ); }); testWidgets('Text.rich nodes are owned by Text widgets, registering with SelectableRegion', (tester) async { @@ -185,13 +193,14 @@ void main() { // what Text.rich builds. A bare RichText(...) has no Text parent. final richTexts = find.byType(RichText); for (final el in richTexts.evaluate()) { - final textAncestor = find.ancestor( - of: find.byElementPredicate((e) => e == el), - matching: find.byType(Text), + final textAncestor = find.ancestor(of: find.byElementPredicate((e) => e == el), matching: find.byType(Text)); + expect( + textAncestor, + findsAtLeastNWidgets(1), + reason: + 'RichText for "${(el.widget as RichText).text.toPlainText()}" ' + 'should be owned by a Text (built via Text.rich, not bare RichText)', ); - expect(textAncestor, findsAtLeastNWidgets(1), - reason: 'RichText for "${(el.widget as RichText).text.toPlainText()}" ' - 'should be owned by a Text (built via Text.rich, not bare RichText)'); } }); }); diff --git a/test/widgets/src/clide_status_indicator_test.dart b/test/widgets/src/clide_status_indicator_test.dart index f5ae5c88..92d52b73 100644 --- a/test/widgets/src/clide_status_indicator_test.dart +++ b/test/widgets/src/clide_status_indicator_test.dart @@ -17,15 +17,17 @@ void main() { // Disable animations so the spinner is static (no perpetual rotation) and // pumpAndSettle can settle — mirroring how every animated widget behaves // under reduced motion. - Future pump(WidgetTester tester, Widget child) => tester.pumpWidget(harness( - f, - Builder( - builder: (ctx) => MediaQuery( - data: MediaQuery.of(ctx).copyWith(disableAnimations: true), - child: Center(child: child), - ), + Future pump(WidgetTester tester, Widget child) => tester.pumpWidget( + harness( + f, + Builder( + builder: (ctx) => MediaQuery( + data: MediaQuery.of(ctx).copyWith(disableAnimations: true), + child: Center(child: child), ), - )); + ), + ), + ); Finder iconWith(Object painterType) => find.byWidgetPredicate((w) => w is ClideIcon && w.painter.runtimeType == painterType); @@ -64,15 +66,19 @@ void main() { // 'running' key. Pre-fix this threw "Duplicate keys found". var status = ClideRunStatus.running; late StateSetter set; - await tester.pumpWidget(harness( - f, - Center( - child: StatefulBuilder(builder: (ctx, s) { - set = s; - return ClideStatusIndicator(status: status); - }), + await tester.pumpWidget( + harness( + f, + Center( + child: StatefulBuilder( + builder: (ctx, s) { + set = s; + return ClideStatusIndicator(status: status); + }, + ), + ), ), - )); + ); void flip(ClideRunStatus next) => set(() => status = next); diff --git a/test/widgets/src/clide_surface_test.dart b/test/widgets/src/clide_surface_test.dart index 2d867c16..fa18fd29 100644 --- a/test/widgets/src/clide_surface_test.dart +++ b/test/widgets/src/clide_surface_test.dart @@ -13,9 +13,7 @@ void main() { tearDown(() async => f.dispose()); testWidgets('default background comes from panelBackground token', (tester) async { - await tester.pumpWidget( - harness(f, const ClideSurface(child: Text('x'))), - ); + await tester.pumpWidget(harness(f, const ClideSurface(child: Text('x')))); final container = tester.widget(find.byType(Container)); final decoration = container.decoration as BoxDecoration; expect(decoration.color, f.services.theme.current.surface.panelBackground); @@ -23,24 +21,14 @@ void main() { testWidgets('explicit color overrides the token default', (tester) async { const custom = Color(0xFF123456); - await tester.pumpWidget( - harness(f, const ClideSurface(color: custom, child: Text('x'))), - ); + await tester.pumpWidget(harness(f, const ClideSurface(color: custom, child: Text('x')))); final container = tester.widget(find.byType(Container)); final decoration = container.decoration as BoxDecoration; expect(decoration.color, custom); }); testWidgets('border when provided renders a BoxBorder', (tester) async { - await tester.pumpWidget( - harness( - f, - ClideSurface( - border: f.services.theme.current.surface.modalSurfaceBorder, - child: const Text('x'), - ), - ), - ); + await tester.pumpWidget(harness(f, ClideSurface(border: f.services.theme.current.surface.modalSurfaceBorder, child: const Text('x')))); final container = tester.widget(find.byType(Container)); final decoration = container.decoration as BoxDecoration; expect(decoration.border, isNotNull); diff --git a/test/widgets/src/clide_tab_bar_test.dart b/test/widgets/src/clide_tab_bar_test.dart index 97fc7772..3cadee8d 100644 --- a/test/widgets/src/clide_tab_bar_test.dart +++ b/test/widgets/src/clide_tab_bar_test.dart @@ -64,23 +64,11 @@ void main() { final handle = tester.ensureSemantics(); expect( tester.getSemantics(find.bySemanticsLabel('Files')), - matchesSemantics( - label: 'Files', - isButton: true, - isSelected: true, - hasSelectedState: true, - hasTapAction: true, - ), + matchesSemantics(label: 'Files', isButton: true, isSelected: true, hasSelectedState: true, hasTapAction: true), ); expect( tester.getSemantics(find.bySemanticsLabel('Git')), - matchesSemantics( - label: 'Git', - isButton: true, - isSelected: false, - hasSelectedState: true, - hasTapAction: true, - ), + matchesSemantics(label: 'Git', isButton: true, isSelected: false, hasSelectedState: true, hasTapAction: true), ); handle.dispose(); }); diff --git a/test/widgets/src/clide_tappable_test.dart b/test/widgets/src/clide_tappable_test.dart index 46f55927..04b30651 100644 --- a/test/widgets/src/clide_tappable_test.dart +++ b/test/widgets/src/clide_tappable_test.dart @@ -23,16 +23,14 @@ void main() { var tapped = 0; final node = FocusNode(); addTearDown(node.dispose); - await tester.pumpWidget(harness( - f, - Center( - child: ClideTappable( - focusNode: node, - onTap: () => tapped++, - builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24), + await tester.pumpWidget( + harness( + f, + Center( + child: ClideTappable(focusNode: node, onTap: () => tapped++, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)), ), ), - )); + ); expect(node.hasFocus, isFalse); node.requestFocus(); @@ -46,17 +44,19 @@ void main() { var tapped = 0; final node = FocusNode(); addTearDown(node.dispose); - await tester.pumpWidget(harness( - f, - Center( - child: ClideTappable( - focusNode: node, - autofocus: true, - onTap: () => tapped++, - builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24), + await tester.pumpWidget( + harness( + f, + Center( + child: ClideTappable( + focusNode: node, + autofocus: true, + onTap: () => tapped++, + builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24), + ), ), ), - )); + ); await tester.pump(); expect(node.hasFocus, isTrue); // Dispatch ActivateIntent directly against the focused context @@ -70,16 +70,14 @@ void main() { testWidgets('disabled tappable (onTap == null) cannot receive focus', (tester) async { final node = FocusNode(); addTearDown(node.dispose); - await tester.pumpWidget(harness( - f, - Center( - child: ClideTappable( - focusNode: node, - onTap: null, - builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24), + await tester.pumpWidget( + harness( + f, + Center( + child: ClideTappable(focusNode: node, onTap: null, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)), ), ), - )); + ); node.requestFocus(); await tester.pump(); expect(node.hasFocus, isFalse, reason: 'canRequestFocus is false when onTap is null'); @@ -88,16 +86,14 @@ void main() { testWidgets('focus ring appears when focused, gone when unfocused', (tester) async { final node = FocusNode(); addTearDown(node.dispose); - await tester.pumpWidget(harness( - f, - Center( - child: ClideTappable( - focusNode: node, - onTap: () {}, - builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24), + await tester.pumpWidget( + harness( + f, + Center( + child: ClideTappable(focusNode: node, onTap: () {}, builder: (_, hovered, pressed) => const SizedBox(width: 60, height: 24)), ), ), - )); + ); Iterable ringColors() => tester .widgetList(find.descendant(of: find.byType(ClideTappable), matching: find.byType(DecoratedBox))) diff --git a/test/widgets/src/clide_toast_test.dart b/test/widgets/src/clide_toast_test.dart index 0c4619b5..3bd8fc08 100644 --- a/test/widgets/src/clide_toast_test.dart +++ b/test/widgets/src/clide_toast_test.dart @@ -17,22 +17,26 @@ void main() { tearDown(() async => f.dispose()); Widget host(Widget child) => Directionality( - textDirection: TextDirection.ltr, - child: ClideKernel( - services: f.services, - child: ClideTheme( - controller: f.services.theme, - child: Align(alignment: Alignment.topLeft, child: child), - ), - ), - ); + textDirection: TextDirection.ltr, + child: ClideKernel( + services: f.services, + child: ClideTheme( + controller: f.services.theme, + child: Align(alignment: Alignment.topLeft, child: child), + ), + ), + ); testWidgets('renders the message and calls onDismiss when the × is tapped', (tester) async { var dismissed = false; - await tester.pumpWidget(host(ClideToast( - entry: const ToastEntry(id: 1, message: 'Pushed to origin/main', severity: ToastSeverity.success), - onDismiss: () => dismissed = true, - ))); + await tester.pumpWidget( + host( + ClideToast( + entry: const ToastEntry(id: 1, message: 'Pushed to origin/main', severity: ToastSeverity.success), + onDismiss: () => dismissed = true, + ), + ), + ); await tester.pump(const Duration(milliseconds: 300)); // settle entrance expect(find.text('Pushed to origin/main'), findsOneWidget); @@ -43,26 +47,23 @@ void main() { }); testWidgets('exposes the message as a live region (a11y)', (tester) async { - await tester.pumpWidget(host(ClideToast( - entry: const ToastEntry(id: 1, message: 'Heads up', severity: ToastSeverity.warning), - onDismiss: () {}, - ))); + await tester.pumpWidget( + host( + ClideToast( + entry: const ToastEntry(id: 1, message: 'Heads up', severity: ToastSeverity.warning), + onDismiss: () {}, + ), + ), + ); await tester.pump(const Duration(milliseconds: 300)); // The message is wrapped in a live-region Semantics so screen readers // announce it when it appears. - expect( - find.byWidgetPredicate((w) => w is Semantics && w.properties.liveRegion == true && w.properties.label == 'Heads up'), - findsOneWidget, - ); + expect(find.byWidgetPredicate((w) => w is Semantics && w.properties.liveRegion == true && w.properties.label == 'Heads up'), findsOneWidget); }); testWidgets('overlay renders a card per queued toast and dismiss removes one', (tester) async { - await tester.pumpWidget(host(const SizedBox( - width: 800, - height: 600, - child: Stack(children: [ToastOverlay()]), - ))); + await tester.pumpWidget(host(const SizedBox(width: 800, height: 600, child: Stack(children: [ToastOverlay()])))); await tester.pump(); expect(find.byType(ClideToast), findsNothing); diff --git a/test/widgets/src/clide_tooltip_test.dart b/test/widgets/src/clide_tooltip_test.dart index 931e1f40..cd1e8e77 100644 --- a/test/widgets/src/clide_tooltip_test.dart +++ b/test/widgets/src/clide_tooltip_test.dart @@ -112,12 +112,7 @@ void main() { // The Positioned ancestor of the tooltip uses `bottom:` (above-mode), // not `top:`, when there isn't enough room below. - final positioned = tester.widget( - find.ancestor( - of: find.text('above'), - matching: find.byType(Positioned), - ), - ); + final positioned = tester.widget(find.ancestor(of: find.text('above'), matching: find.byType(Positioned))); expect(positioned.bottom, isNotNull); expect(positioned.top, isNull); }); diff --git a/test/widgets/src/clide_typeahead_test.dart b/test/widgets/src/clide_typeahead_test.dart index cec1e781..c126f809 100644 --- a/test/widgets/src/clide_typeahead_test.dart +++ b/test/widgets/src/clide_typeahead_test.dart @@ -19,21 +19,23 @@ void main() { var picked = ''; List sugg = ['alice', 'bob']; late StateSetter setOuter; - await tester.pumpWidget(anchoredHarness( - f, - StatefulBuilder( - builder: (ctx, setState) { - setOuter = setState; - return ClideTypeahead( - suggestions: sugg, - onSelect: (v) => picked = v, - formatLabel: (n) => '@$n', - child: const SizedBox(width: 200, height: 24, child: ClideText('field')), - ); - }, + await tester.pumpWidget( + anchoredHarness( + f, + StatefulBuilder( + builder: (ctx, setState) { + setOuter = setState; + return ClideTypeahead( + suggestions: sugg, + onSelect: (v) => picked = v, + formatLabel: (n) => '@$n', + child: const SizedBox(width: 200, height: 24, child: ClideText('field')), + ); + }, + ), + alignment: Alignment.topLeft, ), - alignment: Alignment.topLeft, - )); + ); await tester.pumpAndSettle(); expect(find.text('@alice'), findsOneWidget); expect(find.text('@bob'), findsOneWidget); diff --git a/test/widgets/zero_coverage_widgets_test.dart b/test/widgets/zero_coverage_widgets_test.dart index 2dde5f5d..fe4ba756 100644 --- a/test/widgets/zero_coverage_widgets_test.dart +++ b/test/widgets/zero_coverage_widgets_test.dart @@ -34,19 +34,23 @@ void main() { }); testWidgets('opens to show an input field + the registered commands', (tester) async { - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'cmd.one', - title: 'Command One', - defaultBinding: 'ctrl+1', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); - f.services.commands.register(CommandContribution( - id: 'c2', - command: 'cmd.two', - title: 'Command Two', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'cmd.one', + title: 'Command One', + defaultBinding: 'ctrl+1', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); + f.services.commands.register( + CommandContribution( + id: 'c2', + command: 'cmd.two', + title: 'Command Two', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -57,15 +61,17 @@ void main() { testWidgets('tapping a row invokes the command + closes the palette', (tester) async { var invocations = 0; - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'tap.target', - title: 'Tap Target', - run: (_) async { - invocations++; - return IpcResponse.ok(id: '', data: const {}); - }, - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'tap.target', + title: 'Tap Target', + run: (_) async { + invocations++; + return IpcResponse.ok(id: '', data: const {}); + }, + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -75,18 +81,22 @@ void main() { }); testWidgets('typing narrows the visible commands via palette.setFilter', (tester) async { - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'alpha.cmd', - title: 'Alpha', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); - f.services.commands.register(CommandContribution( - id: 'c2', - command: 'beta.cmd', - title: 'Beta', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'alpha.cmd', + title: 'Alpha', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); + f.services.commands.register( + CommandContribution( + id: 'c2', + command: 'beta.cmd', + title: 'Beta', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -99,21 +109,25 @@ void main() { testWidgets('submitting the input invokes the first filtered command', (tester) async { var invocations = 0; - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'submit.target', - title: 'Submit Target', - run: (_) async { - invocations++; - return IpcResponse.ok(id: '', data: const {}); - }, - )); - f.services.commands.register(CommandContribution( - id: 'c2', - command: 'other.cmd', - title: 'Other', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'submit.target', + title: 'Submit Target', + run: (_) async { + invocations++; + return IpcResponse.ok(id: '', data: const {}); + }, + ), + ); + f.services.commands.register( + CommandContribution( + id: 'c2', + command: 'other.cmd', + title: 'Other', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -126,12 +140,14 @@ void main() { }); testWidgets('hovering a palette row updates its hover state', (tester) async { - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'hover.cmd', - title: 'Hoverable', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'hover.cmd', + title: 'Hoverable', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -149,12 +165,14 @@ void main() { testWidgets('arrow keys move the highlighted command (T-100)', (tester) async { for (final id in ['a', 'b', 'c']) { - f.services.commands.register(CommandContribution( - id: id, - command: 'cmd.$id', - title: 'Item $id', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: id, + command: 'cmd.$id', + title: 'Item $id', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); } f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); @@ -181,15 +199,17 @@ void main() { testWidgets('PaletteAcceptIntent invokes the highlighted command (T-100)', (tester) async { var which = ''; for (final id in ['a', 'b', 'c']) { - f.services.commands.register(CommandContribution( - id: id, - command: 'cmd.$id', - title: 'Item $id', - run: (_) async { - which = id; - return IpcResponse.ok(id: '', data: const {}); - }, - )); + f.services.commands.register( + CommandContribution( + id: id, + command: 'cmd.$id', + title: 'Item $id', + run: (_) async { + which = id; + return IpcResponse.ok(id: '', data: const {}); + }, + ), + ); } f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); @@ -204,12 +224,14 @@ void main() { }); testWidgets('DismissIntent closes the palette (T-100)', (tester) async { - f.services.commands.register(CommandContribution( - id: 'c1', - command: 'thing', - title: 'Thing', - run: (_) async => IpcResponse.ok(id: '', data: const {}), - )); + f.services.commands.register( + CommandContribution( + id: 'c1', + command: 'thing', + title: 'Thing', + run: (_) async => IpcResponse.ok(id: '', data: const {}), + ), + ); f.services.palette.open(); await tester.pumpWidget(harness(f, Stack(children: const [ClidePalette()]))); await tester.pumpAndSettle(); @@ -240,13 +262,7 @@ void main() { testWidgets('debounces onChanged calls to the configured duration', (tester) async { final values = []; - await tester.pumpWidget(harness( - f, - ClideFilterBox( - onChanged: values.add, - debounce: const Duration(milliseconds: 50), - ), - )); + await tester.pumpWidget(harness(f, ClideFilterBox(onChanged: values.add, debounce: const Duration(milliseconds: 50)))); // Type into the editable text field. await tester.enterText(find.byType(EditableText), 'abc'); // Before the debounce elapses no call. @@ -259,13 +275,7 @@ void main() { testWidgets('clear button appears once the field has text and resets onChanged when tapped', (tester) async { final values = []; - await tester.pumpWidget(harness( - f, - ClideFilterBox( - onChanged: values.add, - debounce: const Duration(milliseconds: 10), - ), - )); + await tester.pumpWidget(harness(f, ClideFilterBox(onChanged: values.add, debounce: const Duration(milliseconds: 10)))); // No clear icon when the field is empty. expect(find.byType(GestureDetector), findsNothing); await tester.enterText(find.byType(EditableText), 'foo'); @@ -280,13 +290,7 @@ void main() { testWidgets('onSubmitted fires when the field is submitted', (tester) async { String? submitted; - await tester.pumpWidget(harness( - f, - ClideFilterBox( - onChanged: (_) {}, - onSubmitted: (v) => submitted = v, - ), - )); + await tester.pumpWidget(harness(f, ClideFilterBox(onChanged: (_) {}, onSubmitted: (v) => submitted = v))); await tester.enterText(find.byType(EditableText), 'submit-me'); await tester.testTextInput.receiveAction(TextInputAction.done); await tester.pump(); @@ -294,10 +298,7 @@ void main() { }); testWidgets('renders the hint as a placeholder (shown empty, hidden once typed); icon optional', (tester) async { - await tester.pumpWidget(harness( - f, - ClideFilterBox(onChanged: (_) {}, hint: 'Replace', showIcon: false), - )); + await tester.pumpWidget(harness(f, ClideFilterBox(onChanged: (_) {}, hint: 'Replace', showIcon: false))); // Placeholder visible while empty; the icon slot is kept for alignment but // draws a blank glyph (showIcon: false → EmptyIconPainter). expect(find.text('Replace'), findsOneWidget); @@ -318,14 +319,21 @@ void main() { testWidgets('left / center / right factories render', (tester) async { final wc = WindowControls(); addTearDown(wc.dispose); - await tester.pumpWidget(harness( - f, - Column(children: [ - SizedBox(width: 200, child: ColumnHat.left(windowControls: wc)), - SizedBox(width: 200, child: ColumnHat.center(windowControls: wc, project: 'clide', branch: 'main')), - SizedBox(width: 200, child: ColumnHat.right(windowControls: wc)), - ]), - )); + await tester.pumpWidget( + harness( + f, + Column( + children: [ + SizedBox(width: 200, child: ColumnHat.left(windowControls: wc)), + SizedBox( + width: 200, + child: ColumnHat.center(windowControls: wc, project: 'clide', branch: 'main'), + ), + SizedBox(width: 200, child: ColumnHat.right(windowControls: wc)), + ], + ), + ), + ); // Center hat renders the joined label. expect(find.text('clide > main'), findsOneWidget); }); @@ -333,10 +341,7 @@ void main() { testWidgets('center hat falls back to "clide" with no project/branch', (tester) async { final wc = WindowControls(); addTearDown(wc.dispose); - await tester.pumpWidget(harness( - f, - SizedBox(width: 200, child: ColumnHat.center(windowControls: wc)), - )); + await tester.pumpWidget(harness(f, SizedBox(width: 200, child: ColumnHat.center(windowControls: wc)))); expect(find.text('clide'), findsOneWidget); }); }); @@ -348,17 +353,19 @@ void main() { testWidgets('renders one button per item, marks the active one, fires onSelect', (tester) async { String? selected; - await tester.pumpWidget(harness( - f, - ClideIconRail( - items: [ - ClideIconRailItem(id: 'a', icon: PhosphorIcons.byName('folder'), tooltip: 'A'), - ClideIconRailItem(id: 'b', icon: PhosphorIcons.byName('git-branch'), tooltip: 'B'), - ], - activeId: 'a', - onSelect: (id) => selected = id, + await tester.pumpWidget( + harness( + f, + ClideIconRail( + items: [ + ClideIconRailItem(id: 'a', icon: PhosphorIcons.byName('folder'), tooltip: 'A'), + ClideIconRailItem(id: 'b', icon: PhosphorIcons.byName('git-branch'), tooltip: 'B'), + ], + activeId: 'a', + onSelect: (id) => selected = id, + ), ), - )); + ); await tester.pumpAndSettle(); // Each item exposes a tooltip-labeled semantics button. expect(find.bySemanticsLabel('A'), findsOneWidget); @@ -368,20 +375,20 @@ void main() { }); testWidgets('scrolls instead of overflowing when items exceed the width', (tester) async { - await tester.pumpWidget(harness( - f, - SizedBox( - width: 120, - height: 30, - child: ClideIconRail( - items: [ - for (var i = 0; i < 10; i++) ClideIconRailItem(id: '$i', icon: PhosphorIcons.byName('folder'), tooltip: 'Tab $i'), - ], - activeId: '0', - onSelect: (_) {}, + await tester.pumpWidget( + harness( + f, + SizedBox( + width: 120, + height: 30, + child: ClideIconRail( + items: [for (var i = 0; i < 10; i++) ClideIconRailItem(id: '$i', icon: PhosphorIcons.byName('folder'), tooltip: 'Tab $i')], + activeId: '0', + onSelect: (_) {}, + ), ), ), - )); + ); await tester.pumpAndSettle(); // No RenderFlex overflow (would surface as a thrown FlutterError), // and the rail is horizontally scrollable. @@ -397,17 +404,16 @@ void main() { testWidgets('renders the rotated label + a badge dot when badgeCount > 0', (tester) async { var taps = 0; - await tester.pumpWidget(harness( - f, - SizedBox( + await tester.pumpWidget( + harness( + f, + SizedBox( height: 200, width: ClideSpine.width, - child: ClideSpine( - label: 'SIDEBAR', - badgeCount: 3, - onExpand: () => taps++, - )), - )); + child: ClideSpine(label: 'SIDEBAR', badgeCount: 3, onExpand: () => taps++), + ), + ), + ); await tester.pumpAndSettle(); expect(find.text('SIDEBAR'), findsOneWidget); // Tap to expand. @@ -416,17 +422,16 @@ void main() { }); testWidgets('renders the right-side variant without throwing', (tester) async { - await tester.pumpWidget(harness( - f, - SizedBox( + await tester.pumpWidget( + harness( + f, + SizedBox( height: 200, width: ClideSpine.width, - child: ClideSpine( - label: 'CTX', - side: SpineSide.right, - onExpand: () {}, - )), - )); + child: ClideSpine(label: 'CTX', side: SpineSide.right, onExpand: () {}), + ), + ), + ); await tester.pumpAndSettle(); expect(find.text('CTX'), findsOneWidget); }); @@ -440,13 +445,15 @@ void main() { testWidgets('wraps a child and renders 8 resize zones', (tester) async { final wc = WindowControls(); addTearDown(wc.dispose); - await tester.pumpWidget(harness( - f, - ClideResizeBorder( - windowControls: wc, - child: const SizedBox.expand(child: ColoredBox(color: Color(0xFF000000))), + await tester.pumpWidget( + harness( + f, + ClideResizeBorder( + windowControls: wc, + child: const SizedBox.expand(child: ColoredBox(color: Color(0xFF000000))), + ), ), - )); + ); await tester.pumpAndSettle(); // 4 corners + 4 edges = 8 zones each with a MouseRegion. expect(find.byType(MouseRegion).evaluate().length, greaterThanOrEqualTo(8));